关键字:android、ble、蓝牙、gatt
时间:2019年06月
前言
阅读本文前,需要熟悉Activity的开发,例如:onCreate()、onResume()等。
基本概念
经典蓝牙
通迅前需要手机在设置中填写密钥配对
通过建立socket连接发送数据
BLE
4.0以上版本均为BLE
BLE以Characteristic作为数据传输通道获取数据
每个蓝牙设备可提供多个service
每个service由多个Characteristic组成
每个Characteristic又有多个Descriptor
BLE开发的关键点
通过manager获取adapter,BluetoothManager.getAdapter()
通过adapter获取scannser,BluetoothAdapter.getBluetoothLeScanner()
启动scanner产生回调,BluetoothLeScanner.startScan( ScanCallback )
通过回调函数中的result获取device,ScanCallback.onScanResult( ScanResult ) { ScanResult.getDevice() }
通过device获取gatt,BluetoothDevice.connectGatt( BluetoothGattCallback )
连接成功的回调中启动discoverServices, BluetoothGattCallback.onConnectionStateChange( BluetoothGatt ){ BluetoothGatt.discoverServices() }
发现服务回调中获取service,BluetoothGattCallback.onServicesDiscovered( BluetoothGatt ){ gatt.getServices() }
通过service获取characteristic,BluetoothGattService.getCharacteristics()
修改characteristic获取数据方式 BluetoothGatt.setCharacteristicNotification( characteristic, true | false )
发送读characteristic数据请求,BluetoothGatt.readCharacteristic( characteristic )
在读到数据的回调中获取数据,BluetoothGattCallback.onCharacteristicRead( characteristic ) { characteristic.getValue() }
manifests设置权限
manifests文件中,需要增加蓝牙权限和定位权限。蓝牙权限需要解释,定位权限如果没有,会扫描不到任何蓝牙设备。
<manifest xmlns:...
...
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
...
</manifests>
获取定位授权
在高版本Android系统中,定位需要用户授权才能使用。如果用户未授权,仍然扫描不到任何蓝牙设备。获取用户授权有两种方法。
方法一
在Android设置中找到定位服务,找到App,允许获取位置。
方法二
在程序中弹出对话框,提示用户是否允许。
protected void onResume() {
...
if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.ACCESS_COARSE_LOCATION)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(MainActivity.this,
new String[]{Manifest.permission.ACCESS_COARSE_LOCATION},
1);
}
...
}
检查手机是否支持蓝牙
protected void onCreate(Bundle savedInstanceState) {
...
if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
Toast.makeText(this, "不支持BLE", Toast.LENGTH_SHORT).show();
finish();
}
...
}
获取BluetoothManager实例
protected void onCreate(Bundle savedInstanceState) {
...
final BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
...
通过BluetoothManager实例获取BluetoothAdapter实例
BluetoothAdapter变量定义为成员变量,便于其他地方使用。
...
private BluetoothAdapter mBluetoothAdapter;
...
protected void onCreate(Bundle savedInstanceState) {
...
mBluetoothAdapter = bluetoothManager.getAdapter();
if (mBluetoothAdapter == null) {
Toast.makeText(this, "不支持蓝牙", Toast.LENGTH_SHORT).show();
finish();
}
...
}
判断蓝牙是否开启
检查蓝牙是否打开,没打开则弹框提示用户打开。
protected void onResume() {
...
if (!mBluetoothAdapter.isEnabled()) {
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
}
...
}
扫描附近的蓝牙设备
扫描需要用到BluetoothLeScanner类,该类实例通过mBluetoothAdapter.getBluetoothLeScanner()获取。
部分文章提到使用LeScanCallback类和startLeScan()方法扫描,该方法已经弃用,不建议使用。
BluetoothLeScanner scanner = mBluetoothAdapter.getBluetoothLeScanner();
scanner.startScan(new ScanCallback() {
@Override
public void onScanResult(int callbackType, ScanResult result) {
super.onScanResult(callbackType, result);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
BluetoothDevice device = result.getDevice();
if (!mDevices.contains(device)) {
mDevices.add(device);
// mAdapter是和界面上ListView绑定的ArrayAdapter,具体看完整代码。
mAdapter.add(device.getAddress());
mAdapter.notifyDataSetChanged();
}
}
}
小结
通过上面几步已经能够扫描到周围的蓝牙设备了,还可以通过ScanResult的getRssi()方法获取设备的信号强度,以及使用BluetoothDevice的getName()方法获取设备名称,但getName()有可能为null,使用时请注意。getName()为空非常不便于用户辨识设备,个人感觉是否为空可能与信号强度有关系,可以改变距离进行尝试。
扫描获取到device对象已经可以进行连接了,但device对象无法序列化,传递很不方便,可以使用device.getAddress()代替device进行参数传递。
其他Activity获取device对象
地址可以作为设备唯一标识进行参数传递,再还原成BluetoothDevice对象使用。
BluetoothManager manager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
BluetoothAdapter adapter = manager.getAdapter();
BluetoothDevice device = adapter.getRemoteDevice("43:41:3C:F2:85:EE");
获取BluetoothGatt并进行连接
BluetoothDevice device = adapter.getRemoteDevice("43:41:3C:F2:85:EE");
mBluetoothGatt = device.connectGatt(this, false, mGattCallback); // mGattCallback说明见下文
回调函数
private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
Log.d(TAG, "onConnectionStateChange()");
if (newState == BluetoothProfile.STATE_CONNECTED) {
Log.i(TAG, "Connected to GATT server.");
Log.i(TAG, "Attempting to start service discovery:" +
mBluetoothGatt.discoverServices());
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
Log.i(TAG, "Disconnected from GATT server.");
}
}
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
List services = gatt.getServices();
for (BluetoothGattService service : services) {
Log.d(TAG, "service: " + service.getUuid().toString());
for (BluetoothGattCharacteristic characteristic : service.getCharacteristics()) {
Log.d(TAG, "characteristic: " + characteristic.getUuid().toString());
if (characteristic.getUuid().toString().equals("00002a29-0000-1000-8000-00805f9b34fb")) {
if ((characteristic.getProperties() | BluetoothGattCharacteristic.PROPERTY_READ)>0) {
Log.d(TAG, "PROPERTY_READ");
mBluetoothGatt.setCharacteristicNotification(characteristic, false);
mBluetoothGatt.readCharacteristic(characteristic);
}
if ((characteristic.getProperties() | BluetoothGattCharacteristic.PROPERTY_NOTIFY)>0) {
Log.d(TAG, "PROPERTY_NOTIFY");
mBluetoothGatt.setCharacteristicNotification(characteristic, true);
}
}
for (BluetoothGattDescriptor descriptor : characteristic.getDescriptors()) {
Log.d(TAG, "descriptor: " + descriptor.getUuid().toString());
}
}
}
} else {
Log.w(TAG, "onServicesDiscovered received: " + status);
}
}
@Override
public void onCharacteristicRead(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic,
int status) {
Log.d(TAG, "onCharacteristicRead()");
if (status == BluetoothGatt.GATT_SUCCESS) {
Log.d(TAG, characteristic.getUuid().toString() + " " + new String(characteristic.getValue()));
}
}
@Override
public void onCharacteristicChanged(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic) {
Log.d(TAG, "onCharacteristicChanged()");
}
};
结束语
在研究Android的BLE使用时,第一个手机蓝牙硬件存在问题,导致代码未按预期执行,安装其他蓝牙调试App现象相同,后来更换手机后问题解决。