Android 4.0低功耗蓝牙开发!

    公司开发任务是,将医疗设备经过蓝牙集成到app中,在这开发中遇到了数不尽的坑.在此记录一下作一个记录,若是其余开发人员看见或许能提供一些帮助,若有不对,尽情指正,不胜感激!android

  刚开始接触的时候,被各类超长的API吓到了,像:BluetoothGatt , BluetoothGattCharacteristic , BluetoothGattDescriptor 等等.并且还要作多链接,上位机一对多下位机.网上例子也是杂七杂八.看的头晕.后来在老大的帮助下,渐渐明白许多,在此感谢老大.废话到此结束,下面进入正题.数组

1、检测本机是否支持蓝牙

1. 判断当前设备是否支持蓝牙app

BluetoothManager mBluetoothManager = (BluetoothManager) activity.getSystemService(Context.BLUETOOTH_SERVICE);
BluetoothAdapter adapter = mBluetoothManager.getAdapter();
if(adapter==null){    
   //系统不支持蓝牙。
}

2. 判断当前设备是否支持低功耗蓝牙BLEide

boolean isSupportBle = activity.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE);

3. 判断蓝牙是否开启,开启蓝牙!post

BluetoothManager mBluetoothManager = (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE);
BluetoothAdapter adapter = mBluetoothManager.getAdapter();

if(!adapter.isEnable){  //未开启蓝牙
    //申请开启蓝牙
    Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
    startActivityForResult(intent , request);
}

2、开始扫描周围的蓝牙设备

BluetoothAdapter adapter = mBluetoothManager.getAdapter();
adapter.startLeScan(callback); //扫描须要一个回调。

注意,扫描周围蓝牙是一个很耗电的过程,最好加上一个扫描时间。自动中止。code

handler.postDelayed(new Runnable() {
    @Override
    public void run() {
         adapter.stopLeScan(callback); //中止扫描
    }
},10000);//设置10秒钟结束扫描
public BluetoothAdapter.LeScanCallback scanCallBack = new BluetoothAdapter.LeScanCallback() {
     @Override
     public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {
           //这里注意,本人在开发中遇到的是 常常有的蓝牙设备是没有名字的, (device.getName == null)
           //不知道这是什么缘由引发的,后来跟不少蓝牙高手讨论的是结果初步怀疑应该是芯片的问题
           //尤为是MTK的芯片常常出现这种问题,换了搭载高通和华为的芯片的设备就没问题了。
           
     }
};

3、链接蓝牙设备

BluetoothDevice remoteDevice = adapter.getRemoteDevice(address);
remoteDevice.connectGatt(context, true, mGattCallback);//参数1:上下文。
                                                       //参数2:是否自动链接(当设备能够用时)
                                                       //参数3:链接回调。

这里可能有些疑问就是,明明已经扫描到了,在回调中已经有了 BluetoothDevice 为什么还要去 getRemoteDevice(address)?
那是由于,不少低功耗的设备开机时间是不多的,就拿咱们公司开发的那个血压计,他是开机才开启蓝牙,而测量完了以后过一段时间就会自动关闭。因此防止去链接设备的时候设备已经关机的状况。xml

链接回调ip

private BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
    @Override
    public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
       if (newState == BluetoothProfile.STATE_CONNECTED) {
                //链接成功
        } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
                //链接断开
        }
        changeStatus(newState);//改变当前状态
    }

    @Override
    public void onServicesDiscovered(BluetoothGatt gatt, int status) {
         //当服务发现以后回调这里
    }

    @Override
    public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, 
    int status) {
    
    }

    @Override
    public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
    
    }

    @Override
    public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
    
    }
};

4、发现服务

一个低功耗蓝牙设备是有不少种服务的,就好比该设备的电量信息,设备的当前状态(好比血压计,是正在测量仍是在等待测量)
有的设备支持历史数据等等。这些都是在蓝牙的服务当中。咱们要去发现蓝牙的服务!开发

这里很简单就是一句话,在链接成功的回调中调用:rem

public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
    if (newState == BluetoothProfile.STATE_CONNECTED) {
          //链接成功
          gatt.discoverServices();//开始发现设备的服务
    } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
          //链接断开
    }
    changeStatus(newState);//改变当前状态
}

调用了以后会在 另外一个回调中 回调回来。

@Override
     public void onServicesDiscovered(BluetoothGatt gatt, int status) {
         //当服务发现以后回调这里
     }

5、数据交互

这里是比较重要的地方,注意,每个蓝牙的通信协议不通,有的设备是链接了以后不须要任何操做就等待蓝牙设备上传数据的,而有的设备是须要手动打开数据通道!或者发送指令给蓝牙设备,每个Gatt协议中有多个BluetoothGattService,而每一个BluetoothGattService中又有多个BluetoothGattCharacteristic (我把它看作一个数据通道-_-!),而每个BluetoothGattCharacteristic 的属性是不一样的,有的是可读,有的是可写,有的是可订阅,因此必定不要搞混了,能够用UUID区分他们,这里大多数设备厂家都会给一份设备的通信协议其中就有 哪个UUID 表明什么。都会有说明。经过UUID 获取到了对应的BluetoothGattCharacteristic 以后就能够判断他的属性是什么。

开启数据通道

@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
//服务发现方法回调。
  if (status == BluetoothGatt.GATT_SUCCESS) {
      BluetoothGattService service = gatt.getService(SERVICE_UUID); //经过厂家给的UUID获取BluetoothGattService 
       if (service != null) {
           BluetoothGattCharacteristic characteristic = service.getCharacteristic(CHARACTERISTIC_UUID);//同上
            if (characteristic != null &&
                 (characteristic.getProperties() | BluetoothGattCharacteristic.PROPERTY_NOTIFY) > 0) {
                   //经过判断,打开Notification 通知,提醒。通常是设备测量完成了以后会发送对应的数据上来。
                   gatt.setCharacteristicNotification(characteristic, true);
                   
                   //在经过上面的设置返回为true以后还要进行下面的操做,才能订阅到数据的上传。下面是完整的订阅数据代码!
                   if(gatt.setCharacteristicNotification(characteristic, true)){
                            for(BluetoothGattDescriptor dp: characteristic.getDescriptors()){
                                if (dp != null) {
                                    if ((characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_NOTIFY) != 0) {
                                        dp.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
                                    } else if ((characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_INDICATE) != 0) {
                                        dp.setValue(BluetoothGattDescriptor.ENABLE_INDICATION_VALUE);
                                    }
                                    gatt.writeDescriptor(dp);
                                }
                            }
                        }
            }
       }
   }
 }

像设备发送指令
  通常向设备发送什么指令在通信协议上面也是有的,都是发送一个byte[]数组,每一位表明什么协议里面都是不一样的。例如:一个测量温度的设备,他当前是华氏度的单位,咱们能够给他发送一个指令让他把单位更换成摄氏度:

private void changeMonitorMod(BluetoothGatt gatt, byte[] buffer) {
   if (gatt != null && gatt != null) {
       BluetoothGattService writeService = gatt.getService(MYUUID);
       if (writeService == null) {
              return;
       }
   }

    BluetoothGattCharacteristic writeCharacteristic = writeService.getCharacteristic(MYWRITECHARACTERISTIC);
    if (writeCharacteristic == null) {
          return;
    }
    
    writeCharacteristic.setValue(buffer);
    //上面的buffer数组中装的就是指令,多长? 每一位上面的数字表明什么意思在协议中查看!
    gatt.writeCharacteristic(writeCharacteristic);//像设备写入指令。
          
}

不要忘了,要在清单文件中AndroidManifest.xml 声明权限哦。

<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>

关于一些坑:

  1. 不少厂家很坑爹,给的文档水的要命,第一时间要看看文档详细不详细,若是没有文档至少也要给个Demo.

  2. 注意设备的开机时间,自动关机时间,对状态的保存。

  3. 不少设备在自动关机以后的回调是很慢的,甚至设备关机10秒以后才会回调到链接状态的回调方法中。

  4. 关于手动设置断开 gatt.disConnect() 这个方法,我试过了,调用以后确实会当即回调到对应的状态方法中,可是实际上物理上的链接是尚未断开的。物理上的链接断开以后还会再次回调到方法中。这是一个比较漫长的回调,区别与设备,不通设备的机制不同,有的快,有的慢。

  好了,差很少就这么多,写的匆忙,若是有哪里不对,轻喷,还请大佬们指正。谢啦!