这是系列文章的第二篇,第一篇 Android BLE开发指南(一)入门基础 主要介绍了BLE开发的一些基础知识。那么接下来的
这篇文章主要讲解BLE中心设备端程序的开发流程,让你的Android设备可以通过 BLE 进行数据的收发。
<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_COARSE_LOCATION" />
Android 6.0 (API 23) 开始需要动态申请权限,这个不多说了。
BLE的相关操作都需要在蓝牙开关打开的前提下进行。所以首先检查蓝牙是否打开。
private BluetoothAdapter mBtAdapter;
public void init(Application application) {
......
mBtAdapter = BluetoothAdapter.getDefaultAdapter();
}
public boolean isBleEnabled() {
return mBtAdapter != null && mBtAdapter.isEnabled();
}
如果开关未开启,则向用户申请开启蓝牙开关
public boolean setBleEnable(Activity activity, boolean enable) {
boolean setSucceed = true;
if (enable) {
Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
activity.startActivityForResult(intent, BleManager.REQUEST_ENABLE_BT);
} else {
......
}
return setSucceed;
}
用户同意或者拒绝蓝牙开关申请后,会通过 onActivityResult 将结果进行反馈,如果蓝牙开启成功,则继续下一步操作。
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode == RESULT_OK && requestCode == BleManager.REQUEST_ENABLE_BT) {
BleManager.getInstance().startScanLeDevice(mBleScanListener);
}
}
通过调用 BluetoothAdapter.startLeScan 开启扫描,传入 BluetoothAdapter.LeScanCallback 回调作为参数,用以接收扫描结果。
由于扫描操作比较耗电,在找到目前设备后,应当及时调用 BluetoothAdapter.stopLeScan 停止扫描。并设置超时结束,避免扫描时间过长增加功耗。
private static final int DEFAULT_SCAN_TIMEOUT = 10000; // 10s
private BluetoothAdapter mBtAdapter;
private BluetoothAdapter.LeScanCallback mLeScanCallback;
private boolean mIsScanning;
private Handler mHandler = new Handler();
public void startScanLeDevice(BleScanListener scanListener) {
scanLeDevice(true, scanListener);
}
public void stopScanLeDevice() {
scanLeDevice(false, null);
}
private void scanLeDevice(boolean enable, BleScanListener scanListener) {
if (enable) {
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
stopScanLeDevice();
}
}, DEFAULT_SCAN_TIMEOUT);
if (!mIsScanning) {
mBtAdapter.startLeScan(mLeScanCallback);
}
mIsScanning = true;
......
} else {
if (mIsScanning) {
mBtAdapter.stopLeScan(mLeScanCallback);
}
mIsScanning = false;
......
}
}
在 Android 6.0 (API 23) 开始,Google提供了开启扫描的新接口——BluetoothLeScanner,功能更加强大,可以对扫描进行很多个性化设置,有兴趣的筒子们可以研究一下。
@RequiresApi(api = Build.VERSION_CODES.M)
public void scanLeDeviceNew() {
BluetoothLeScanner leScanner = mBtAdapter.getBluetoothLeScanner();
ScanSettings scanSettings = new ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_BALANCED)
.setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
.setMatchMode(ScanSettings.MATCH_MODE_STICKY)
.build();
List<ScanFilter> scanFilters = new ArrayList<>();
ScanFilter scanFilter = new ScanFilter.Builder()
.setDeviceAddress("device address")
.setServiceUuid(ParcelUuid.fromString("service uuid"))
.build();
scanFilters.add(scanFilter);
leScanner.startScan(scanFilters, scanSettings, new ScanCallback() {
@Override
public void onScanResult(int callbackType, ScanResult result) {
super.onScanResult(callbackType, result);
}
@Override
public void onBatchScanResults(List<ScanResult> results) {
super.onBatchScanResults(results);
}
@Override
public void onScanFailed(int errorCode) {
super.onScanFailed(errorCode);
}
});
}
在扫描到BLE设备后,通过 BluetoothAdapter.LeScanCallback 接口的 onLeScan 方法接收回调通知,每找到一个设备,会回调一次该函数。从回调的数据中可以获取到外围设备的名称、UUID和MAC地址等信息。其中,MAC地址是用于连接目标外围设备的唯一标识。
这里需要注意的是,此MAC地址非彼MAC地址。为什么这么说呢? 其实就是该MAC地址并非外围设备真实的MAC地址,而是根据一定规则进行处理后的地址,可能是处于安全考虑的一种保护措施吧。
private BleScanListener mBleScanListener;
private BluetoothAdapter.LeScanCallback mLeScanCallback = new BluetoothAdapter.LeScanCallback() {
@Override
public void onLeScan(BluetoothDevice bluetoothDevice, int rssi, byte[] bytes) {
if (mBleScanListener != null) {
mBleScanListener.onBleDeviceFind(new BleDeviceInfo(bluetoothDevice.getName(),
UUIDUtil.parseUuidStr(bytes),
bluetoothDevice.getAddress()));
}
}
};
首先,通过扫描时获取到的目标设备的MAC地址,新建一个 BluetoothDevice 实例对象, 再调用 connectGatt 发起连接操作。上面我们说过,这个MAC地址,是经过一定规则生成而来的,所以在连接外围设备时,直接把它的真实MAC地址拿来进行连接是行不通的,必须先通过扫描来拿到目标设备的虚拟MAC地址才行。
private BluetoothGattCallback mGattCallback;
public void connectDevice(BleDeviceInfo deviceInfo, BleDeviceListener gattListener) {
BluetoothDevice btDevice = mBtAdapter.getRemoteDevice(deviceInfo.getAddress());
BluetoothGatt btGatt = btDevice.connectGatt(mContext, false, mGattCallback);
}
从上面可以看到,connectGatt 需要传入一个 BluetoothGattCallback 接口作为参数,其连接结果也会通过该接口的 onConnectionStateChange 函数进行回调。
如果 newState 的值等于 BluetoothProfile.STATE_CONNECTED,表示设备连接成功。
private BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
switch (newState) {
case BluetoothProfile.STATE_CONNECTED:
gatt.discoverServices();
break;
case BluetoothProfile.STATE_DISCONNECTED:
gatt.close();
break;
}
}
......
};
连接成功后,是不是可以开始尽情的操作了呢?还不是时候,接着往下走哈~
gatt.discoverServices();
在成功建立连接后,需要调用 discoverServices 查找可用的BLE服务。扫描结果将会通过 BluetoothGattCallback 接口的 onServicesDiscovered 函数进行回调。调用 gatt.getServices 函数即可拿到查找出来的所有BLE服务。
private BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
switch (status) {
case BluetoothGatt.GATT_SUCCESS:
List<BluetoothGattService> serviceList = gatt.getServices();
for (BluetoothGattService btGattService : serviceList) {
// 遍历服务中包含的所有Characteristic
List<BluetoothGattCharacteristic> charics = btGattService.getCharacteristics();
......
}
break;
}
}
......
};
在第一篇文章 Android BLE开发指南(一)入门基础 中,我们介绍过,每个BLE服务中包含一个或多个 Characteristic。BLE数据通信 Characteristic 发挥了关键的作用。
这里定义的两个 UUID,对应外围设备端BLE服务中定义好的两个 Characteristic 的 UUID(一个可写,一个可读)。通过这两个UUID,就可以获取到对应的 Characteristic 实例。一般BLE数据通信都有可写和可读的两个 Characteristic,分别用于数据通信时的数据发送和数据接收。
public static final UUID UUID_CHARIC_WRITE =
UUID.fromString("000057a8-0000-1000-8000-00805f9b34fb");
public static final UUID UUID_CHARIC_READ =
UUID.fromString("000057a9-0000-1000-8000-00805f9b34fb");
private BluetoothGattCharacteristic mWritableCharacter = null;
private BluetoothGattCharacteristic mReadableCharacter = null;
private boolean findAvailableCharic(BluetoothGatt gatt) {
List<BluetoothGattService> services = gatt.getServices();
for (BluetoothGattService service : services) {
String curServiceUUID = service.getUuid().toString();
for (UUID serviceUuid : UUIDUtil.AVAILABLE_UUIDS) {
if (serviceUuid.toString().equals(curServiceUUID)) {
mReadableCharacter = service.getCharacteristic(UUID_CHARIC_READ);
mWritableCharacter = service.getCharacteristic(UUID_CHARIC_WRITE);
if (mWritableCharacter != null && mReadableCharacter != null) {
return true;
}
}
}
}
return false;
}
来到这里,离我们发送数据的终极目的已经很近啦~
发送数据通过可写的 Characteristic 来完成。
把需要发送的数据通过 mWritableCharacter.setValue 保存起来,再通过 mBluetoothGatt.writeCharacteristic 进行发送。前面提到过,BLE数据通信每次只能传输不大于20byte的数据(一般我们会定义一个传输的协议,以充分利用好这个20个字节,这个等到分包发送数据时再讲解)。
这里直接传入一个字符串作为数据,方便程序的演示。
private BluetoothGatt mBluetoothGatt;
private BluetoothGattCharacteristic mWritableCharacter = null;
private void sendDataByCharacter(String value) {
mWritableCharacter.setValue(value);
mBluetoothGatt.writeCharacteristic(mWritableCharacter);
}
发送完成后,外围设备端的Android程序中 BluetoothGattServerCallback 接口的 onCharacteristicWriteRequest 函数会被回调,具体在 Android BLE开发指南(三)外围设备端程序开发详解 中说明。
接收数据通过可读的 Characteristic 来完成。
首先需要设置一个通知的监听,这一步很关键,不然外围设备端发送的数据将无法接收到。
mBluetoothGatt.setCharacteristicNotification(characteristic, true);
通知监听设置完成后,如果外围设备端有发送数据过来,BluetoothGattCallback 接口的 onCharacteristicChanged 函数会被回调,数据可以通过 characteristic.getValue 获取到。
private BluetoothGattCallback mBtGattCallback = new BluetoothGattCallback() {
......
@Override
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
LogUtil.d("receive data: "+ characteristic.getStringValue(0));
......
}
......
};
数据通信完毕后,如果需要断开连接,则调用 disconnect 即可。在连接断开完成后,会回调 onConnectionStateChange ,这时调用 close 关闭。
private BluetoothGatt mBluetoothGatt;
public void disconnectDevice() {
if (mBluetoothGatt != null) {
mBluetoothGatt.disconnect();
}
}
private BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
switch (newState) {
......
case BluetoothProfile.STATE_DISCONNECTED:
gatt.close();
break;
}
}
}
程序运行示例图如下,左边是中心设备端程序的界面,右边是外围设备端程序的界面。
中心设备发送 “central dev msg” 到外围设备,外围设备发送 ”peripheral dev msg“ 到中心设备,两个设备之间实现了数据的互相收发。
接下来,请开始你的表(撸)演(码)~
以上,希望对大家有帮助哈~