BLE
是低功耗
蓝牙。
通用属性配置文件 (GATT)
GATT 配置文件是一种通用规范,内容针对在 BLE 链路上发送和接收称为“属性”的简短数据片段。目前所有低功耗应用配置文件均以 GATT 为基础。
服务Service
服务是一个特征对象的集合,通常UUID为16字节的数。
一个Service可以包含多个特征对象。
特征对象character
服务中的细粒度的操作对象,例如指定可读、可写等
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
ACCESS_COARSE_LOCATION
访问CellID
或WiFi
,只要当前设备可以接收到基站的服务信号,便可获得位置信息。(COARSE英文原意为:粗略的,可以理解为这种方式获得的位置信息是相对粗略的数据)。
如果您的
应用适配 Android 9(API 级别 28)或更低版本
,则您可以声明ACCESS_COARSE_LOCATION
权限而非 ACCESS_FINE_LOCATION
权限。
获取手机端BLE适配器对象。
获取 BletoothAdapter
对象之前,需要判断手机是否支持BLE!
Android版本在4.2,也就是API 17 才能支持BLE
,但目前绝大多数的手机都不止这个版本了。
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.JELLY_BEAN_MR1
|| !activity.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
Log.i("checkBLEState()-->", "您的手机不支持BLE");
//提示手机端不支持BLE设备
JSUtil.execCallback(BLEWebview, checkBLECallBackID, "PHONE_NOT_BLE", JSUtil.OK, true);
return;
}
要获取到 BletoothAdapter
操作对象之前,需要优先获取设备管理器对象 BluetoothManager
。
// 蓝牙管理,这是系统服务可以通过getSystemService(BLUETOOTH_SERVICE)的方法获取实例
BluetoothManager bluetoothManager = (BluetoothManager) activity.getSystemService(Activity.BLUETOOTH_SERVICE);
获取后,就可以采取管理器对象获取到手机端蓝牙适配器对象了。
BluetoothAdapter mBluetoothAdapter = bluetoothManager.getAdapter();
如果手机BLE未开启,判断并自动开启。
if (!mBluetoothAdapter.isEnabled()) {
Log.i("mBluetoothAdapter-->", "蓝牙未开启,提示用户开启蓝牙");
//自动开启BLE
mBluetoothAdapter.enable();
JSUtil.execCallback(BLEWebview, checkBLECallBackID, "PROMPT_USER_CLICK_BTN", JSUtil.OK, true);
return;
}
开启BLE是需要时间的,在源码中有这方面的说明,这里需要对此注意点。
成功获取到 BluetoothAdapter
对象,且BLE
为开启状态时,就可以扫描周围的设备了。
开启扫描操作后,需要起一个定时器,达到
超过指定时间关闭扫描
的功能。
因为扫描是一个极耗电
的操作。
/**
* 执行扫描操作
*
* @param scantimes
* 扫描时间
*/
public void startScan(final int scantimes) {
// 用户可能在扫描中,关闭蓝牙
if (mBluetoothAdapter == null) {
Log.i("startScan-->", "蓝牙未开启");
return;
}
// 正在扫描跳出扫描
if (isScanning) {
Log.i("startScan-->", "正在扫描");
return;
}
// 此项方法只会执行一次 避免多次造成资源浪费
new Timer().schedule(new TimerTask() {
@Override
public void run() {
isScanning = false;
if (mBluetoothAdapter != null) {
mBluetoothAdapter.stopLeScan(scanAroundBleDeviceCallback);
}
Log.i("startScan-->", "定时线程");
}
}, scantimes);
//除了调用扫描流程外,还需要增加一个扫描操作的回执监听
/**
* 扫描操作 扫描到设备后的回调操作
*/
public BluetoothAdapter.LeScanCallback scanAroundBleDeviceCallback = new LeScanCallback() {
@Override
public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {
Log.i("scanAroundBleDeviceCallback -- onLeScan -- mac -->", String.valueOf(device.getAddress()));
Log.i("scanAroundBleDeviceCallback -- onLeScan -- rssi -->", String.valueOf(rssi));
}
};
虽然有定时关闭扫描
操作的逻辑,不过在扫描和连接操作通常都是很迅速的,在扫描到设备后,需要及时的关闭扫描操作,避免资源的消耗。
mBluetoothAdapter.stopLeScan(scanAroundBleDeviceCallback);
当扫描到设备,需要进行连接操作。
try {
BluetoothGatt mBluetoothGatt = mBluetoothDevice.connectGatt(activity, false, gattcallback);
} catch (Exception e) {
e.printStackTrace();
}
//连接操作也需要一个回执监听
// GATT连接的回调函数
public BluetoothGattCallback gattcallback = new BluetoothGattCallback() {
/**
* 蓝牙连接状态改变后调用 此回调 (断开,连接)
*
* @param gatt
* @param status
* @param newState
*/
@Override
public void onConnectionStateChange(final BluetoothGatt gatt, int status, final int newState) {
super.onConnectionStateChange(gatt, status, newState);
Log.i("onConnectionStateChange--status->", String.valueOf(status));
Log.i("onConnectionStateChange--newState->", String.valueOf(newState));
if (newState == BluetoothProfile.STATE_CONNECTED && mBluetoothDevice != null && mBluetoothGatt != null) {// 连接成功
Log.i("onConnectionStateChange-->", "连接成功");
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {// 连接失败 或者连接断开都会调用此方法
Log.i("连接回调-->", "getConnectedState()");
if (getConnectedState() == BluetoothProfile.STATE_CONNECTED) {
Log.i("onConnectionStateChange-->", "连接伪失败");
return;
}
Log.i("onConnectionStateChange-->", "连接失败");
}
/**
* 连接成功后开启服务的回调
*
* @param gatt
* @param status
*/
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
super.onServicesDiscovered(gatt, status);
// 寻找服务时
if (status == BluetoothGatt.GATT_SUCCESS) {
Log.i("onServicesDiscovered-->", "服务开启成功");
} else {// 未发现该设备的服务
}
/**
* 读特征值的回调
*/
@Override
public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic character_v1, int status) {
Log.i("onCharacteristicRead--status-->", String.valueOf(status));
// 读取到值,在这里读数据
if (status == BluetoothGatt.GATT_SUCCESS) {// 0
} else if (status == BluetoothGatt.GATT_FAILURE) {
Log.i("onCharacteristicRead-->", "读取数据失败");
}
}
/**
* 给指定Characteristic发送数据后的回调
*
* @param gatt
* @param characteristic
* @param status
*/
@Override
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {// 写入成功
Log.i("onCharacteristicWrite-->", "写入数据成功");
} else if (status == BluetoothGatt.GATT_FAILURE) {// 写入失败
Log.i("onCharacteristicWrite-->", "写入数据失败");
} else if (status == BluetoothGatt.GATT_WRITE_NOT_PERMITTED) {// 没有写入的权限
}
}
// 订阅了远端设备的Characteristic信息后,
// 当远端设备的Characteristic信息发生改变后,回调此方法
@Override
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
super.onCharacteristicChanged(gatt, characteristic);
}
@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 onReliableWriteCompleted(BluetoothGatt gatt, int status) {
super.onReliableWriteCompleted(gatt, status);
}
@Override
public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {
super.onReadRemoteRssi(gatt, rssi, status);
}
};
连接操作执行后,可能会出现如下的现象:
在日志中得到连接失败的回执信息,但是设备却和手机端是正常连接的。
此时则需要判断连接伪状态
。
使用如下代码判断其是否为连接状态:
/**
* 获取app和ble设备的连接状态
*
* @return 100表示未执行次方法;0disConnected;1connecting;2connected;3disConnecting
*
*/
public int getConnectedState() {
int state = 100;
if (lock_bluetoothManager != null && (lock_mac != null | lock_mac == "") && mBluetoothAdapter != null
&& mBluetoothAdapter.isEnabled()) {
BluetoothDevice bdevice = mBluetoothAdapter.getRemoteDevice(lock_mac);
// 蓝牙连接状态为0,1,2,3 为了避免出问题初始化为100
/**
* STATE_DISCONNECTED 0 STATE_CONNECTING 1 STATE_CONNECTED 2 STATE_DISCONNECTING
* 3
*/
state = lock_bluetoothManager.getConnectionState(bdevice, BluetoothProfile.GATT);
}
Log.i("getConnectedState()方法调用,返回数据为-->", String.valueOf(state));
return state;
}
连接成功后,至进行正常的数据通信操作前,需要优先开通通信服务
。否则出现数据无法发送的问题。
new Timer().schedule(new TimerTask() {
@Override
public void run() {
Log.i("定时开启服务--->", "开启服务");
try {
// 连接成功后去发现该连接的设备的服务
mBluetoothGatt.discoverServices();
} catch (NullPointerException e) {
e.printStackTrace();
Log.i("discoverServices()-->", "捕获到空指针异常 执行退出操作");
// 这里只是使用FIND_NO_SERVICE 让按钮状态值能够回复
JSUtil.execCallback(BLEWebview, checkBLECallBackID, "FIND_NO_SERVICE", JSUtil.OK, true);
quit();
}
}
}, discoverServicesTimes);
连接成功后,不是说立即就能开启通信服务
的,连接是一个过程信息,还需要等待系统连接稳定,通过多次的试验,此处的discoverServicesTimes
参数值最少为600毫秒
。
当开启通信服务后,成功或者失败的回执信息,会出现在 BluetoothGattCallback
中的onServicesDiscovered
中。
开启通信服务后,往往就可以根据公司的通信协议进行通信操作测试了。
由于需要进行通信操作,此时则需要根据Service
和Charcter
获取通信操作对象信息,实现数据的读取
和发送
。
/**
* 获取服务封装方法
*
* @param uuid
* 每个service的唯一标识信息
* @return
*/
public BluetoothGattService getService(UUID uuid) {
Log.i("getService-->", "getConnectedState()");
if (mBluetoothGatt != null && getConnectedState() == BluetoothProfile.STATE_CONNECTED) {
return mBluetoothGatt.getService(uuid);
} else {
return null;
}
}
/**
* 获取特征封装方法
*
* @param serviceUUID
* 指定的service标识信息
* @param characteristicUUID
* 每个service中都包含多个characteristic对象信息
* @return
*/
public BluetoothGattCharacteristic getCharacteristic(String serviceUUID, String characteristicUUID) {
BluetoothGattService service = getService(UUID.fromString(serviceUUID));
if (service == null) {
return null;
}
final BluetoothGattCharacteristic gattCharacteristic = service
.getCharacteristic(UUID.fromString(characteristicUUID));
if (gattCharacteristic != null) {
return gattCharacteristic;
} else {
return null;
}
}
本次的案例中采取 读/写 方式进行,后续退出 notify 类型。
发送指定的信息:
/**
* * 向指定的特征对象中发送相关的信息
*
* @param mBluetoothGattCharacteristic
* 发送信息的目标的对象地址
* @param sendByteVal
* 发送的数据信息
* @param mBluetoothGattCharacteristic
* @param sendByteVal
* @return true 发送执行成功 false发送执行失败
*/
public boolean writeToChar(BluetoothGattCharacteristic mBluetoothGattCharacteristic, byte[] sendByteVal) {
boolean writeToCharSuccess = false;
// 只有在状态为连接中,才能进行数据的发送
if (mBluetoothGattCharacteristic != null && mBluetoothGatt != null
&& getConnectedState() == BluetoothProfile.STATE_CONNECTED) {
mBluetoothGattCharacteristic.setValue(sendByteVal);
writeToCharSuccess = mBluetoothGatt.writeCharacteristic(mBluetoothGattCharacteristic);
}
return writeToCharSuccess;
}
读取指定的信息:
/**
* 读取特征值数据函数封装
*/
public void READBLEDATA() {
// 虽然做了拦截操作 但为了保证安全健壮性
if (readCharf3 == null) {
Log.i("FIND_SERVICE-->", "获取F3特征对象失败");
JSUtil.execCallback(BLEWebview, checkBLECallBackID, "GET_CHAR_FAIL", JSUtil.OK, true);
quit();
return;
}
// 对象存在 则进行读取特征值数据操作
readCharacteristic(readCharf3);
}
/**
* 读取指定特征对象中的数据操作
*
* @param characteristic
* 具体的特征对象
*/
public void readCharacteristic(BluetoothGattCharacteristic characteristic) {
if (mBluetoothAdapter == null || mBluetoothGatt == null) {
return;
}
mBluetoothGatt.readCharacteristic(characteristic);
}
//尝试断开
mBluetoothGatt.disconnect();
//等待回执连接状态为断开连接后,再次释放资源
mBluetoothGatt.close();
虽然直接调用
mBluetoothGatt.close();
也能达到断开的目的,但多次操作后,会造成Android手机蓝牙的死机事件!
mBluetoothGatt.close();
是释放资源操作。
mBluetoothGatt.disconnect();
才是断开连接操作,但是断开连接操作动作过于缓慢!
Android BLE 蓝牙编程(一)
低功耗蓝牙官网文档