L'année dernière, j'ai développé des applications Bluetooth Low Energy ( BLE ) pour iOS et cela s'est avéré assez simple. L'étape suivante consistait à les porter sur Android ... à quel point cela pouvait-il être difficile?
Je peux dire avec certitude - c'était plus difficile que je ne l'imaginais, j'ai dû faire beaucoup d'efforts pour un travail stable sous Android. J'ai étudié de nombreux articles en libre accès, certains se sont avérés faux, beaucoup étaient très utiles et aidés en la matière. Dans cette série d'articles, je souhaite décrire mes découvertes afin que vous ne perdiez pas beaucoup de temps à chercher comme je l'ai fait.
Caractéristiques de BLE pour Android
La documentation Google sur BLE est très générale , dans certains cas des informations importantes sont manquantes ou obsolètes, les exemples d'applications ne montrent pas comment utiliser BLE correctement. Je n'ai trouvé que quelques sources sur la façon de faire correctement BLE. La présentation de Stuart Kent fournit une excellente matière de départ. Pour certains sujets avancés, il existe un bon article nordique .
L'API Android BLE est une opération de bas niveau ; dans les applications réelles, vous devez utiliser plusieurs couches d'abstraction (comme, par exemple, une opération prête à l'emploi dans iOS-CoreBluetooth). Habituellement, vous devez le faire vous-même: file d'attente de commandes, liaison, maintenance de la connexion, gestion des erreurs et des bogues, accès multithread. Les bibliothèques les plus connues sont SweetBlue , RxAndroidBle et Nordic . À mon avis, le plus facile à apprendre est le nordique, voir les détails ici .
Android BLE . . , ! , Samsung Google!
Android ( ) , 4,5 6. , , 133. .
, , «» . .
. BluetoothLeScanner
:
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
BluetoothLeScanner scanner = adapter.getBluetoothLeScanner();
if (scanner != null) {
scanner.startScan(filters, scanSettings, scanCallback);
Log.d(TAG, "scan started");
} else {
Log.e(TAG, "could not get scanner object");
}
filters
scanSettings
, scanCallback
:
private final ScanCallback scanCallback = new ScanCallback() {
@Override
public void onScanResult(int callbackType, ScanResult result) {
BluetoothDevice device = result.getDevice();
// ...do whatever you want with this found device
}
@Override
public void onBatchScanResults(List<ScanResult> results) {
// Ignore for now
}
@Override
public void onScanFailed(int errorCode) {
// Ignore for now
}
};
ScanResult
, BluetoothDevice
, . , , ScanResult
:
Activity
, onScanResult
, Activity
, onScanResult
.
null , , UUID .
UUID
, UUID: 1810. Advertisement data UUID , . , , Advertisement data , .
. : , UUID Advertisement data, .
:
UUID BLP_SERVICE_UUID = UUID.fromString("00001810-0000-1000-8000-00805f9b34fb");
UUID[] serviceUUIDs = new UUID[]{BLP_SERVICE_UUID};
List<ScanFilter> filters = null;
if(serviceUUIDs != null) {
filters = new ArrayList<>();
for (UUID serviceUUID : serviceUUIDs) {
ScanFilter filter = new ScanFilter.Builder()
.setServiceUuid(new ParcelUuid(serviceUUID))
.build();
filters.add(filter);
}
}
scanner.startScan(filters, scanSettings, scanCallback);
UUID ( 1810
), 16-bit UUID
128-bit UUID
( 00001810-000000-1000-8000-000-00805f9b34fb
). UUID BASE_PART UUID, . .
, :
, , Polar H7 «Polar H7 391BBB014», - «Polar H7» , «391BBB014» - . . «Polar H7», ,
ScanResult
. :
String[] names = new String[]{"Polar H7 391BB014"};
List<ScanFilter> filters = null;
if(names != null) {
filters = new ArrayList<>();
for (String name : names) {
ScanFilter filter = new ScanFilter.Builder()
.setDeviceName(name)
.build();
filters.add(filter);
}
}
scanner.startScan(filters, scanSettings, scanCallback);
MAC-.
. MAC- , , , . , , Bluetooth.
String[] peripheralAddresses = new String[]{"01:0A:5C:7D:D0:1A"};
// Build filters list
List<ScanFilter> filters = null;
if (peripheralAddresses != null) {
filters = new ArrayList<>();
for (String address : peripheralAddresses) {
ScanFilter filter = new ScanFilter.Builder()
.setDeviceAddress(address)
.build();
filters.add(filter);
}
}
scanner.startScan(filters, scanSettings, scanByServiceUUIDCallback);
, UUID, MAC- . , . .
ScanSettings
ScanSettings
Android . , , :
ScanSettings scanSettings = new ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_LOW_POWER)
.setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
.setMatchMode(ScanSettings.MATCH_MODE_AGGRESSIVE)
.setNumOfMatches(ScanSettings.MATCH_NUM_ONE_ADVERTISEMENT)
.setReportDelay(0L)
.build();
ScanMode
, . Bluetooth . , . 4 , Nordics :
SCAN_MODE_LOW_POWER
. Android 0.5, 4.5. , advertisement .
SCAN_MODE_BALANCED
. : 2, : 3, «» .
SCAN_MODE_LOW_LATENCY
. , Android , , . . .
SCAN_MODE_OPPORTUNISTIC
. , ! , , . Android , (. « »).
Callback Type
callback ScanResult
, 3 :
CALLBACK_TYPE_ALL_MATCHES
. Callback , advertisement . - 200-500 allback, advertisement .
CALLBACK_TYPE_FIRST_MATCH
. Callback , advertisement .
CALLBACK_TYPE_MATCH_LOST
. Callback , advertisement advertisement . .
CALLBACK_TYPE_ALL_MATCHES
CALLBACK_TYPE_FIRST_MATCH
. . - CALLBACK_TYPE_ALL_MATCHES
, callback, - CALLBACK_TYPE_FIRST_MATCH
.
Match mode
, Android «».
MATCH_MODE_AGGRESSIVE
. advertisement .
MATCH_MODE_STICKY
. , advertisement .
, MATCH_MODE_AGGRESSIVE
, .
Number of matches
advertisement .
MATCH_NUM_ONE_ADVERTISEMENT
. .
MATCH_NUM_FEW_ADVERTISEMENT
. .
MATCH_NUM_MAX_ADVERTISEMENT
. advertisement , .
. - , 2 .
Report delay
allback . , Android onBatchScanResults
. onScanResult
. , . - , MAC- ( ).
: Samsung S6 / Samsung S6 Edge, RSSI ( ) .
Android Bluetooth
BLE «» Bluetooth . : , MAC-, (, ), (Classic, Dual, BLE) .. Android , . , . . , Android , . - MAC- !
Bluetooth , , , 3 , :
Bluetooth
( )
, , - . , Samsung, Bluetooth.
, BT . , :
// Get device object for a mac address
BluetoothDevice device = bluetoothAdapter.getRemoteDevice(peripheralAddress)
// Check if the peripheral is cached or not
int deviceType = device.getType();
if(deviceType == BluetoothDevice.DEVICE_TYPE_UNKNOWN) {
// The peripheral is not cached
} else {
// The peripheral is cached
}
, , . .
?
– , , , . , BLE-, , (foreground), .
, Google () :
c Android 8.1 .
ScanFilters
, Android , , . Google. Google.
c Android 7 30 , Android
SCAN_MODE_OPPORTUNISTIC
. , , 30 . commit .
-
Google . ! Android , 10 , . :
(permissions)
, . (permissions):
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
, , . ACCESS_COARSE_LOCATION
Google «» .
private boolean hasPermissions() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (getApplicationContext().checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
requestPermissions(new String[] { Manifest.permission.ACCESS_COARSE_LOCATION }, ACCESS_COARSE_LOCATION_REQUEST);
return false;
}
}
return true;
}
. , BLE 2 : ACCESS_FINE_LOCATION
( API<23) ACCESS_BACKGROUND_LOCATION
Stackoverflow.
Android10:
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
, Bluetooth, - Intent
:
BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if (!bluetoothAdapter.isEnabled()) {
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
}
BLE Activity (Fragment / Service), , (permissions) Android-Bluetooth . .
!