蓝牙4.0集成了传统蓝牙和低功耗蓝牙两个标准,所以蓝牙4.0有双模和单模之分。双模即传统蓝牙部分+低功耗蓝牙部分,单模即是单纯的低功耗蓝牙部分(BLE)。
蓝牙开发之前需要在 AndroidManifest.xml
中申请蓝牙相关权限
<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" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
蓝牙操作基本可以分为五步:
bluetoothAdapter.isEnabled()
,true 蓝牙已打开;false 蓝牙未打开,需要跳转到设置页面打开蓝牙。跳转蓝牙设置页面,打开蓝牙
Intent intent = new Intent(Settings.ACTION_BLUETOOTH_SETTINGS);
startActivity(intent);
蓝牙打开后,我们首先需要获取蓝牙适配器 bluetoothAdapter,获取蓝牙适配器后,就可以进行蓝牙扫描操作。
public void openBle() {
if (null != mBtAdapter) {
return;
}
BluetoothManager manager = (BluetoothManager) mContext.getSystemService(BLUETOOTH_SERVICE);
if (null != manager) {
mBtAdapter = manager.getAdapter();
}
if (null == mBtAdapter) {
mBtAdapter = BluetoothAdapter.getDefaultAdapter();
}
//设备不支持蓝牙功能
if (mBtAdapter == null) {
Toast.makeText(mContext, "设备不支持蓝牙功能!", Toast.LENGTH_LONG).show();
}
}
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
mBtAdapter.startLeScan(mLeScanCallback);
} else {
//需要 sdk 版本在 21(Android 5.0) 以上才可以定义使用
mBtAdapter.getBluetoothLeScanner().startScan(mScanCallback);
}
//bluetoothAdapter.startDiscovery(); //该方法不建议使用
蓝牙扫描开始后,就会有设备进行返回,返回的数据会出现重复,需要我们根据自身需求进行去重操作,并显示在列表中。
扫描结果有三种:
⚠️ 需要调用bluetoothAdapter.startDiscovery()
方法进行扫描,并且注册 ACTION_FOUND 广播。
private BluetoothAdapter.LeScanCallback mLeScanCallback =
new BluetoothAdapter.LeScanCallback() {
@Override
public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {
//蓝牙扫描回调
mCallBackResult.onScanResult(scanResult);
}
}
};
⚠️ 需要使用 mBtAdapter.startLeScan(mLeScanCallback);
进行扫描
mScanCallback = new ScanCallback() {
@Override
public void onScanResult(int callbackType, android.bluetooth.le.ScanResult result) {
super.onScanResult(callbackType, result);
scanType = SCAN_RESULT_TYPE_TWO;
// sdk 版本在 21(Android 5.0) 以上扫描结果处理
mCallBackResult.onScanResult(scanResult);
}
@Override
public void onBatchScanResults(List results) {
super.onBatchScanResults(results);
}
@Override
public void onScanFailed(int errorCode) {
super.onScanFailed(errorCode);
}
};
⚠️ 需要使用mBtAdapter.getBluetoothLeScanner().startScan(mScanCallback);
进行蓝牙扫描
扫描到我们所需要的设备后,下一步就要进行设备连接。连接设备可以对扫描到的设备 BluetoothDevice 进行连接,也可以根据 macAddress 进行连接(其实也是根据macAddress 找到设备 BluetoothDevice 进行连接)
public boolean connect(final String address) {
if (mBluetoothAdapter == null || address == null) {
Log.w(TAG, "BluetoothAdapter not initialized or unspecified address.");
return false;
}
if (mBluetoothDeviceAddress != null && address.equals(mBluetoothDeviceAddress) && mBluetoothGatt != null) {
Log.d(TAG, "Trying to use an existing mBluetoothGatt for connection.");
mBluetoothGatt.disconnect();
mBluetoothGatt.close();
}
final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address);
if (device == null) {
Log.w(TAG, "Device not found. Unable to connect.");
return false;
}
//设备连接
mBluetoothGatt = device.connectGatt(this, false, mGattCallback);
mBluetoothDeviceAddress = address;
return true;
}
以上的代码中提到了 BluetoothGatt 和 BluetoothGattCallback。
向蓝牙写入命令
向蓝牙写入命令,特征值必须有“写”的特性,才允许向蓝牙发送控制指令。
public void writeRXCharacteristic(byte[] value) {
Log.w("---Write","byte[] is "+ Arrays.toString(value)+ "\nHex is "+BleUtils.bytes2HexString(value));
if(mBluetoothGatt == null)
{
System.out.println("=================================================================");
}
// 根据 UUID 获取蓝牙服务
BluetoothGattService RxService = mBluetoothGatt.getService(RX_SERVICE_UUID);
if (RxService == null) {
showMessage("Rx service not found!");
return;
}
// 根据 UUID 获取“写”的特征值
BluetoothGattCharacteristic RxChar = RxService.getCharacteristic(RX_CHAR_UUID);
if (RxChar == null) {
showMessage("Rx charateristic not found!");
return;
}
//给特征值 赋值(向蓝牙传递的命令,十六进制转 byte 数组)
RxChar.setValue(value);
//蓝牙写操作
boolean status = mBluetoothGatt.writeCharacteristic(RxChar);
}
注册“读”特性特征值
只能向有“读”特性的特征值注册读 操作
public void readCharacteristic(BluetoothGattCharacteristic characteristic) {
if ((characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_READ) !=0) {
if (mBluetoothAdapter == null || mBluetoothGatt == null) {
Log.w(TAG, "BluetoothAdapter not initialized");
return;
}
mBluetoothGatt.readCharacteristic(characteristic);
}
}
断开蓝牙连接
public void disconnect() {
if (mBluetoothAdapter == null || mBluetoothGatt == null) {
Log.w(TAG, "BluetoothAdapter not initialized");
return;
}
mBluetoothGatt.disconnect();
}
关闭蓝牙操作
public void close() {
if (mBluetoothGatt == null) {
return;
}
Log.w(TAG, "mBluetoothGatt closed");
mBluetoothDeviceAddress = null;
mBluetoothGatt.close();
mBluetoothGatt = null;
}
根据扫描方式的不同,停止扫描的方法也是不同:
```java
/**
* 停止扫描
*/
public void stopScan() {
switch (scanType) {
case SCAN_RESULT_TYPE_ONE:
mBtAdapter.stopLeScan(mLeScanCallback);//Android 5.0 以下(sdk 21以下)
break;
case SCAN_RESULT_TYPE_TWO:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
//Android 5.0 及以上(sdk 21以上)
mBtAdapter.getBluetoothLeScanner().stopScan(mScanCallback);
}
break;
case SCAN_RESULT_TYPE_THREE:
//使用 bluetoothAdapter.startDiscovery() 方法扫描设备
mBtAdapter.cancelDiscovery();
break;
default:
break;
}
}
```
onConnectionStateChange(BluetoothGatt gatt, int status, int newState)
设备连接状态回调。当 newState == BluetoothGatt.STATE_CONNECTED
时代表设备了解成功,可以进行服务扫描了 mBluetoothGatt.discoverServices()
。
当 newState == BluetoothGatt.STATE_DISCONNECTED
时,代表设备断开连接。
onServicesDiscovered(BluetoothGatt gatt, int status)、
发现蓝牙服务回调。可以进行特征值筛选,通道注册等操作。其中可以根据特征值的特性进行特征值注册和筛选。例如:BluetoothGattCharacteristic 的property值 等于 0x02 则只有“读”的特性;等于 0x04 为“写”的特性,但是不回调;等于 0x08 是“写”的特性,能够回调等。(详情参考BluetoothGattCharacteristic 类)
以下是注册读取通道的方法:
public void enableTXNotification()
{
if (mBluetoothGatt == null) {
showMessage("mBluetoothGatt null" + mBluetoothGatt);
return;
}
//根据 UUID 获取蓝牙服务
BluetoothGattService RxService = mBluetoothGatt.getService(RX_SERVICE_UUID);
if (RxService == null) {
showMessage("Rx service not found!");
return;
}
//根据 UUID 获取“读”特征值
BluetoothGattCharacteristic TxChar = RxService.getCharacteristic(TX_CHAR_UUID);
if (TxChar == null) {
showMessage("Tx charateristic not found!");
return;
}
//注册通知
mBluetoothGatt.setCharacteristicNotification(TxChar,true);
// 对特征值进行描述
BluetoothGattDescriptor descriptor = TxChar.getDescriptor(CCCD);
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
mBluetoothGatt.writeDescriptor(descriptor);
}
在蓝牙开发过程中,主要是弄清蓝牙协议,每个蓝牙设备的协议是不一样的,握手方式也是各不相同。对于向蓝牙发送命令要格外注意,稍有不慎命令编码就会出错。对于蓝牙返回的数据,也是多种多样,我们要分别处理。最后,由于蓝牙开发很多都是异步操作,为了节省Activity的开销,最好蓝牙相关操作放到service内进行。