昨年、私はiOS用のBluetooth Low Energy(BLE)アプリケーションを開発してきましたが、それは非常に簡単であることがわかりました。それからそれらをAndroidに移植することがありました...それはどれほど難しいでしょうか?
確かに言えますが、想像以上に難しかったので、Androidでの安定した運用に力を入れなければなりませんでした。私はパブリックドメインで多くの記事を研究しましたが、いくつかは誤りであることが判明し、多くは非常に有用で問題に役立ちました。このシリーズの記事では、私のように検索に多くの時間を無駄にしないように、私の調査結果について説明したいと思います。
BLE forAndroidの機能
BLEに関するGoogleのドキュメントは非常に一般的であり、重要な情報が欠落しているか古くなっている場合があります。サンプルアプリケーションではBLEの正しい使用方法が示されていません。BLEを正しく行う方法についての情報源はほんのわずかしか見つかりませんでした。 スチュアートケントのプレゼンテーション は、優れた出発材料を提供します。いくつかの高度なトピックについては、北欧の優れた記事があり ます。
Android BLE APIは低レベルの操作であり、実際のアプリケーションでは、いくつかの抽象化レイヤーを使用する必要があります(たとえば、iOS-CoreBluetoothですぐに実行できるように)。通常は自分で行う必要があります:コマンドキュー、ボンディング、接続のメンテナンス、エラーとバグの処理、マルチスレッドアクセス。最も有名なライブラリは、 SweetBlue、 RxAndroidBle 、 Nordicです。私の意見では、学ぶのが最も簡単なのは北欧 です。詳細はこちらをご覧ください。
メーカーはAndroidBLEスタックに変更を加える か、完全に独自の実装に置き換えます。また、アプリケーション内のさまざまなデバイスの動作の違いを考慮する必要があります。1つの電話でうまく機能するものは、他の電話では機能しない場合があります。一般に、すべてがそれほど悪いわけではありません。たとえば、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 . .
!