[Android例項] 細談Ble4.0 APP開發
阿新 • • 發佈:2019-01-04
BluetoothGattCallback
所有操作的回撥函式。
private BluetoothGattCallback gattCallback = new BluetoothGattCallback() {
@Override
public void onCharacteristicChanged(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic) {
super.onCharacteristicChanged(gatt, characteristic);
//收到裝置notify值 (裝置上報值)
}
@Override
public void onCharacteristicRead(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic, int status) {
super.onCharacteristicRead(gatt, characteristic, status);
//讀取到值
}
@Override
public void onCharacteristicWrite(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic, int status) {
super.onCharacteristicWrite(gatt, characteristic, status);
if (status == BluetoothGatt.GATT_SUCCESS) {
//write成功(傳送值成功)
}
}
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status,
int newState) {
super.onConnectionStateChange(gatt, status, newState);
if (status == BluetoothGatt.GATT_SUCCESS) {
if (newState == BluetoothGatt.STATE_CONNECTED) {
// 連線成功
} else if (newState == BluetoothGatt.STATE_DISCONNECTED) {
// 斷開連線
}
}
}
@Override
public void onDescriptorRead(BluetoothGatt gatt,
BluetoothGattDescriptor descriptor, int status) {
super.onDescriptorRead(gatt, descriptor, status);
}
@Override
public void onDescriptorWrite(BluetoothGatt gatt,
BluetoothGattDescriptor descriptor, int status) {
super.onDescriptorWrite(gatt, descriptor, status);
}
@Override
public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {
super.onReadRemoteRssi(gatt, rssi, status);
if (status == BluetoothGatt.GATT_SUCCESS) {
//獲取到RSSI, RSSI 正常情況下 是 一個 負值,如 -33 ; 這個值的絕對值越小,代表裝置離手機越近
//通過mBluetoothGatt.readRemoteRssi();來獲取
}
}
@Override
public void onReliableWriteCompleted(BluetoothGatt gatt, int status) {
super.onReliableWriteCompleted(gatt, status);
}
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
super.onServicesDiscovered(gatt, status);
if (status == BluetoothGatt.GATT_SUCCESS) {
//尋找到服務
}
}
};
---------------------------------------------------------------------------------------------------------------------------------------
當呼叫了連線函式mBluetoothGatt = bluetoothDevice.connectGatt(this.context, false, gattCallback);之後,
如果連線成功就會 走到 連線狀態回撥:
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status,
int newState) {
if (status == BluetoothGatt.GATT_SUCCESS) {
//首先判斷這個status 如果等於 BluetoothGatt.GATT_SUCCESS(value=0)代表這個回撥是正常的,
//如果不等於 0,那邊就代表沒有成功,也不需要進行其他操作了,
// 連線成功和斷開連線都會走到這裡
if (newState == BluetoothGatt.STATE_CONNECTED) {
// 連線成功
//連線成功之後,我們應該立刻去尋找服務(上面提到的BluetoothGattService),只有尋找到服務之後,才可以和裝置進行通訊
gatt.discoverServices();// 尋找服務
} else if (newState == BluetoothGatt.STATE_DISCONNECTED) {
// 斷開連線
}
}
}
當判斷到連線成功之後,會去尋找服務, 這個過程是非同步的,會耗點時間,當尋找到服務之後,會走到回撥:
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
super.onServicesDiscovered(gatt, status);
if (status == BluetoothGatt.GATT_SUCCESS) {
//尋找到服務
//尋找服務之後,我們就可以和裝置進行通訊,比如下發配置值,獲取裝置電量什麼的
readBatrery();//讀取電量操作
sendSetting(); //下發配置值
}
}
/***讀操作***/
void readBatrery(){
//如上面所說,想要和一個學生通訊,先知道他的班級(ServiceUUID)和學號(CharacUUID)
BluetoothGattService batteryService=mBluetoothGatt.getService(UUID.fromString("0000180f-0000-1000-8000-00805f9b34fb")); //此處的0000180f...是舉例,實際開發需要詢問硬體那邊
if(batteryService!=null){
BluetoothGattCharacteristic batteryCharacteristic=batteryService.getCharacteristic(UUID.fromString("00002a19-0000-1000-8000-00805f9b34fb"));//此處的00002a19...是舉例,實際開發需要詢問硬體那邊
if(batteryCharacteristic!=null){
mBluetoothGatt.readCharacteristic(batteryCharacteristic);//讀取電量, 這是讀取batteryCharacteristic值的方法,讀取其他的值也是如此,只是它們的ServiceUUID 和CharacUUID不一樣
}
}
}
如果讀取電量(或者讀取其他值)成功之後 ,會來到 回撥:
@Override
public void onCharacteristicRead(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic, int status) {
super.onCharacteristicRead(gatt, characteristic, status);
//讀取到值,根據UUID來判斷讀到的是什麼值
if (characteristic.getUuid().toString()
.equals("00002a19-0000-1000-8000-00805f9b34fb")) {// 獲取到電量
int battery = characteristic.getValue()[0];
}
}
/***寫操作***/
void sendSetting(){
BluetoothGattService sendService=mBluetoothGatt.getService(UUID.fromString("00001805-0000-1000-8000-00805f9b34fb"));//此處的00001805...是舉例,實際開發需要詢問硬體那邊
if(sendService!=null){
BluetoothGattCharacteristic sendCharacteristic=sendService.getCharacteristic(UUID.fromString("00002a08-0000-1000-8000-00805f9b34fb"));//此處的00002a08...是舉例,實際開發需要詢問硬體那邊
if(sendCharacteristic!=null){
sendCharacteristic.setValue(new byte[] { 0x01,0x20,0x03 });//隨便舉個數據
mBluetoothGatt.writeCharacteristic(sendCharacteristic);//寫命令到裝置,
}
}
}
如果下發配置成功之後,會來到回撥:
@Override
public void onCharacteristicWrite(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic, int status) {
super.onCharacteristicWrite(gatt, characteristic, status);
if (status == BluetoothGatt.GATT_SUCCESS) {
//write成功(傳送值成功),可以根據 characteristic.getValue()來判斷是哪個值傳送成功了,比如 連線上裝置之後你有一大串命令需要下發,你呼叫多次寫命令, 這樣你需要判斷是不是所有命令都成功了,因為android不太穩定,有必要來check命令是否成功,否則你會發現你明明呼叫 寫命令,但是裝置那邊不響應
}
}
講解完讀寫操作,還有一個重要操作 就是 Notify(通知)
notify 就是讓裝置 可以傳送通知給你,也可以說上報值給你(傳送命令給你)
首先你得開啟裝置的通知功能: //引數 enable 就是開啟還是關閉, characteristic就是你想讓具有通知功能的BluetoothGattCharacteristic
private boolean enableNotification(boolean enable,
BluetoothGattCharacteristic characteristic) {
if (mBluetoothGatt == null || characteristic == null)
return false;
if (!mBluetoothGatt.setCharacteristicNotification(characteristic,
enable))
return false;
BluetoothGattDescriptor clientConfig = characteristic
.getDescriptor(UUIDUtils.CCC);
if (clientConfig == null)
return false;
if (enable) {
clientConfig
.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
} else {
clientConfig
.setValue(BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE);
}
return mBluetoothGatt.writeDescriptor(clientConfig);
}
一旦裝置那邊notify 資料給你,你會在回撥裡收到:
@Override
public void onCharacteristicChanged(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic) {
super.onCharacteristicChanged(gatt, characteristic);
//收到裝置notify值 (裝置上報值),根據characteristic.getUUID()來判斷是誰傳送值給你,根據characteristic.getValue()來獲取這個值
}
=========================以上屬於BLE基本的操作,下面是進階========================================
1. 關於傳送命令, 比如剛連線上裝置,你會把很多配置都發送給裝置,此時可能會呼叫多次 write操作,這個時候你應該需要在 write操作之間 加點間隔 例如開啟執行緒,用 Thread.sleep(150), 因為只有收到onCharacteristicWrite回撥之後才代表你的值成功了,如果
不加間隔,你會發現可能只會成功一個值。目前測試下來,我才用的是間隔150毫秒比較好,不會太慢,成功率也會高, 可自己實踐慢慢除錯,這個數值還與裝置端update速率有關
在Write 操作的時候,還有個辦法可以增快下發速度,且不需要加sleep
就是設定characteristic .setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE); Wrtite characteristic without requiring a response by the remote device, 就是不需 要回復,這樣速度會快,測試下來連續傳送9個數據,裝置端那邊列印資料都收到且是正確的,但回撥值有點奇怪:
圖1 是我的傳送指令 ,連續無間隔傳送9個命令:
圖2,3是 onCharacteristicWrite 回撥列印資訊:
從圖片看出,有的值沒有收到 onCharacteristicWrite 回撥,如第一個值 { 1, 0x08, 3 } ,有的值回調了2次 如 { 1, 0x04, 3 },可能會覺得有的值沒有傳送成功吧?
以下是 裝置端收到值的列印資訊:(0x00FF是用做分隔符,可忽略)
可以發現,9個值均接收正常, 所以說 這種快速發值,還是有點可靠的。測試的不多,各位可斟酌使用
2.關於重連
提到ble,估計想的最多的就是重連了。重連當然得是自動重連。可能有人想到的的就是 斷開之後再來一次bluetoothDevice.connectGatt(this.context, false, gattCallback)行不行?
先看看官方的說法 :
官方說 mbluetoothGatt.connect()方法就是用來重連的, 只要你斷線之後 呼叫此方法就好了。
測試下來 確實可以,但不同手機體驗不一樣, 但大家知道 android 碎片化現象很嚴重,什麼 flyme,什麼MIUI,emotion之類的。所以需要做什麼適配。
只調用 mbluetoothGatt.connect()來重連,我歸類為2種 三星 手機 和 非三星手機。
三星手機現象:
重連非常迅速 ,斷開之後,只要一回到有效範圍內。立馬重連成功。 就算隔一天你回來 靠近裝置,立馬連線成功。這簡直就是太棒了。
非三星手機現象:
也會重連成功,但這時間無法保證,有可能1分鐘,有可能5分鐘,10分鐘,甚至就連不上了。體驗非常不好。
說一下解決辦法:
判斷手機是否為三星手機:
private boolean checkIsSamsung() { //此方法是我自行使用眾多三星手機總結出來,不一定很準確
String brand = android.os.Build.BRAND;
Log.e("", " brand:" + brand);
if (brand.toLowerCase().equals("samsung")) {
return true;
}
return false;
}
在裝置斷線之後, 我們需要進入重連流程:
if(isSamsung){
//這裡最好 Sleep 300毫秒,測試發現有時候三星手機斷線之後立馬呼叫connect會容易藍芽奔潰
mbluetoothGatt.connect();
}else{
connectGatt();
handler.removeCallbacks(runnableReconnect );
handler.postDelayed(runnableReconnect , 30*1000);
}
流程就是斷線之後 三星手機 呼叫官方的connect()函式,而非三星手機 使用connectGatt() ,相當於重新建立連線,並且每30s去迴圈詢問是否需要重新建立連線,如果已經連線好,取消這個迴圈。這樣能保證過一天回來你也有機會重連上,非三星手機通過這個方法重連體驗特別好,也可以達到回來之後瞬間連線的效果
這樣的方案目前是比較好的,手機APP也可以很省電。,像很多其他的防丟器類APP,比如nut.研究過這個app,這個軟體總是在掃描,一直scan,不管你連上還是不連上。 android的scan是很耗電的,app功能完善之後同時也要考慮電量優化。
這裡測試過 手機的耗電量,用示波器來接手機測試的,
三星斷線之後 重連的階段,耗電量大概在20ma-30ma(鎖屏電量 3ma),
魅族MX2 在30s迴圈期間,耗電量大概在15ma(鎖屏電量 15ma),
這裡推測三星的connect()期間它系統自己在後臺使用低頻率的掃描,這樣可以使他重連是很快的。scan就要耗電,所以它電量會比鎖屏上升一點。
魅族MX2採用每30s重新建立連線,沒有scan,基本沒有額外耗電。
這種 重連流程 用身邊的手機 三星S3,S4,note2,MX2,M3,Nexus5測試過都很好用。親們也可以用其他測試一下。
public void connectGatt() {
if (bluetoothDevice != null) {
if (mBluetoothGatt != null) {
mBluetoothGatt.close();
mBluetoothGatt.disconnect();
mBluetoothGatt = null;
}
mBluetoothGatt = bluetoothDevice.connectGatt(this.context, false,
gattCallback);
} else {
Log.e(tagString,
"the bluetoothDevice is null, please reset the bluetoothDevice");
}
}
Runnable runnableReconnect = new Runnable() {
@Override
public void run() {
if (! isConnected() ) {
handler.postDelayed(this, 30 * 1000);
connectGatt();
}
}
};
3.關於多裝置連線核心就是 建立並儲存多個 BluetoothGatt物件而已
為了方便管理,舉個例子,可以自定義類 :MYDevice{
private BluetoothDevice bluetoothDevice;
private BluetoothGatt mBluetoothGatt;
public BluetoothDevice getBluetoothDevice() {
return bluetoothDevice;
}
public void setBluetoothDevice(BluetoothDevice bluetoothDevice) {
this.bluetoothDevice = bluetoothDevice;
}
public boolean connect() {
if (bluetoothDevice != null) {
if (mBluetoothGatt != null) {
mBluetoothGatt.close();
mBluetoothGatt.disconnect();
mBluetoothGatt = null;
}
mBluetoothGatt = bluetoothDevice.connectGatt(this.context, false,
gattCallback);
if (mBluetoothGatt != null)
return mBluetoothGatt.connect();
} else {
Log.e(tagString,
"the bluetoothDevice is null, please reset the bluetoothDevice");
return false;
}
return false;
}
}
每連線一臺裝置,就建立一個MYDevice例項, 用setBluetoothDevice(BluetoothDevice bluetoothDevice)設定bluetoothDevice物件,然後呼叫 connect()方法進行連線。
有5臺裝置就有5個MYDevice例項,可以把這5個實力放入list裡面,方便使用
4.問題總結
三星的很容易出現failed to register callback,或者waiting for callback 如圖
解決辦法: 如網上所說連接方法放入UI執行緒操作 , 親測有效
出現133錯誤 如圖
呼叫connectGatt(this.context, false, gattCallback)(立刻重連);之後如果30秒之內沒連上,會有133出現,這個超時時間文件也沒看到過, 自己測 試出來的。
出現這個錯誤之後會很難再連上裝置,除非重啟手機藍芽(有時甚至要重啟手機) 或者重啟裝置。
這個問題或多或少與app程式碼,裝置2邊都有關係。目前 本人使用上文提到的重連方法 加上本人的裝置 現在很少很少遇到這個錯誤了,總之就是儘量的不好頻繁的斷開,重新連線
手機藍芽奔潰,譬如再次掉用 scan方法會阻塞主執行緒,且掃不到裝置
這出現這個問題一般都會伴隨下面的現象:
執行連線函式的時候 會系統列印這些log。 但是 對比一下下圖:
下面的圖多了一句 onclientregistered; 下圖這個情況是正常的,不會引起藍芽異常,而一旦出現上圖情況,手機藍芽必奔潰
這多半和app程式碼有關, 在gatt尋找到服務之後不要過多的有異常操作
所有操作的回撥函式。
private BluetoothGattCallback gattCallback = new BluetoothGattCallback() {
@Override
public void onCharacteristicChanged(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic) {
super.onCharacteristicChanged(gatt, characteristic);
//收到裝置notify值 (裝置上報值)
}
@Override
public void onCharacteristicRead(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic, int status) {
super.onCharacteristicRead(gatt, characteristic, status);
//讀取到值
}
@Override
public void onCharacteristicWrite(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic, int status) {
super.onCharacteristicWrite(gatt, characteristic, status);
if (status == BluetoothGatt.GATT_SUCCESS) {
//write成功(傳送值成功)
}
}
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status,
int newState) {
super.onConnectionStateChange(gatt, status, newState);
if (status == BluetoothGatt.GATT_SUCCESS) {
if (newState == BluetoothGatt.STATE_CONNECTED) {
// 連線成功
} else if (newState == BluetoothGatt.STATE_DISCONNECTED) {
// 斷開連線
}
}
}
@Override
public void onDescriptorRead(BluetoothGatt gatt,
BluetoothGattDescriptor descriptor, int status) {
super.onDescriptorRead(gatt, descriptor, status);
}
@Override
public void onDescriptorWrite(BluetoothGatt gatt,
BluetoothGattDescriptor descriptor, int status) {
super.onDescriptorWrite(gatt, descriptor, status);
}
@Override
public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {
super.onReadRemoteRssi(gatt, rssi, status);
if (status == BluetoothGatt.GATT_SUCCESS) {
//獲取到RSSI, RSSI 正常情況下 是 一個 負值,如 -33 ; 這個值的絕對值越小,代表裝置離手機越近
//通過mBluetoothGatt.readRemoteRssi();來獲取
}
}
@Override
public void onReliableWriteCompleted(BluetoothGatt gatt, int status) {
super.onReliableWriteCompleted(gatt, status);
}
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
super.onServicesDiscovered(gatt, status);
if (status == BluetoothGatt.GATT_SUCCESS) {
//尋找到服務
}
}
};
---------------------------------------------------------------------------------------------------------------------------------------
當呼叫了連線函式mBluetoothGatt = bluetoothDevice.connectGatt(this.context, false, gattCallback);之後,
如果連線成功就會 走到 連線狀態回撥:
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status,
int newState) {
if (status == BluetoothGatt.GATT_SUCCESS) {
//首先判斷這個status 如果等於 BluetoothGatt.GATT_SUCCESS(value=0)代表這個回撥是正常的,
//如果不等於 0,那邊就代表沒有成功,也不需要進行其他操作了,
// 連線成功和斷開連線都會走到這裡
if (newState == BluetoothGatt.STATE_CONNECTED) {
// 連線成功
//連線成功之後,我們應該立刻去尋找服務(上面提到的BluetoothGattService),只有尋找到服務之後,才可以和裝置進行通訊
gatt.discoverServices();// 尋找服務
} else if (newState == BluetoothGatt.STATE_DISCONNECTED) {
// 斷開連線
}
}
}
當判斷到連線成功之後,會去尋找服務, 這個過程是非同步的,會耗點時間,當尋找到服務之後,會走到回撥:
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
super.onServicesDiscovered(gatt, status);
if (status == BluetoothGatt.GATT_SUCCESS) {
//尋找到服務
//尋找服務之後,我們就可以和裝置進行通訊,比如下發配置值,獲取裝置電量什麼的
readBatrery();//讀取電量操作
sendSetting(); //下發配置值
}
}
/***讀操作***/
void readBatrery(){
//如上面所說,想要和一個學生通訊,先知道他的班級(ServiceUUID)和學號(CharacUUID)
BluetoothGattService batteryService=mBluetoothGatt.getService(UUID.fromString("0000180f-0000-1000-8000-00805f9b34fb")); //此處的0000180f...是舉例,實際開發需要詢問硬體那邊
if(batteryService!=null){
BluetoothGattCharacteristic batteryCharacteristic=batteryService.getCharacteristic(UUID.fromString("00002a19-0000-1000-8000-00805f9b34fb"));//此處的00002a19...是舉例,實際開發需要詢問硬體那邊
if(batteryCharacteristic!=null){
mBluetoothGatt.readCharacteristic(batteryCharacteristic);//讀取電量, 這是讀取batteryCharacteristic值的方法,讀取其他的值也是如此,只是它們的ServiceUUID 和CharacUUID不一樣
}
}
}
如果讀取電量(或者讀取其他值)成功之後 ,會來到 回撥:
@Override
public void onCharacteristicRead(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic, int status) {
super.onCharacteristicRead(gatt, characteristic, status);
//讀取到值,根據UUID來判斷讀到的是什麼值
if (characteristic.getUuid().toString()
.equals("00002a19-0000-1000-8000-00805f9b34fb")) {// 獲取到電量
int battery = characteristic.getValue()[0];
}
}
/***寫操作***/
void sendSetting(){
BluetoothGattService sendService=mBluetoothGatt.getService(UUID.fromString("00001805-0000-1000-8000-00805f9b34fb"));//此處的00001805...是舉例,實際開發需要詢問硬體那邊
if(sendService!=null){
BluetoothGattCharacteristic sendCharacteristic=sendService.getCharacteristic(UUID.fromString("00002a08-0000-1000-8000-00805f9b34fb"));//此處的00002a08...是舉例,實際開發需要詢問硬體那邊
if(sendCharacteristic!=null){
sendCharacteristic.setValue(new byte[] { 0x01,0x20,0x03 });//隨便舉個數據
mBluetoothGatt.writeCharacteristic(sendCharacteristic);//寫命令到裝置,
}
}
}
如果下發配置成功之後,會來到回撥:
@Override
public void onCharacteristicWrite(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic, int status) {
super.onCharacteristicWrite(gatt, characteristic, status);
if (status == BluetoothGatt.GATT_SUCCESS) {
//write成功(傳送值成功),可以根據 characteristic.getValue()來判斷是哪個值傳送成功了,比如 連線上裝置之後你有一大串命令需要下發,你呼叫多次寫命令, 這樣你需要判斷是不是所有命令都成功了,因為android不太穩定,有必要來check命令是否成功,否則你會發現你明明呼叫 寫命令,但是裝置那邊不響應
}
}
講解完讀寫操作,還有一個重要操作 就是 Notify(通知)
notify 就是讓裝置 可以傳送通知給你,也可以說上報值給你(傳送命令給你)
首先你得開啟裝置的通知功能: //引數 enable 就是開啟還是關閉, characteristic就是你想讓具有通知功能的BluetoothGattCharacteristic
private boolean enableNotification(boolean enable,
BluetoothGattCharacteristic characteristic) {
if (mBluetoothGatt == null || characteristic == null)
return false;
if (!mBluetoothGatt.setCharacteristicNotification(characteristic,
enable))
return false;
BluetoothGattDescriptor clientConfig = characteristic
.getDescriptor(UUIDUtils.CCC);
if (clientConfig == null)
return false;
if (enable) {
clientConfig
.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
} else {
clientConfig
.setValue(BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE);
}
return mBluetoothGatt.writeDescriptor(clientConfig);
}
一旦裝置那邊notify 資料給你,你會在回撥裡收到:
@Override
public void onCharacteristicChanged(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic) {
super.onCharacteristicChanged(gatt, characteristic);
//收到裝置notify值 (裝置上報值),根據characteristic.getUUID()來判斷是誰傳送值給你,根據characteristic.getValue()來獲取這個值
}
=========================以上屬於BLE基本的操作,下面是進階========================================
1. 關於傳送命令, 比如剛連線上裝置,你會把很多配置都發送給裝置,此時可能會呼叫多次 write操作,這個時候你應該需要在 write操作之間 加點間隔 例如開啟執行緒,用 Thread.sleep(150), 因為只有收到onCharacteristicWrite回撥之後才代表你的值成功了,如果
不加間隔,你會發現可能只會成功一個值。目前測試下來,我才用的是間隔150毫秒比較好,不會太慢,成功率也會高, 可自己實踐慢慢除錯,這個數值還與裝置端update速率有關
在Write 操作的時候,還有個辦法可以增快下發速度,且不需要加sleep
就是設定characteristic .setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE); Wrtite characteristic without requiring a response by the remote device, 就是不需 要回復,這樣速度會快,測試下來連續傳送9個數據,裝置端那邊列印資料都收到且是正確的,但回撥值有點奇怪:
圖1 是我的傳送指令 ,連續無間隔傳送9個命令:
圖2,3是 onCharacteristicWrite 回撥列印資訊:
從圖片看出,有的值沒有收到 onCharacteristicWrite 回撥,如第一個值 { 1, 0x08, 3 } ,有的值回調了2次 如 { 1, 0x04, 3 },可能會覺得有的值沒有傳送成功吧?
以下是 裝置端收到值的列印資訊:(0x00FF是用做分隔符,可忽略)
可以發現,9個值均接收正常, 所以說 這種快速發值,還是有點可靠的。測試的不多,各位可斟酌使用
2.關於重連
提到ble,估計想的最多的就是重連了。重連當然得是自動重連。可能有人想到的的就是 斷開之後再來一次bluetoothDevice.connectGatt(this.context, false, gattCallback)行不行?
先看看官方的說法 :
官方說 mbluetoothGatt.connect()方法就是用來重連的, 只要你斷線之後 呼叫此方法就好了。
測試下來 確實可以,但不同手機體驗不一樣, 但大家知道 android 碎片化現象很嚴重,什麼 flyme,什麼MIUI,emotion之類的。所以需要做什麼適配。
只調用 mbluetoothGatt.connect()來重連,我歸類為2種 三星 手機 和 非三星手機。
三星手機現象:
重連非常迅速 ,斷開之後,只要一回到有效範圍內。立馬重連成功。 就算隔一天你回來 靠近裝置,立馬連線成功。這簡直就是太棒了。
非三星手機現象:
也會重連成功,但這時間無法保證,有可能1分鐘,有可能5分鐘,10分鐘,甚至就連不上了。體驗非常不好。
說一下解決辦法:
判斷手機是否為三星手機:
private boolean checkIsSamsung() { //此方法是我自行使用眾多三星手機總結出來,不一定很準確
String brand = android.os.Build.BRAND;
Log.e("", " brand:" + brand);
if (brand.toLowerCase().equals("samsung")) {
return true;
}
return false;
}
在裝置斷線之後, 我們需要進入重連流程:
if(isSamsung){
//這裡最好 Sleep 300毫秒,測試發現有時候三星手機斷線之後立馬呼叫connect會容易藍芽奔潰
mbluetoothGatt.connect();
}else{
connectGatt();
handler.removeCallbacks(runnableReconnect );
handler.postDelayed(runnableReconnect , 30*1000);
}
流程就是斷線之後 三星手機 呼叫官方的connect()函式,而非三星手機 使用connectGatt() ,相當於重新建立連線,並且每30s去迴圈詢問是否需要重新建立連線,如果已經連線好,取消這個迴圈。這樣能保證過一天回來你也有機會重連上,非三星手機通過這個方法重連體驗特別好,也可以達到回來之後瞬間連線的效果
這樣的方案目前是比較好的,手機APP也可以很省電。,像很多其他的防丟器類APP,比如nut.研究過這個app,這個軟體總是在掃描,一直scan,不管你連上還是不連上。 android的scan是很耗電的,app功能完善之後同時也要考慮電量優化。
這裡測試過 手機的耗電量,用示波器來接手機測試的,
三星斷線之後 重連的階段,耗電量大概在20ma-30ma(鎖屏電量 3ma),
魅族MX2 在30s迴圈期間,耗電量大概在15ma(鎖屏電量 15ma),
這裡推測三星的connect()期間它系統自己在後臺使用低頻率的掃描,這樣可以使他重連是很快的。scan就要耗電,所以它電量會比鎖屏上升一點。
魅族MX2採用每30s重新建立連線,沒有scan,基本沒有額外耗電。
這種 重連流程 用身邊的手機 三星S3,S4,note2,MX2,M3,Nexus5測試過都很好用。親們也可以用其他測試一下。
public void connectGatt() {
if (bluetoothDevice != null) {
if (mBluetoothGatt != null) {
mBluetoothGatt.close();
mBluetoothGatt.disconnect();
mBluetoothGatt = null;
}
mBluetoothGatt = bluetoothDevice.connectGatt(this.context, false,
gattCallback);
} else {
Log.e(tagString,
"the bluetoothDevice is null, please reset the bluetoothDevice");
}
}
Runnable runnableReconnect = new Runnable() {
@Override
public void run() {
if (! isConnected() ) {
handler.postDelayed(this, 30 * 1000);
connectGatt();
}
}
};
3.關於多裝置連線核心就是 建立並儲存多個 BluetoothGatt物件而已
為了方便管理,舉個例子,可以自定義類 :MYDevice{
private BluetoothDevice bluetoothDevice;
private BluetoothGatt mBluetoothGatt;
public BluetoothDevice getBluetoothDevice() {
return bluetoothDevice;
}
public void setBluetoothDevice(BluetoothDevice bluetoothDevice) {
this.bluetoothDevice = bluetoothDevice;
}
public boolean connect() {
if (bluetoothDevice != null) {
if (mBluetoothGatt != null) {
mBluetoothGatt.close();
mBluetoothGatt.disconnect();
mBluetoothGatt = null;
}
mBluetoothGatt = bluetoothDevice.connectGatt(this.context, false,
gattCallback);
if (mBluetoothGatt != null)
return mBluetoothGatt.connect();
} else {
Log.e(tagString,
"the bluetoothDevice is null, please reset the bluetoothDevice");
return false;
}
return false;
}
}
每連線一臺裝置,就建立一個MYDevice例項, 用setBluetoothDevice(BluetoothDevice bluetoothDevice)設定bluetoothDevice物件,然後呼叫 connect()方法進行連線。
有5臺裝置就有5個MYDevice例項,可以把這5個實力放入list裡面,方便使用
4.問題總結
三星的很容易出現failed to register callback,或者waiting for callback 如圖
解決辦法: 如網上所說連接方法放入UI執行緒操作 , 親測有效
出現133錯誤 如圖
呼叫connectGatt(this.context, false, gattCallback)(立刻重連);之後如果30秒之內沒連上,會有133出現,這個超時時間文件也沒看到過, 自己測 試出來的。
出現這個錯誤之後會很難再連上裝置,除非重啟手機藍芽(有時甚至要重啟手機) 或者重啟裝置。
這個問題或多或少與app程式碼,裝置2邊都有關係。目前 本人使用上文提到的重連方法 加上本人的裝置 現在很少很少遇到這個錯誤了,總之就是儘量的不好頻繁的斷開,重新連線
手機藍芽奔潰,譬如再次掉用 scan方法會阻塞主執行緒,且掃不到裝置
這出現這個問題一般都會伴隨下面的現象:
執行連線函式的時候 會系統列印這些log。 但是 對比一下下圖:
下面的圖多了一句 onclientregistered; 下圖這個情況是正常的,不會引起藍芽異常,而一旦出現上圖情況,手機藍芽必奔潰
這多半和app程式碼有關, 在gatt尋找到服務之後不要過多的有異常操作