The Android Doze Mode Trap
Starting with Android 8.0 (API level 26) and tightened aggressively in Android 13/14, the operating system imposes draconian background execution limits. If an application enters the background and the screen turns off, the OS initiates Doze maintenance windows, throttling CPU cycles and suspending network threads. For a tactical push-to-talk application like TrueTalkie, where operators must hear urgent incoming voice calls immediately with locked phones in pockets, standard background execution is completely inadequate.
Why React Native JS Bridges Get Suspended
Off-the-shelf React Native audio packages run their event dispatchers over the JavaScript bridge. Once Android suspends the main activity lifecycle, the React Native JS thread is paused. Incoming UDP network packets still hit the device's NIC, but the JavaScript runtime is asleep and cannot process or play the audio.
- ›React Native JS thread pauses within seconds of screen lock.
- ›Standard audio packages cannot maintain real-time socket listeners in background.
- ›Audio processing and socket listening must live entirely on the native JVM thread.
Building the Native Kotlin Foreground Service
The solution requires an explicit Android Foreground Service running with the `FOREGROUND_SERVICE_TYPE_MICROPHONE` and `FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE` flags. This signals to Android's `ActivityManager` that the service is critical to user experience and must never be killed by low-memory killers (LMK).
class TacticalAudioService : Service() {
private var wakeLock: PowerManager.WakeLock? = null
override fun onCreate() {
super.onCreate()
val powerManager = getSystemService(Context.POWER_SERVICE) as PowerManager
wakeLock = powerManager.newWakeLock(
PowerManager.PARTIAL_WAKE_LOCK,
"TrueTalkie::AudioStreamingWakeLock"
).apply { acquire(10 * 60 * 1000L) }
val notification = createNotification()
startForeground(
NOTIFICATION_ID,
notification,
ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE
)
}
}Sticky Notifications & Audio WakeLocks
We configured a persistent notification with `setOngoing(true)` and priority `PRIORITY_HIGH`. Coupled with a `PARTIAL_WAKE_LOCK`, the CPU core stays active just enough to process incoming PCM buffers while allowing the GPU and display to sleep.
Hardware AudioRecord & Echo Cancellation
We instantiated `AudioRecord` at 16,000Hz (16kHz wideband) mono 16-bit PCM. By activating Android's hardware `AcousticEchoCanceler` and `NoiseSuppressor` effects directly on the audio session, background static is eliminated at the hardware driver level.
Bridging Kotlin Buffers to React Native
To avoid bridging gigabytes of raw PCM bytes over the JS bridge, the Kotlin module manages the audio ring buffer in native memory. React Native only receives high-level events: `onAudioIncoming`, `onTransmissionStarted`, and `onChannelSwitched`, keeping the JS thread lightweight.
Power Consumption & Battery Benchmarks
Continuous audio standby testing over 8 hours of background operation demonstrated a battery drain of less than 2.1% per hour, proving that low-latency real-time voice can be sustained without draining field batteries.
Conclusion & Production Best Practices
React Native excels at cross-platform UI speed, but when device hardware, background audio, and raw OS lifecycles are on the line, writing clean, idiomatic native Kotlin modules is the only robust engineering choice.