How does this beacon scanning work? And in particular, how do you scan for iBeacons on Android? Can we continuously do it without draining the battery? After In The Pocket open sourced their iBeacon scanning library, I want to share the technical details, on how you scan without a library.
Let’s describe what happens when you scan for beacons. You have the emitting device, that broadcasts a byte array via BLE advertising mode. It's emitted multiple times a second! Other Bluetooth devices (like your phone) can listen to these advertisements, and respond to it. To respond for, of course, you need to listen first.
Listen to advertisements
How do you listen? By starting a new scan via BluetoothLeScanner.startScan(List<ScanFilter> filters, ScanSettings settings, ScanCallback callback).
You can only start this scan when all requirements are met:
- BLE hardware
- Bluetooth on
- Location on (Android 6+)
- Location runtime permission granted (Android 6+)
Once a condition is no longer met, the scan will stop.
There are 3 parameters. With the ScanFilter you can specify what you are listening for. The ScanSettings, as their name tells, allows you to set some settings like scan frequency. The ScanCallback notifies you whenever you are in the range of something you are scanning for.
ScanFilters
You can pass a list of ScanFilters. They define what beacon advertisements you want to be notified of, when in range. You can create a ScanFilter with a ScanFilter.Builder. You can filter on many parameters. If you want to filter based on the iBeacon spec, it's best to use the ScanFilter.Builder setManufacturerData (int manufacturerId, byte[] manufacturerData, byte[] manufacturerDataMask). You can filter with it on uuid, major and minor.
As you see you need to set an int and 2 byte arrays. The manufacturerId is not very important. But the manufacturerData is! You can create a byte array that matches the byte array that is advertised by the beacon! And to make it even more cool, you can use the manufacturerDataMask to tell what bytes should match, and what bytes shouldn't. This way you can scan for all beacons with a fixed uuid, but any major or minor! Any combination is possible really.
This utils class showcases how to create such byte arrays.
ScanSettings
The ScanSettings enable you to tell how frequent you want to scan for beacons. The faster you scan, the more battery you will use!
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private static ScanSettings getScanSettings()
{
final ScanSettings.Builder builder = new ScanSettings.Builder();
builder.setReportDelay(0);
builder.setScanMode(ScanSettings.SCAN_MODE_LOW_POWER);
return builder.build();
}
ScanCallback
Once you are in the range of a matching beacon, the callback will be called. You have to convert to ScanResult’s bytes back to your iBeacon. You can use this code for reference.
I'm hoping this became less of a black box for they who want to start iBeacon scanning on Android!