一:概述
这段时间做了蓝牙4.0的项目,就是一个蓝牙设备控制手机进行拍照。并且有很多按键,不同的按键对应到手机上有不同的功能,并且组合起来也有不同的功能。
低功耗蓝牙有中央设备后周边设备的概念手机就是一个中央设备,像我这次试用的一个控制器,
我试过小米体重秤。来测试玩。
a.GATT 这是蓝牙技术联盟定义的一个协议。
b.Service 这个是许多或者一个特征值的集合。
c.Characteristic 这是特征值。我们需要使用的数据就是这个。
我们可以读取或者在其值在变化的时候会接收到其变化的回调。
d.Descriptor 这个是特征值的描述。最开始我有点不太懂这个东西到底哪里能用到。结果在设置
Characteristic 为可以通知的时候才看到,就是辅助Characteristic 的。这个谷歌官方的解释
可以看看我做的一个小demo的效果(一个控制器按键后):
二:源码解析
现在就通过源码讲解下我这个小demo:
因为这个是需要蓝牙权限,并且在android 系统4.3后才支持的接口。所以在使用蓝牙之前都需要申请权限和判断是否可以使用.
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
//这里的true标示手机如果不支持低功耗蓝牙就直接不让安装,如果你不想这样的话,可以写false
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>
这里是检查是否支持低功耗蓝牙。
// Use this check to determine whether BLE is supported on the device. Then
// you can selectively disable BLE-related features.
if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
Toast.makeText(this,"不支持蓝牙4.0", Toast.LENGTH_SHORT).show();
finish();
}
获取mBluetoothAdapter 需要用他进行判断蓝牙的开关,和搜索蓝牙设备。
// Initializes Bluetooth adapter.
final BluetoothManager bluetoothManager =
(BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter = bluetoothManager.getAdapter();
if(mBluetoothAdapter == null){
Toast.makeText(this,"获取失败!", Toast.LENGTH_SHORT).show();
finish();
}
猜这个是干嘛的,居然猜到是判断是否开蓝牙了。哟西。
如果没有开蓝牙当然就让用户开了。会有一个弹窗出来让用户选择是开还是关。
if (!mBluetoothAdapter.isEnabled()) {
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
}
如果用户选择不开的话,就直接结束当前的activity。
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
// User chose not to enable Bluetooth.
if (requestCode == REQUEST_ENABLE_BT && resultCode == Activity.RESULT_CANCELED) {
finish();
return;
}
super.onActivityResult(requestCode, resultCode, data);
}
好了。蓝牙也开了。那么开始搜索蓝牙了。mLeScanCallback是一个回调,当搜索到一个蓝牙的时候就回调他的接口
private void scanLeDevice(final boolean enable) {
if (enable) {
// Stops scanning after a pre-defined scan period.
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
mScanning = false;
mBluetoothAdapter.stopLeScan(mLeScanCallback);
}
}, SCAN_PERIOD);
mScanning = true;
mBluetoothAdapter.startLeScan(mLeScanCallback);
} else {
mScanning = false;
mBluetoothAdapter.stopLeScan(mLeScanCallback);
}
}
喏,回调就长这个样子。还行吧。
BluetoothDevice 重写了equals的方法用设备的address作为作为判断的
所以 listDevice.contains(device) 是可以判断的。好吧。这是java的基础知识。还是唠叨两句。
private BluetoothAdapter.LeScanCallback mLeScanCallback =
new BluetoothAdapter.LeScanCallback() {
@Override
public void onLeScan(final BluetoothDevice device, int rssi,
byte[] scanRecord) {
runOnUiThread(new Runnable() {
@Override
public void run() {
if(!listDevice.contains(device)){
//不重复添加
listDevice.add(device);
deviceAdapter.setListDevice(listDevice);
}
}
});
}
};
我们找到了蓝牙之后干嘛呢?你觉得还能干嘛呢?肯定就是开始玩dota啦(想的美)。
好了我们准备开始连接蓝牙。
/*mBluetoothLeService是一个android上的后台服务,不是低功耗蓝牙中的服务啊。这个service是从谷歌demo中拷贝的,改动了部分。*/
mBluetoothLeService.connect(device.getAddress());
这个connect中具体的实现,我看见了mBluetoothGatt,就是那个破协议。
然后还看见mGattCallback一个回调,想想应该是连接成功后的一些回调。
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) {
if (mBluetoothGatt.connect()) {
mConnectionState = STATE_CONNECTING;
return true;
} else {
return false;
}
}
final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address);
if (device == null) {
Log.w(TAG, "Device not found. Unable to connect.");
return false;
}
// We want to directly connect to the device, so we are setting the autoConnect
// parameter to false.
mBluetoothGatt = device.connectGatt(this, false, mGattCallback);
Log.d(TAG, "Trying to create a new connection.");
mBluetoothDeviceAddress = address;
mConnectionState = STATE_CONNECTING;
return true;
}
下面的就是连接成功后具体的回调。各部分干什么的在注释中可以看看。
broadcastUpdate(…)是发送广播。
private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
String intentAction;
if (newState == BluetoothProfile.STATE_CONNECTED) {
//蓝牙连接成功后
broadcastUpdate(intentAction);
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
//蓝牙断开连接
broadcastUpdate(intentAction);
}
}
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
//这个就是发现了蓝牙4.0的服务
broadcastUpdate(ACTION_GATT_SERVICES_DISCOVERED);
} else {
Log.w(TAG, "onServicesDiscovered received: " + status);
}
}
@Override
public void onCharacteristicRead(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic,
int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
//这个是读取Characteristic,通俗点就是读取我们想要的数据
broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);
}
}
@Override
public void onCharacteristicChanged(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic) {
//这个是我们监听的某个Characteristic变化了。然后发送一个广播
broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);
}
};
再看看我们接收广播的时候做了什么 这个广播接收的部分代码。因为代码贴的太多感觉有点不爽了。
else if (BluetoothLeService.
ACTION_GATT_SERVICES_DISCOVERED.equals(action)) {
//services被发现的状态
//在这里找到要要读的特征值或者需要通知的特征值
LogServiceAndChara(mBluetoothLeService.getSupportedGattServices());
}
我们在看看LogServiceAndChara()函数到底干了啥。(这个函数有点乱。T_T,就贴出部分代码吧)
首先我们遍历所有服务然后找我们想要的特殊服务(-_-我啥都不知道。)这个特殊服务怎么找?
用uuid,找到了我们想找的服务后,然互就开始找特征值,也是用uuid识别。
private void LogServiceAndChara(List<BluetoothGattService> gattServices){
// Loops through available GATT Services.
for (BluetoothGattService gattService : gattServices) {
if(gattService.getUuid().toString().equals(SampleGattAttributes.DEVICE_SERVICE)){
//找到了这个服务
List<BluetoothGattCharacteristic> gattCharacteristics =
gattService.getCharacteristics();
for(BluetoothGattCharacteristic gattCharacteristic : gattCharacteristics){ if(gattCharacteristic.getUuid().toString().equals(SampleGattAttributes.DEVICE_CHARACTER)){
//找到了对应的服务
if((gattCharacteristic.getProperties() | BluetoothGattCharacteristic.PROPERTY_READ)>0){
if(mNotifyCharacteristic != null){
mBluetoothLeService.setCharacteristicNotification(gattCharacteristic,false);
mNotifyCharacteristic = null;
}
mBluetoothLeService.readCharacteristic(gattCharacteristic);
}
if((gattCharacteristic.getProperties() | BluetoothGattCharacteristic.PROPERTY_NOTIFY)>0){
mBluetoothLeService.setCharacteristicNotification(gattCharacteristic,true);
mNotifyCharacteristic = gattCharacteristic;
}
}
}
}
}
}
这两个分别是特征值和服务的uuid
public static String DEVICE_CHARACTER = "0000dfb1-0000-1000-8000-00805f9b34fb";
public static String DEVICE_SERVICE = "0000dfb0-0000-1000-8000-00805f9b34fb";
找到了我们想要的特征值后我们就要监听他了,或者读取他,我这里是用的监听。
这里有个点我纠结了挺久的地方。
BluetoothGattDescriptor descriptor = characteristic.getDescriptor(
UUID.fromString(SampleGattAttributes.CLIENT_CHARACTERISTIC_CONFIG));
我打印了所有特征值的descriptor的uuid都是一个样,我觉得应该不是硬件上自定义的。应该是蓝牙技术联盟定义的公用的。
然后去蓝牙技术联盟的官网查了下。发现了这个。蓝牙技术联盟CLIENT_CHARACTERISTIC_CONFIG解释
确定了就是他们定义公用的,然后用
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
设置这个特征值为可以为监听状态(就是可以变化的时我能收到通知)
public void setCharacteristicNotification(BluetoothGattCharacteristic characteristic,
boolean enabled) {
if (mBluetoothAdapter == null || mBluetoothGatt == null) {
Log.w(TAG, "BluetoothAdapter not initialized");
return;
}
mBluetoothGatt.setCharacteristicNotification(characteristic, enabled);
//关于CLIENT_CHARACTERISTIC_CONFIG这个descriptor的解释
BluetoothGattDescriptor descriptor = characteristic.getDescriptor(
UUID.fromString(SampleGattAttributes.CLIENT_CHARACTERISTIC_CONFIG));
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
mBluetoothGatt.writeDescriptor(descriptor);
}
在接收到变化或读取到后都能在这里接收到(这个是来自广播中的方法)
else if (BluetoothLeService.ACTION_DATA_AVAILABLE.equals(action)) {
//接收到数据的状态
str += intent.getStringExtra(BluetoothLeService.EXTRA_DATA);
str += "\n\r";
tv.setText(str);
}
加个好友共同学习(不是公众号):
因为小弟水平有限,如果有写的有问题,希望指出。