Wake locks are Android power management tools that prevent the system from entering sleep states. React Native Background Guardian provides two types of wake locks: partial wake locks for background CPU execution and screen wake locks for keeping the display on.
Understanding wake locks
Android devices aggressively conserve power by entering sleep states when idle. Wake locks allow apps to signal that they need the device to stay awake:
- Partial wake lock: Keeps the CPU running but allows the screen to turn off
- Screen wake lock: Keeps the screen on while the app is in the foreground
Wake locks consume significant battery power. Always release them when your task is complete, and use appropriate timeouts to prevent battery drain if your app crashes.
Partial wake locks
Partial wake locks keep the CPU running while allowing the screen, keyboard backlight, and other components to turn off. This is essential for background processing.
When to use partial wake locks
Use partial wake locks for:
- Background audio playback - Music or podcast players
- Long-running downloads - File downloads or sync operations
- Location tracking - Continuous GPS tracking
- Real-time data processing - Sensor data collection and processing
- Network operations - WebSocket connections or continuous polling
Acquiring a partial wake lock
import BackgroundGuardian from 'react-native-background-guardian';
// Acquire a wake lock with a custom tag and 10-minute timeout
const acquired = await BackgroundGuardian.acquireWakeLock(
'MyBackgroundTask',
10 * 60 * 1000 // 10 minutes in milliseconds
);
if (acquired) {
console.log('Wake lock acquired successfully');
// Perform your background work
await doBackgroundWork();
// Always release when done
await BackgroundGuardian.releaseWakeLock();
} else {
console.error('Failed to acquire wake lock');
}
Parameters explained
Tag parameter
The tag parameter is an optional identifier for debugging:
// Default tag
await BackgroundGuardian.acquireWakeLock(); // Uses "BackgroundGuardian"
// Custom tag for debugging
await BackgroundGuardian.acquireWakeLock('AudioPlayback');
await BackgroundGuardian.acquireWakeLock('LocationTracking');
await BackgroundGuardian.acquireWakeLock('DataSync');
The tag appears in system logs and ADB output, making it easier to identify which component is holding the wake lock.
Internally, the library creates wake lock tags in the format BackgroundGuardian:WakeLock:YourTag. From BackgroundGuardianModule.kt:77:val wakeLockTag = if (tag.isNotEmpty()) "$WAKE_LOCK_TAG:$tag" else WAKE_LOCK_TAG
Timeout parameter
The timeout parameter sets an automatic release time in milliseconds:
// Default timeout: 24 hours (86,400,000 ms)
await BackgroundGuardian.acquireWakeLock('Task');
// Custom timeout: 5 minutes
await BackgroundGuardian.acquireWakeLock('QuickSync', 5 * 60 * 1000);
// Custom timeout: 1 hour
await BackgroundGuardian.acquireWakeLock('LongTask', 60 * 60 * 1000);
Always set an appropriate timeout! If your app crashes while holding a wake lock without a timeout, the CPU will remain active indefinitely, draining the battery. The default 24-hour timeout prevents this, but shorter timeouts are better for battery life.
How it works internally
On Android, partial wake locks are implemented using PowerManager.WakeLock with the PARTIAL_WAKE_LOCK flag:
// From BackgroundGuardianModule.kt:75-84
val powerManager = context.getSystemService(Context.POWER_SERVICE) as PowerManager
val wakeLockTag = if (tag.isNotEmpty()) "$WAKE_LOCK_TAG:$tag" else WAKE_LOCK_TAG
wakeLock = powerManager.newWakeLock(
PowerManager.PARTIAL_WAKE_LOCK,
wakeLockTag
).apply {
acquire(timeout.toLong())
}
On iOS, acquireWakeLock() is a no-op that returns true immediately, as iOS handles background execution differently through Background Modes configured in Xcode.
Releasing wake locks
Always release wake locks when your background work is complete:
const released = await BackgroundGuardian.releaseWakeLock();
if (released) {
console.log('Wake lock released successfully');
} else {
console.log('No wake lock was held or release failed');
}
The implementation safely handles cases where no wake lock was acquired:
// From BackgroundGuardianModule.kt:105-132
override fun releaseWakeLock(promise: Promise) {
try {
val currentWakeLock = wakeLock
if (currentWakeLock == null) {
Log.d(TAG, "No wake lock to release")
promise.resolve(true)
return
}
if (!currentWakeLock.isHeld) {
Log.d(TAG, "Wake lock already released")
wakeLock = null
promise.resolve(true)
return
}
currentWakeLock.release()
wakeLock = null
Log.d(TAG, "Wake lock released successfully")
promise.resolve(true)
} catch (e: Exception) {
Log.e(TAG, "Failed to release wake lock", e)
wakeLock = null
promise.resolve(false)
}
}
Checking wake lock status
You can check if a wake lock is currently held:
const isHeld = await BackgroundGuardian.isWakeLockHeld();
if (isHeld) {
console.log('Wake lock is currently active');
} else {
console.log('No wake lock is held');
}
This is useful for updating UI state or debugging:
useEffect(() => {
const checkStatus = async () => {
const isHeld = await BackgroundGuardian.isWakeLockHeld();
setWakeLockActive(isHeld);
};
const interval = setInterval(checkStatus, 1000);
return () => clearInterval(interval);
}, []);
On Android, isWakeLockHeld() checks the actual wake lock state. On iOS, it always returns false as wake locks don’t exist on that platform.
Reference counting behavior
The library uses a single-instance wake lock model, not reference counting:
// First acquisition succeeds
await BackgroundGuardian.acquireWakeLock('Task1'); // ✅ Acquires lock
// Second acquisition is a no-op
await BackgroundGuardian.acquireWakeLock('Task2'); // ⚠️ Returns true but doesn't acquire a second lock
// Single release affects both
await BackgroundGuardian.releaseWakeLock(); // ✅ Releases the only lock
From the implementation:
// From BackgroundGuardianModule.kt:57-65
override fun acquireWakeLock(tag: String, timeout: Double, promise: Promise) {
try {
// If we already have an active wake lock, return true without acquiring another
wakeLock?.let {
if (it.isHeld) {
Log.d(TAG, "Wake lock already held, skipping acquisition")
promise.resolve(true)
return
}
}
// ... acquire new lock
}
}
Important implication: If you need multiple concurrent wake locks for different components, you’ll need to implement your own reference counting:class WakeLockManager {
private refCount = 0;
async acquire(tag: string) {
if (this.refCount === 0) {
await BackgroundGuardian.acquireWakeLock(tag);
}
this.refCount++;
}
async release() {
this.refCount--;
if (this.refCount === 0) {
await BackgroundGuardian.releaseWakeLock();
}
}
}
Screen wake locks
Screen wake locks keep the display on while the app is in the foreground. This is different from partial wake locks and does not keep the CPU running in the background.
When to use screen wake locks
Use screen wake locks for:
- Video playback - Prevent screen timeout during videos
- Recipe apps - Keep screen on while cooking
- Navigation apps - Display always visible during directions
- Kiosk mode - Public display terminals
- Presentation mode - Slides or dashboards
- Reading apps - Prevent screen timeout while reading
Enabling screen wake lock
const enabled = await BackgroundGuardian.enableScreenWakeLock();
if (enabled) {
console.log('Screen will stay on');
}
Disabling screen wake lock
Always disable the screen wake lock when the user is done:
await BackgroundGuardian.disableScreenWakeLock();
console.log('Screen can turn off normally');
Android
On Android, screen wake locks use FLAG_KEEP_SCREEN_ON:
// From BackgroundGuardianModule.kt:162-179
override fun enableScreenWakeLock(promise: Promise) {
val activity = currentActivity
if (activity == null) {
Log.w(TAG, "No current activity to enable screen wake lock")
promise.resolve(false)
return
}
UiThreadUtil.runOnUiThread {
try {
activity.window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
Log.d(TAG, "Screen wake lock enabled")
promise.resolve(true)
} catch (e: Exception) {
Log.e(TAG, "Failed to enable screen wake lock", e)
promise.resolve(false)
}
}
}
This flag only affects the current Activity and is automatically cleared when the Activity is destroyed.
iOS
On iOS, screen wake locks disable the idle timer:
// Conceptual iOS implementation
UIApplication.shared.isIdleTimerDisabled = true
Screen wake locks only work while the app is in the foreground. They do not replace partial wake locks for background CPU execution. Use acquireWakeLock() for background tasks.
Complete usage examples
Background audio player
import BackgroundGuardian from 'react-native-background-guardian';
import { Audio } from 'expo-av';
class AudioPlayer {
private sound: Audio.Sound | null = null;
private isPlaying = false;
async play(uri: string) {
// Acquire wake lock before starting playback
const acquired = await BackgroundGuardian.acquireWakeLock(
'AudioPlayback',
2 * 60 * 60 * 1000 // 2 hours
);
if (!acquired) {
console.error('Failed to acquire wake lock');
return;
}
try {
const { sound } = await Audio.Sound.createAsync({ uri });
this.sound = sound;
await sound.playAsync();
this.isPlaying = true;
} catch (error) {
console.error('Playback error:', error);
// Release wake lock on error
await BackgroundGuardian.releaseWakeLock();
}
}
async stop() {
if (this.sound) {
await this.sound.stopAsync();
await this.sound.unloadAsync();
this.sound = null;
}
this.isPlaying = false;
// Release wake lock when playback stops
await BackgroundGuardian.releaseWakeLock();
}
async pause() {
if (this.sound) {
await this.sound.pauseAsync();
this.isPlaying = false;
}
// Release wake lock when paused
await BackgroundGuardian.releaseWakeLock();
}
async resume() {
if (this.sound) {
// Reacquire wake lock before resuming
await BackgroundGuardian.acquireWakeLock('AudioPlayback', 2 * 60 * 60 * 1000);
await this.sound.playAsync();
this.isPlaying = true;
}
}
}
Video player with screen wake lock
import { useEffect, useState } from 'react';
import { View } from 'react-native';
import Video from 'react-native-video';
import BackgroundGuardian from 'react-native-background-guardian';
export function VideoPlayer({ source }) {
const [isPlaying, setIsPlaying] = useState(false);
useEffect(() => {
// Enable screen wake lock when component mounts
BackgroundGuardian.enableScreenWakeLock();
// Disable when component unmounts
return () => {
BackgroundGuardian.disableScreenWakeLock();
};
}, []);
return (
<View style={{ flex: 1 }}>
<Video
source={source}
style={{ width: '100%', height: '100%' }}
paused={!isPlaying}
onLoad={() => setIsPlaying(true)}
onEnd={() => setIsPlaying(false)}
/>
</View>
);
}
Location tracking with wake lock
import BackgroundGuardian from 'react-native-background-guardian';
import * as Location from 'expo-location';
class LocationTracker {
private subscription: Location.LocationSubscription | null = null;
private isTracking = false;
async startTracking(onLocationUpdate: (location: Location.LocationObject) => void) {
// First, ensure battery optimization exemption
const isIgnoring = await BackgroundGuardian.isIgnoringBatteryOptimizations();
if (!isIgnoring) {
console.warn('Battery optimization not disabled - tracking may be unreliable');
}
// Acquire wake lock for continuous tracking
const acquired = await BackgroundGuardian.acquireWakeLock(
'LocationTracking',
4 * 60 * 60 * 1000 // 4 hours
);
if (!acquired) {
throw new Error('Failed to acquire wake lock');
}
try {
// Request location permissions
const { status } = await Location.requestForegroundPermissionsAsync();
if (status !== 'granted') {
throw new Error('Location permission denied');
}
// Start location updates
this.subscription = await Location.watchPositionAsync(
{
accuracy: Location.Accuracy.High,
timeInterval: 10000, // 10 seconds
distanceInterval: 10, // 10 meters
},
onLocationUpdate
);
this.isTracking = true;
console.log('Location tracking started');
} catch (error) {
console.error('Failed to start tracking:', error);
// Release wake lock on error
await BackgroundGuardian.releaseWakeLock();
throw error;
}
}
async stopTracking() {
if (this.subscription) {
this.subscription.remove();
this.subscription = null;
}
this.isTracking = false;
// Release wake lock
await BackgroundGuardian.releaseWakeLock();
console.log('Location tracking stopped');
}
}
Kiosk mode (always-on display)
import { useEffect } from 'react';
import { View, Text } from 'react-native';
import BackgroundGuardian from 'react-native-background-guardian';
export function KioskScreen() {
useEffect(() => {
async function enterKioskMode() {
// Enable screen wake lock
await BackgroundGuardian.enableScreenWakeLock();
// Also acquire partial wake lock to keep CPU active
await BackgroundGuardian.acquireWakeLock('KioskMode');
console.log('Entered kiosk mode');
}
async function exitKioskMode() {
// Disable screen wake lock
await BackgroundGuardian.disableScreenWakeLock();
// Release partial wake lock
await BackgroundGuardian.releaseWakeLock();
console.log('Exited kiosk mode');
}
enterKioskMode();
return () => {
exitKioskMode();
};
}, []);
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Text style={{ fontSize: 48 }}>Kiosk Display</Text>
<Text>Screen will stay on indefinitely</Text>
</View>
);
}
Best practices
1. Always use timeouts
Never acquire a wake lock without a timeout:
// ❌ Bad: No timeout (uses 24-hour default)
await BackgroundGuardian.acquireWakeLock('Task');
// ✅ Good: Appropriate timeout for task duration
await BackgroundGuardian.acquireWakeLock('Task', 10 * 60 * 1000); // 10 minutes
2. Release in finally blocks
Always release wake locks, even if errors occur:
try {
await BackgroundGuardian.acquireWakeLock('Upload', 30 * 60 * 1000);
await uploadLargeFile();
} catch (error) {
console.error('Upload failed:', error);
} finally {
// Always release, even on error
await BackgroundGuardian.releaseWakeLock();
}
Use meaningful tags for easier debugging:
// ❌ Bad: Generic tags
await BackgroundGuardian.acquireWakeLock('task');
// ✅ Good: Descriptive tags
await BackgroundGuardian.acquireWakeLock('MusicPlayback:Track123');
await BackgroundGuardian.acquireWakeLock('FileSync:Photos');
await BackgroundGuardian.acquireWakeLock('LocationTracking:WorkoutSession');
4. Check wake lock status
Verify wake lock status before critical operations:
async function performCriticalTask() {
const isHeld = await BackgroundGuardian.isWakeLockHeld();
if (!isHeld) {
console.warn('Wake lock not held - task may be interrupted');
await BackgroundGuardian.acquireWakeLock('CriticalTask', 15 * 60 * 1000);
}
await doWork();
}
5. Combine with battery optimization exemptions
For reliable background execution, combine wake locks with battery optimization exemptions:
async function setupReliableBackgroundTask() {
// Step 1: Check battery optimization status
const isIgnoring = await BackgroundGuardian.isIgnoringBatteryOptimizations();
if (!isIgnoring) {
Alert.alert(
'Setup Required',
'Please disable battery optimization for reliable background operation.',
[{ text: 'OK', onPress: () => BackgroundGuardian.openBatteryOptimizationSettings() }]
);
return;
}
// Step 2: Acquire wake lock
const acquired = await BackgroundGuardian.acquireWakeLock('BackgroundTask', 60 * 60 * 1000);
if (acquired) {
// Step 3: Start background work
await startBackgroundWork();
}
}
6. Monitor battery impact
Track wake lock usage and inform users about battery impact:
import { NativeModules } from 'react-native';
class WakeLockMonitor {
private acquisitionTime: number | null = null;
async acquire(tag: string, timeout: number) {
this.acquisitionTime = Date.now();
const acquired = await BackgroundGuardian.acquireWakeLock(tag, timeout);
return acquired;
}
async release() {
const released = await BackgroundGuardian.releaseWakeLock();
if (released && this.acquisitionTime) {
const duration = Date.now() - this.acquisitionTime;
console.log(`Wake lock held for ${duration}ms`);
// Track for analytics
trackWakeLockUsage(duration);
this.acquisitionTime = null;
}
return released;
}
}
Debugging wake locks
You can verify wake lock behavior using ADB:
Check active wake locks
# View all wake locks on the device
adb shell dumpsys power | grep "Wake Locks"
# Search for your app's wake locks
adb shell dumpsys power | grep "BackgroundGuardian"
Monitor wake lock acquisition
# Watch logs for wake lock events
adb logcat | grep "BackgroundGuardian"
# Output will show:
# BackgroundGuardian: Wake lock acquired with tag: BackgroundGuardian:WakeLock:YourTag, timeout: 600000ms
# BackgroundGuardian: Wake lock released successfully
Check battery stats
# View battery usage statistics for your app
adb shell dumpsys batterystats --charged your.package.name
# This shows how long wake locks were held
Limitations
1. Doze mode interference
Wake locks may be ignored during Doze mode unless you’re exempt from battery optimizations:
const isIgnoring = await BackgroundGuardian.isIgnoringBatteryOptimizations();
if (!isIgnoring) {
console.warn('Wake locks may be ignored during Doze mode');
}
See the Doze and App Standby page for details.
2. Power Save mode restrictions
Wake locks are less effective when Power Save (Battery Saver) mode is active:
const isPowerSave = await BackgroundGuardian.isPowerSaveMode();
if (isPowerSave) {
Alert.alert(
'Battery Saver Active',
'Background tasks may be limited. Disable Battery Saver for best results.'
);
}
3. OEM-specific killing
Wake locks don’t prevent OEM-specific app killers from terminating your app. See the OEM Restrictions page.
4. Foreground services recommended
For long-running tasks, combine wake locks with a foreground service to prevent the system from considering your app idle:
// Use another library for foreground services
import BackgroundService from 'react-native-background-actions';
async function startLongRunningTask() {
// Start foreground service
await BackgroundService.start(taskHandler, options);
// Also acquire wake lock
await BackgroundGuardian.acquireWakeLock('LongTask', 4 * 60 * 60 * 1000);
}