Upload
kudo1048
View
7.795
Download
2
Embed Size (px)
Citation preview
Developers.IO 2016
F-4
工藤 星命 モバイルアプリサービス部
Ⓒ Classmethod, Inc.
2016年02月20日
全部見せます! 最前線エンジニアが語る BLEアプリケーション実装のハマりどころ(Android)
1
#cmdevio2016
目次
BLE概要
2Ⓒ Classmethod, Inc.
実装サンプル
ハマりどころ
まとめ
BLE概要
3
キーワード類• 省電力 • GAPとGATT
• すべてのBLEデバイスに定義されている仕様 • サービス
• 複数のデータ構造をグループ化したもの • 複数のキャラクタリスティックをもつ(持たないのもある)
• キャラクタリスティック • 複数のデータ構造をグループ化したもの • 複数のディスクリプタをもつ(持たないのもある)
• ディスクリプタ • キャラクタリスティックの追加情報
• セントラル • いわゆるクライアント、主にスマホなど通信を開始したり制御する側
• ペリフェラル • いわゆるサーバー、セントラルからの操作を受け付ける
4Ⓒ Classmethod, Inc.
実装サンプル
5
パーミッションの宣言マニフェストに定義
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name=“android.permission.BLUETOOTH_ADMIN"/>
android6.0からは、以下のパーミッションが必要になるので
パーミッションモデルに従って、ユーザーに許可をもらう必要がでてきます。
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
もしくは
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
6Ⓒ Classmethod, Inc.
Bluetoothを操作する前にBLEが搭載されているかチェック public static boolean checkBLESupport(Context context) { return context.getPackageManager().hasSystemFeature( FEATURE_BLUETOOTH_LE);}
7Ⓒ Classmethod, Inc.
BLEがサポートされていたらアダプターを取得 BluetoothManager bluetoothManager =(BluetoothManager)getSystemService(Context.BLUETOOTH_SERVICE) mBluetoothAdapter = bluetoothManager.getAdapter
8Ⓒ Classmethod, Inc.
スキャンの前にBluetoothが有効になっている事を確認 if (mBluetoothAdapter != null && mBluetoothAdapter.isEnabled()) { // スキャン開始 } else { // OFFだった場合、ONにする事を促すダイアログを表示 Intent btIntent = new Intent( BluetoothAdapter.ACTION_REQUEST_ENABLE); startActivity(btIntent) }
9Ⓒ Classmethod, Inc.
ここまで• パーミッションの宣言
• BLEをサポートしているか
• BluetoothはONになっているか
10Ⓒ Classmethod, Inc.
スキャン開始メソッドandroid4.4以下(API Level 20以下)ではmBluetoothAdapter.startLeScan(mLeScanCallback);
android5.0以上(API Level 21以上)ではBluetoothAdapter#startLeScanはdeprecatedの為、以下を使用mBluetoothScanner = mBluetoothAdapter.getBluetoothLeScanner;mBluetoothScanner.startScan(mScanCallback);
11Ⓒ Classmethod, Inc.
スキャン実行private void startLeScan(){ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { mBluetoothAdapter.startLeScan(mLeScanCallback); } else { startScan(); }}@TargetApi(Build.VERSION_CODES.LOLLIPOP)private void startScan(){ mBluetoothScanner = mBluetoothAdapter.getBluetoothLeScanner; mBluetoothScanner.startScan(mScanCallback);}
12Ⓒ Classmethod, Inc.
スキャンコールバックBluetoothAdapter#startLeScan(LeScanCallback callback)private BluetoothAdapter.LeScanCallback mLeScanCallback = new BluetoothAdapter.LeScanCallback() { @Override public void onLeScan (final BluetoothDevice device, int rssi, byte[] scanRecord) { // スキャン結果を処理 mLeDeviceListAdapter.addDevice(device); mLeDeviceListAdapter.notifyDataSetChanged(); } } };
13Ⓒ Classmethod, Inc.
スキャンコールバックBluetoothScanner#startScan(ScanCallback callback)private ScanCallback mScanCallback = new ScanCallback() { @Override public void onScanResult(int callbackType, ScanResult result) { super.onScanResult(callbackType, result); // スキャン結果を処理 BluetoothDevice device = result.getDevice // ScanResultからrssiやScanRecordも取得できる } }; … }
14Ⓒ Classmethod, Inc.
スキャンコールバックBluetoothScanner#startScan(ScanCallback callback)private ScanCallback mScanCallback = new ScanCallback() { … @Override public void onScanFailed(int errorCode) { super.onScanFailed(errorCode); // エラーコードと内容はScanCallbackに定義されてる // SCAN_FAILED_ALREADY_STARTEDなど } }; … }
15Ⓒ Classmethod, Inc.
スキャン停止private void stopScan(){ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { mBluetoothAdapter.stopLeScan(mLeScanCallback); } else { mBluetoothLeScanner.stopScan(mScanCallback); }}// スキャンはバッテリー消費が多いのでtimeoutを設けるmHandler.postDelayed(new Runnable() { @Override public void run() { mScanning = false; stopLeScan(); } }, SCAN_PERIOD);
16Ⓒ Classmethod, Inc.
コネクションBluetoothDevice device = mBluetoothAdapter.getRemoteDevice(mAddress);mBluetoothGatt = device.connectGatt(mContext, false, mGattCallback);第二引数は自動接続のON/OFF。trueにした場合、メソッド実行時にデバイスが範囲外でも範囲内に入った時に接続される。が、切断後の再接続は確実ではないので、この自動接続を前提とした設計はおすすめできません。切断のコールバックを受け取った時に再度、connectGattを呼び出すのが無難です。が、切断時のエラーの種類や、再接続のタイミングによっては接続できなかったりする事も多い印象です。
17Ⓒ Classmethod, Inc.
ハマりどころ
18
BluetoothGattCallbackprivate final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() { @Override public void onConnectionStateChange(BluetoothGatt gatt, int status,int newState) { if(status == BluetoothGatt.GATT_SUCCESS) { if (newState == BluetoothProfile.STATE_CONNECTED) { // 渡されるインスタンスが期待するものとは限らない // gattを更新して必要な情報を判定 mGatt = gatt; … // 再接続後はキャッシュされたデータが読み込まれる? // キャッシュのクリアはアダプターのON/OFFか、 // BluetoothGatt#refreshをリフレクションで呼ぶ boolean bool = mGatt.discoverServices(); } }
19Ⓒ Classmethod, Inc.
BluetoothGattCallbackprivate final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() { @Override public void onConnectionStateChange(BluetoothGatt gatt, int status,int newState) { if(status == BluetoothGatt.GATT_SUCCESS) { if (newState == BluetoothProfile.STATE_CONNECTED) { … boolean bool = mGatt.discoverServices(); // boolがfalseだとRead /Writeが失敗する // onServicesDiscoveredで判定が必要 } }
20Ⓒ Classmethod, Inc.
BluetoothGattCallbackprivate final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() { @Override public void onConnectionStateChange(BluetoothGatt gatt, int status,int newState) { if(status == BluetoothGatt.GATT_SUCCESS) { if (newState == BluetoothProfile.STATE_CONNECTED) { … } else { // 切断(STATE_DISCONNECTED)時の処理 // 明示的にdisConnectしなかった時はフラグなどで // 再接続させるか、disConnectを呼ぶ // disConnect時にキャッシュをクリアするのが良い? // BluetoothGatt#refreshは失敗する事もある(falseを返す) } } }
21Ⓒ Classmethod, Inc.
BluetoothGattCallback … if(status == BluetoothGatt.GATT_SUCCESS) { if (newState == BluetoothProfile.STATE_CONNECTED) { … } else { // 切断(STATE_DISCONNECTED)時の処理 } …
STATE_DISCONNECTEDはdisConnect以外に物理的に信号が遮断された場合などに呼ばれるので、自分で切断したかどうかの判断が必要。(いくつかの実機で試したところ、どれも約10秒程の遮断で呼ばれた)データ通信以外に、コネクション用の通信が行われており、組み込み側のシステム(BTE)で制御されているので、物理遮断の即時検知はデータ通信をタイマーやハンドラで対応しました。(iOSはすぐに検知した)
22Ⓒ Classmethod, Inc.
BluetoothGattCallback … @Override public void onConnectionStateChange(BluetoothGatt gatt, int status,int newState) { if(status == BluetoothGatt.GATT_SUCCESS) { if (newState == BluetoothProfile.STATE_CONNECTED) { … } } else if(status == 129) { 129,133の謎の汎用エラーがある } }
23Ⓒ Classmethod, Inc.
ペアリング切れまくりんぐ• 129,133のstatus時は接続に失敗
• 再接続時に組み込み側から非暗号化通信のエラーが呼ばれる
• 呼ばれない時もある
• なぜか接続履歴が消える(設定からペアリング情報を削除した時と同じ)
• 消えない時もある
• ペアリング用の暗号鍵も消えるので再度ペアリングが必要になる
• 5.xでは129,133自体が発生しない?
• この時に紐付け状態を確認(getBondState())してもBOND_BONDED
24Ⓒ Classmethod, Inc.
ペアリング切れまくりんぐ• 紐付け状態が変更された時はブロードキャストされる
• されないこともある
• キャッシュは関係ないぽい(一回目の接続で発生する事もある)
• デバイス側のモジュールで変わる?
25Ⓒ Classmethod, Inc.
まとめ• 4.xはつらい • いかにつらいかを人に伝えるスキルが求められる • 5.xは4.xより安定するが、callbackベースはやはりつらい
が、
• ビーコンやIOTで利用分野は広がっており、これからも多くの事業化や製品化がされる事にともない、BLEを避けていくのは難しくなっていくでしょう。
26Ⓒ Classmethod, Inc.
Developers.IO 2016
ご静聴ありがとうございました。 スライドは後日ブログで公開します。
28
A-1
Ⓒ Classmethod, Inc.
#cmdevio2016