蓝牙 4.0
蓝牙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" />
<!-- Android 6.0 需要申请位置权限 -->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
蓝牙操作基本可以分为五步:
打开蓝牙
扫描设备
连接设备
控制设备
接收数据
Android BLE 相关功能及 API
说明
服务:每个蓝牙设备,都有多个服务,每个服务都有不同的作用,我们可以根据蓝牙协议提供的 service uuid 找出 相应的service。
特征值:蓝牙数据传输的载体。每个服务里包含多个特征值,每个特征值都有自己的特性(读、写或通知等)。
1、判断蓝牙是否打开
bluetoothAdapter.isEnabled(),true 蓝牙已打开;false 蓝牙未打开,需要跳转到设置页面打开蓝牙。
跳转蓝牙设置页面,打开蓝牙
Intent intent = new Intent(Settings.ACTION_BLUETOOTH_SETTINGS);
startActivity(intent);
2、获取 bluetoothAdapter 并扫描蓝牙
蓝牙打开后,我们首先需要获取蓝牙适配器 bluetoothAdapter,获取蓝牙适配器后,就可以进行蓝牙扫描操作。
获取 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();
}
}
蓝牙扫描
在 Android 6.0 以后扫描蓝牙需要有位置权限,否则扫描不到附近的设备。
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
mBtAdapter.startLeScan(mLeScanCallback);
} else {
//需要 sdk 版本在 21(Android 5.0) 以上才可以定义使用
mBtAdapter.getBluetoothLeScanner().startScan(mScanCallback);
}
//bluetoothAdapter.startDiscovery(); //该方法不建议使用
3、扫描 BLE 返回
蓝牙扫描开始后,就会有设备进行返回,返回的数据会出现重复,需要我们根据自身需求进行去重操作,并显示在列表中。
扫描结果有三种:
广播接收扫描结果(BroadcastReceiver)
⚠️ 需要调用bluetoothAdapter.startDiscovery() 方法进行扫描,并且注册 ACTION_FOUND 广播。
Android 5.0 以下API
private BluetoothAdapter.LeScanCallback mLeScanCallback =
new BluetoothAdapter.LeScanCallback() {
@Override
public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {
//蓝牙扫描回调
mCallBackResult.onScanResult(scanResult);
}
}
};
⚠️ 需要使用 mBtAdapter.startLeScan(mLeScanCallback);进行扫描
Android 5.0 及以上( sdk 为 21 以上)
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<android.bluetooth.le.ScanResult> results) {
super.onBatchScanResults(results);
}
@Override
public void onScanFailed(int errorCode) {
super.onScanFailed(errorCode);
}
};
⚠️ 需要使用mBtAdapter.getBluetoothLeScanner().startScan(mScanCallback); 进行蓝牙扫描
4、设备连接
扫描到我们所需要的设备后,下一步就要进行设备连接。连接设备可以对扫描到的设备 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。
BluetoothGatt:它是操作 BLE 的主要类,蓝牙的连接、断开、发现蓝牙服务、注册通道、发送命令等都会用到它。
BluetoothGattCallback 是关于蓝牙操作的回调,里面包含了连接是否成功回调、服务发现回调、蓝牙返回数据回调、写命令成功回调等十二个回调方法。
5、蓝牙操作
向蓝牙写入命令
向蓝牙写入命令,特征值必须有“写”的特性,才允许向蓝牙发送控制指令。
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);
}
}
6、断开连接和关闭蓝牙
断开蓝牙连接
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;
}
7、停止扫描
根据扫描方式的不同,停止扫描的方法也是不同:
/**
* 停止扫描
*/
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;
}
}
8、BluetoothGattCallback 回调方法
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);
}
onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic)
蓝牙设备数据返回回调
onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status)
读操作的回调。一般情况下不会使用该方法
onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status)
写操作回调
总结
在蓝牙开发过程中,主要是弄清蓝牙协议,每个蓝牙设备的协议是不一样的,握手方式也是各不相同。对于向蓝牙发送命令要格外注意,稍有不慎命令编码就会出错。对于蓝牙返回的数据,也是多种多样,我们要分别处理。最后,由于蓝牙开发很多都是异步操作,为了节省Activity的开销,最好蓝牙相关操作放到service内进行。