1. 程式人生 > >[Android例項] 細談Ble4.0 APP開發

[Android例項] 細談Ble4.0 APP開發

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尋找到服務之後不要過多的有異常操作