Two jobs: advertise and scan
Bluetooth LE discovery always has two sides. One device advertises: it repeatedly broadcasts small packets that say “I am here” and carry a little data. The other device scans: it listens for those packets and reports what it hears to the app.
These are the two BLE roles. The peripheral advertises and accepts connections; the central scans and starts them. Android’s BLE overview points out the consequence for phones: two devices that only support the central role cannot talk to each other, and neither can two that only support the peripheral role. A phone talking to a fitness tracker only needs to scan, because the tracker advertises. For two phones to meet, at least one of them has to advertise.
The identifier both apps agree on
A phone hears a lot of advertisements: headphones, watches, trackers and other phones. To pick out the right one, both copies of the app are built with the same service UUID, a 128-bit identifier that the peripheral side puts in its advertisement.
Advertisements are small, so the identifier has to fit. Android’s BluetoothLeAdvertiser reference says an advertiser can broadcast up to 31 bytes of advertisement data. On iOS, Apple’s startAdvertising(_:) reference allows only two keys, the local name and the service UUIDs, and gives a foreground app up to 28 bytes in the initial advertisement, plus 10 bytes in the scan response for the local name only, not counting 2 bytes of header per data type.
The scanning side then filters on that UUID:
- iOS.
scanForPeripherals(withServices:options:)takes a list of service UUIDs and reports only peripherals that advertise them. Apple calls this the recommended practice over passingnil, which reports every peripheral it finds. - Android.
startScan()accepts a list ofScanFilterobjects that restrict what the scan looks for, along withScanSettingsthat set its parameters.
From discovery to connection
A matching advertisement is only the start. The scanning phone then:
- Connects to the advertising phone.
- Discovers its services and characteristics through GATT.
- Writes to a characteristic to send, and subscribes to notifications to receive.
Scanning costs power, and both platforms say so. Android’s guide says to stop scanning as soon as you find the device you want, never to scan on a loop, and always to set a time limit on a scan. Apple builds the same thinking into iOS: in the background, repeated advertisements from one peripheral are merged into a single discovery and scans run less often, changes Apple says help minimise radio use and improve battery life.
When one phone is in the background
Discovery is most reliable when both apps are open. Off screen, each platform changes the rules.
iOS. An app needs the bluetooth-central and bluetooth-peripheral background modes to scan and advertise in the background at all. Even then, a background scan must name the service UUIDs it wants. A backgrounded advertiser drops its local name and places all its service UUIDs in an “overflow” area that, in Apple’s words, can be discovered only by an iOS device explicitly scanning for them. Can a BLE app run in the background on iOS? goes through these limits.
Android. The BluetoothLeScanner reference says unfiltered scans stop when the screen turns off and resume when it turns on, and that filtered scans avoid this. Scanning and advertising also need the right runtime permissions, which Bluetooth permissions on Android, explained covers.
For a mixed pair, the iOS overflow area matters most: an Android phone scanning for your UUID may not see an iPhone whose app is in the background. How do iPhone and Android phones message each other over Bluetooth? looks at that case.
Both phones, both roles
If either person might open the app first, each phone has to advertise and scan at the same time. That brings one more design choice: when both phones discover each other, the app needs a rule for which side opens the connection, so the pair does not connect twice.
It also rules out libraries that implement only the central role; Can react-native-ble-plx connect two phones directly? explains the gap. Offline Protocol’s React Native mesh SDK, for example, is a native module that advertises and scans over BLE on each phone, which is also why it needs a native build and does not run in Expo Go.