月度归档:2015年12月

搞定Android Bluetooth Low Energy-04

蓝牙读写

蓝牙连接成功之后,可以对services进行读写。由于services是存在蓝牙设备中的,需要调用discoverServices()来发现蓝牙服务。
读写数据主要一下几种,读写和扫描一样都是异步的。

  • writeCharacteristic()
  • writeDescriptor()
  • setCharacteristicNotification()
  • readCharacteristic()
  • readDescriptor()
  • readRemoteRssi()
BluetoothGatt.java
//通过characteristic,把value写给蓝牙设备
characteristic.setValue(byte[] value)
public boolean writeCharacteristic(BluetoothGattCharacteristic characteristic)

BluetoothGattCallback.java
//写完回调方法
public void onCharacteristicWrite (BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status)


//BluetoothGattDescriptor 和 BluetoothGattCharacteristic操作类似,只是不同类型,descriptor一般属于某个characteristic。
BluetoothGatt.java
//通过descriptor,把value写给蓝牙设备
descriptor.setValue(byte[] value)
public boolean writeDescriptor (BluetoothGattDescriptor descriptor)

BluetoothGattCallback.java
//写完回调方法
public void onDescriptorWrite (BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status)



//开启notification相对麻烦点
//开启notifications需要两步, 首页本地开启监听, 然后远程蓝牙设备也要开启。
private static void enableMagnetometerNotifications(BluetoothGatt bluetoothGatt) {
    BluetoothGattService service = bluetoothGatt.getService(uuid1);
    BluetoothGattCharacteristic c = service.getCharacteristic(uuid2);
    bluetoothGatt.setCharacteristicNotification(c, true); //本地开启

    BluetoothGattDescriptor config = c.getDescriptor(uuid3);
    config.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
    bluetoothGatt.writeDescriptor(config); //远程开启
}

BluetoothGattCallback.java
//notification回调方法
public void onCharacteristicChanged (BluetoothGatt gatt, BluetoothGattCharacteristic characteristic)

Services、Characteristics、Descriptors之间的关系

gatt_ccc

  1. BluetoothGatt.discoverServices()发现蓝牙所有服务
  2. BluetoothGatt.getService (UUID uuid) 获取到具体服务
  3. BluetoothGattService.getCharacteristic (UUID uuid) 获取到具体Characteristic
  4. BluetoothGattCharacteristic.getDescriptor (UUID uuid)获取到具体Descriptor

开发建议

  1. 有些手机服务会缓存,再特殊情况蓝牙服务会改变的情况下,需要强制refresh。
  2. 神奇的129(0x81)错误码:Android Issue 156730
  3. 写数据不要并发,最好做个消息队列,写完成了再写下一个:133(0x85)错误码。
  4. BLE默认可以发送最大20字节
  5. Android-4-3-bluetooth-low-energy-unstable

参考文献

搞定Android Bluetooth Low Energy-03

连接蓝牙设备

BluetoothDevice,扫描到的远程蓝牙设备,可以进行连接connectGatt(),也可以查询设备信息(如:名称、地址、配对状态)
可以通过Mac地址得到BluetoothDevice, BluetoothAdapter.getRemoteDevice(mac)
也可以通过系统配对的蓝牙设备中,获取到BluetoothAdapter.getBondedDevices()

connectGatt

BluetoothDevice.java
/**
 *参数autoConnect:当设备有效(可连接)时,系统是否自动去连接设备。
 *true:系统就会发起一个后台连接,等到系统发现了一个有效设备,就会自动连上,通常这个过程是非常慢的。
 *false:就会直接连接,通常会比较快。
*/
public BluetoothGatt connectGatt (Context context, boolean autoConnect, BluetoothGattCallback callback)

BluetoothGatt.java
// 通过connectGatt连接成功,中途断开重连蓝牙,不需要再次扫描了。有可能失败~
public boolean connect ()
// 断开连接
public void disconnect()
// 关闭Bluetooth GATT client
public void close()

BluetoothGattCallback.java
// 蓝牙连接状态变化
public void onConnectionStateChange (BluetoothGatt gatt, int status, int newState)

连接蓝牙实例

    protected BluetoothGatt mGatt = null;
    //device 连接
    mGatt = device.connectGatt(context, false, mGattCallback);

    protected BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
        @Override
        public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {

            if (newState == BluetoothProfile.STATE_CONNECTED) {
                //连接成功
                
            } else {
                //连接断开

            }
        }

    };

开发建议

1. connectGatt(),connect(),disconnect(),discoverServices()使用UI线程 Samsung Galaxy S4 BUG
2. BLE开发的各种坑
3. Android Bug:BluetoothGatt.connect() doesn’t work on Nexus devices

搞定Android Bluetooth Low Energy-02

发现蓝牙设备

一般情况下,要连接蓝牙设备,先要发现蓝牙设备,也就是进行蓝牙扫描。
Peripheral devices(一直发送蓝牙广播,例如:手环)
Central devices(扫描接收Peripheral devices的广播,例如:Android手机)
advertising_and_discovery    广播示意图

这个有点类似ARP(Address Resolution Protocol,地址解析协议),不过ARP高级多了,但都是为了得到目标设备的通信信息。

Android蓝牙扫描

Android4.3/4.4存在一些bug和不稳定性,Android5.0出了新的API。

Android4.3/4.4
BluetoothAdapter.java
//开始扫描,扫描所有附近的蓝牙设备,扫描结果在回调:onLeScan
public boolean startLeScan (BluetoothAdapter.LeScanCallback callback)
//开始扫描广播中指定uuid service的设备,但是对于128位的UUID,这个方法无效。
public boolean startLeScan (UUID[] serviceUuids, BluetoothAdapter.LeScanCallback callback)
//由于扫描是耗电操作,完成后尽早结束扫描。
public void stopLeScan (BluetoothAdapter.LeScanCallback callback)

BluetoothAdapter.LeScanCallback.java
//扫描结果回调,回调是子线程。
public abstract void onLeScan (BluetoothDevice device, int rssi, byte[] scanRecord)
Android5.0及以上
BluetoothLeScanner.java
public void startScan (ScanCallback callback)
public void startScan (List<ScanFilter> filters, ScanSettings settings, ScanCallback callback)
public void stopScan (ScanCallback callback)

ScanCallback.java
public void onScanResult (int callbackType, ScanResult result)
public void onScanFailed (int errorCode)
Android扫描强烈建议:

0. 权限:BLUETOOTH and BLUETOOTH_ADMIN
1. 扫描是耗电操作,扫描完毕立马结束扫描。
2. 扫描是耗电操作,不要一直扫描,设置超时时间(当设备不在的时候,纯属浪费)。
3. 有些手机,一次扫描结果只上报一次而且device.getName()可能返回为null,如果想再次上报扫描,需要重新开始扫描。
4. 扫描结果回调是子线程。
5. startLeScan()有可能失败,需要关注return值。
6. Android6.0还需要权限:ACCESS_COARSE_LOCATION or ACCESS_FINE_LOCATION

Android bug:BLE filtering in startLeScan(UUIDs, callback) doesn’t work for 128-bit UUIDs

Android蓝牙扫描实例

权限配置:
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
库依赖

Add it in your root build.gradle at the end of repositories:

allprojects {
	repositories {
		...
		maven { url "https://oss.sonatype.org/content/repositories/snapshots" }
	}
}
Add the dependency
dependencies {
    compile 'com.github.captain-miao:ble:1.0.0-SNAPSHOT'
}
几行搞定扫描
BleScanner mBleScanner = new BleScanner(getContext(), new SimpleScanCallback() {
    @Override
    public void onBleScan(BluetoothDevice device, int rssi, byte[] scanRecord) {
        
    }

    @Override
    public void onBleScanFailed(BleScanState scanState) {

    }
});

//开始扫描 10秒超时
mBleScanner.startBleScan(10 * 1000);

//停止扫描
mBleScanner.stopBleScan();

Android6.0 权限处理参考:

Android M(6.0) 权限爬坑之旅