[Android实例] 细谈Ble4.0 APP开发

来源:互联网 发布:免费手机绘图软件 编辑:程序博客网 时间:2024/04/28 22:54


转载:http://www.eoeandroid.com/thread-563868-1-2.html


感谢大家的阅读, 欢迎交流 QQ 245121415

13年夏天毕业到现在,从一个只会android皮毛的小子到现在颇有点经验的小工程师,一直从事着蓝牙ble的开发。如大多数人一样,一开始看起来好难啊,天书啊,这些类那些类都干啥的,奔溃。
有幸刚毕业就到了一家从事穿戴设备开发的公司,虽然是个小团队,但能学到很多东西,做android端app开发的就我一个新人。
经历这么多年的学习,也算是有点经验,现在过来总结一下,少走弯路。

Android ble开发和IOS ble开发基本差不多,有点小区别。IOS 表现是特别稳定特别好用,重连飞快,需要操心的少。 Android目前坑还是比较多,各种错误,蓝牙奔溃等等。但,细心调理,Android也还是可以用的。

本篇文章主讲 Android, IOS的简单点,可以下次讲。

先来介绍几个类 :BluetoothGatt,BluetoothGattService,BluetoothGattCharacteristic,BluetoothGattCallback   


  官方介绍网址是   https://developer.android.com/reference/android/bluetooth/BluetoothGatt.html.有兴趣的童鞋可以翻墙去看。以下是我尽可能形象的通 俗表达
BluetoothGatt
       BluetoothGatt 是开发里最重要最常用到的东西了,我把它理解为 手机与 设备通信的管道。有了这个管道,收发数据就容易了。
         BluetoothGatt 是通过蓝牙设备连接获得:  bluetoothDevice.connectGatt(this.context, false, gattCallback);
                                 先来谈一下这个方法:
                         BluetoothGatt android.bluetooth.BluetoothDevice.connectGatt(Context context, boolean autoConnect, BluetoothGattCallback callback)


                          public BluetoothGatt connectGatt (Context context, boolean autoConnect, BluetoothGattCallback callback)                           Added in API level 18

                          Connect to GATT Server hosted by this device. Caller acts as GATT client. The callback is used to deliver results to Caller,           
                            such as connection status as well as any further GATT client operations. The method returns a BluetoothGatt instance. 
                             You   can use BluetoothGatt to conduct GATT client operations.

                               Parameters
                             autoConnectWhether to directly connect to the remote device (false) or to automatically connect as soon as the remote device becomes available (true).                                callbackGATT callback handler that will receive asynchronous callbacks.
                                Throws                           IllegalArgumentExceptionif callback is null                         --------------------------------------------------------------------------------------
                          autoConnect  为false  立刻发起一次连接
为true  自动连接,只要蓝牙设备变得可用



                           实测发现,用false连接比较好,比较快, true会等个十几秒甚至几分钟才会连接上。  开发过程中一般都是用false,扫描到bluetoothdevice之后,直接用false连接即可。
                           BluetoothGattCallback   是非常重要的回调函数,手机与蓝牙设备的一切通信结果都在这里体现。待会细讲。


BluetoothGattService
           蓝牙设备所拥有的服务。在这里比喻成 班级 吧。bluetoothdevice就比喻成学校吧。 一个学校可以有很多个班级。班级 根据UUID来区别。
BluetoothGattCharacteristic
           蓝牙设备所拥有的特征。比喻成 学生。 一个班级里也可以有很多个学生。学生也是根据UUID 来区别
                当你需要用手机来和蓝牙设备通信的时候,就相当于 你想 和一个学生交流,你得先知道 这个学生的学号,所在班级号,就是开发中的所说的CharacUUID, ServiceUUID


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寻找到服务之后不要过多的有异常操作


2 0