Android background BLE mechanisms
Android lets a BLE app scan, connect and stay connected while it is not on screen, as long as the app process is alive. Android’s guide to background communication says there is no limitation on using the scanning or Companion Device Manager APIs while the app is not visible, but both need the process to be running. The work is choosing how the process stays alive, or how the system wakes it.
| Mechanism | Android versions | Find devices | Connect | Stay connected |
|---|---|---|---|---|
Filtered scan with a ScanCallback | All with BLE | Yes, while the process runs; unfiltered scans pause at screen off | Yes, while the process runs | Until the process is killed |
Scan with a PendingIntent | Android 8.0 (API level 26) and later | Yes, and the system wakes the app on a match | Briefly, from a Worker; on Android 11 and lower a foreground service can start directly | No, short work only |
Foreground service, type connectedDevice | Type attribute from Android 10; required for apps targeting Android 14 | Yes | Yes | Yes, while the service runs |
Companion Device Manager with CompanionDeviceService | Association from Android 8.0; presence service from Android 12; new presence request API in Android 16 | The system watches for the associated device | Yes, the app is bound when the device appears | Yes, while the device is in range or connected |
| WorkManager | Any | Not suggested by the guide | Yes, for a defined job | No, app restrictions apply |
Rows reflect Android’s developer documentation as of October 2026. Advertising follows the same rule as the rest: it needs a running process, and the background guide does not treat it separately.
Two older rules still matter. On Android 10 and 11, a service that discovers Bluetooth devices must also hold ACCESS_BACKGROUND_LOCATION. And from Android 12, apps can drop location for scanning altogether, which Bluetooth permissions on Android, explained covers with the rest of the permission set.
Foreground services for background Bluetooth
A foreground service is needed when a link has to stay open while the user is elsewhere, such as syncing data or listening to a peripheral’s notifications. Android’s background guide names a foreground service of type connectedDevice as one of the two ways to keep a connection alive, the other being the Companion Device Manager. Shorter jobs can use a PendingIntent scan or WorkManager instead.
To run one:
- Declare the type. Add
android:foregroundServiceType="connectedDevice"to the service and declareFOREGROUND_SERVICE_CONNECTED_DEVICE. Apps that target Android 14 must declare a type for every foreground service. - Hold a Bluetooth permission first. A
connectedDeviceservice has a runtime prerequisite, which a Bluetooth app meets with at least one granted permission amongBLUETOOTH_CONNECT,BLUETOOTH_ADVERTISEandBLUETOOTH_SCAN. - Start it while the app is visible. Apps that target Android 12 or higher cannot start a foreground service from the background, and the system throws
ForegroundServiceStartNotAllowedException. Exemptions include the user acting on a notification or widget, a few system broadcasts, the user turning off battery optimisation for the app, and apps using the Companion Device Manager with one of its background permissions. - Show the notification. The service carries a status bar notification so the user knows the work is running.
- Reconnect after a kill. A foreground service makes the process less likely to be killed, not immune. When a connection drops because the process died, reconnect using the steps Android gives for connecting to a device.
Android also says to keep connections only as long as needed and disconnect once the task is done, so a service that runs all day should have a reason to.
Background scanning without a filter
Android stops unfiltered scans when the screen turns off, to save power. The BluetoothLeScanner reference says scanning resumes when the screen turns back on, and that a scan with a ScanFilter avoids the pause. A background scan should always name what it is looking for, usually a service UUID or a manufacturer ID.
Android’s scanning guide adds two habits: stop scanning as soon as the device is found, and never scan on a loop without a time limit. For an app whose process is not always running, startScan() with a PendingIntent, available from Android 8.0 (API level 26), hands the scan to the system and delivers matching results to the app, which wakes to receive them. The background guide discourages scheduling periodic scans instead, because that starts the app whether or not a device is in range.
Companion Device Manager for wearables
The Companion Device Manager (CDM) is Android’s system service for associating an app with a specific accessory, such as a watch or a sensor, and then keeping that app awake while the accessory is near. Use it when the app serves one known device that the user pairs once, which is the usual shape of a wearable app.
How it works:
- Association. From Android 8.0, the app asks the CDM to find devices that match a filter. The system scans on the app’s behalf, shows the user a chooser, and the app does not need
ACCESS_FINE_LOCATIONfor that scan, though Location Services must be on. Association alone opens no connection and does not scan continuously. - Background rights. An associated app can declare
REQUEST_COMPANION_RUN_IN_BACKGROUND, which lets it run in the background and start a foreground service from there, or the narrowerREQUEST_COMPANION_START_FOREGROUND_SERVICES_FROM_BACKGROUND. - Presence. The app implements a
CompanionDeviceServiceand asks the CDM to observe the device’s presence. The system binds the service when the device comes into Bluetooth LE range or connects over Bluetooth, and unbinds it when the device leaves. Binding raises the app’s process priority. Android 16 replaced the address-basedstartObservingDevicePresence(String)with a version that takes anObservingDevicePresenceRequest.
The limits are in Android’s own guide: limited filtering, and no support for random MAC addresses. The CDM reference recommends that a device with a rotating address use a resolvable private address and be bonded to the phone so Android can resolve it. If your peripheral cannot do that, use a filtered scan and a foreground service instead.
Android GATT error 133
Status 133 is a generic failure from Android’s Bluetooth stack, so it says that a connection or operation failed but not why. 133 is 0x85, which the open-source Android Bluetooth stack defines as GATT_ERROR. It is not one of the status constants in the public BluetoothGatt reference, which lists codes such as GATT_INSUFFICIENT_AUTHENTICATION (5) and GATT_FAILURE (257).
Android does not document the causes of 133, so treat it as a prompt to rule out the connection rules Android does document:
- Close what you are finished with. Android’s GATT guide says to call
close()on theBluetoothGattobject when you are done with a connection. Close the old object before starting a new attempt to the same device. - Ask for the LE transport. For a device that supports both Bluetooth Classic and LE, pass
BluetoothDevice.TRANSPORT_LEtoconnectGatt(), which takes a preferred transport for dual-mode devices. - Do not overlap connection requests. Android’s background guide says versions below Android 10 allow one connection request at a time and queue the rest. Run one GATT operation and one connection attempt at a time per device.
- Check the peripheral is there. A direct connection, with
autoConnectfalse, fails if the device is not available. For a device that comes and goes,autoConnecttrue makes Android try to reconnect whenever the device is available again. - Get a fresh device handle. Take the
BluetoothDevicefrom a current scan result, the bonded device list orgetRemoteLeDevice().
Then retry with a delay rather than in a tight loop, and log the device model and Android version beside each failure, because behaviour differs between phones.
Intermittent wearable and sensor sync
A wearable or sensor that only sees the phone now and then needs sync designed so that missing the phone for hours loses nothing and reconnecting sends each record exactly once. Android’s background rules mean the phone will not always be listening, so the device has to hold its data and the protocol has to survive gaps.
- Store on the device while apart. The wearable or sensor writes each reading to its own flash with a sequence number that only goes up, so it never depends on the phone being there.
- Batch on reconnect. When the link comes back, send everything since the last acknowledged sequence number in as few transfers as possible. Why does batching save battery and bandwidth? explains the saving.
- Use sequence numbers to find gaps. The phone records the highest sequence number it has stored for each device and asks for anything missing, so a dropped connection mid-transfer resumes rather than restarts.
- Make writes idempotent. Key each record by device and sequence number, so a record sent twice after a retry is stored once. What is idempotency? covers the pattern.
- Acknowledge after storing. The phone acknowledges only after the record is durably written, and the device deletes only acknowledged records.
- Carry the device’s own time. Stamp readings with the device clock and the sequence number, and correct for drift on the phone. What is clock drift? explains why device clocks disagree.
- Let the system wake the app. On Android, use the Companion Device Manager’s presence service or a filtered
PendingIntentscan so the app wakes when the device appears, rather than scanning on a timer.
iOS vs Android background Bluetooth rules
Both platforms allow background Bluetooth LE work under conditions, but they set different conditions. Android cares whether the process is alive and how it was started. iOS cares whether the app declared a Bluetooth background mode, and limits what it can do once it has.
| Android | iOS | |
|---|---|---|
| How background work is allowed | A live process: foreground service, PendingIntent scan, or Companion Device Manager | The bluetooth-central or bluetooth-peripheral background mode |
| Background scanning | Filtered scans only stay on at screen off | Scans must list service UUIDs and run less often |
| Background advertising | Needs a running process | Service UUIDs move to an overflow area and the local name is dropped |
| After the app is killed | PendingIntent scans and the Companion Device Manager can wake it | State preservation and restoration relaunches it for Bluetooth events |
| User-visible sign | Foreground service notification | No notification required |
Can a BLE app run in the background on iOS? covers the iOS column in detail. Test every combination of foreground and background, on both platforms, on real phones.
Offline Protocol and Android background limits
Offline Protocol’s React Native SDK ships the Bluetooth LE bridges for iOS and Android, and it lives under these same rules, so it cannot keep a mesh running anywhere the operating system would not keep any other app running. On Android its manifest declares a foreground service of type connectedDevice, shown to the user as a “Mesh Active” notification with a Stop action. The SDK’s documentation states that Android can still kill the process, and that by default the SDK does not recreate the protocol on its own afterwards, because a protocol running with no app code to store incoming messages would acknowledge messages that are then lost. Apps reconcile mesh state when they come back to the foreground, or opt in to having the app’s own code restart it. A preview Kotlin library for native Android apps is also available.
For wearables, the design is the one in the sync section above. The wearable keeps its own Bluetooth LE link to its companion app, and the SDK runs on the phone, where it can carry that data to other phones nearby or to a backend once a path exists.