关键字: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) { Listservices = 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现象相同,后来更换手机后问题解决。