最近在工作中使用到蓝牙的功能,当然我们这个蓝牙时跟蓝牙芯片结合使用的,而不是手机跟手机连接通信的。其实本质时差不多的,只是设备不一样罢了。在这里我不会贴出蓝牙那些协议等等复杂的名词解释,因为这个不是一两句话能解释的清楚,在我们先不太了解蓝牙的这些专业名词之前,我们先掌握它的基本使用就可以了,后续如果想深入了解的话,我们再花时间去学习。本文介绍的低功耗的蓝牙,是Android 4.3才开始支持的,而要使用传统蓝牙和高版本的蓝牙请参照官方文档,有中文介绍哦。
官方蓝牙文档:https://developer.android.google.cn/guide/topics/connectivity/bluetooth.html
官方不同版本蓝牙示例(有3个):
https://github.com/googlesamples?utf8=✓&q=bluetooth&type=&language=
一、蓝牙的基本使用流程(草图)
从图上可以看出我这个例子只是单方面的通信,即手机只接收数据而不发送数据。
二、蓝牙关键类
BluetoothManager:蓝牙管理服务,如果对Android基本框架熟悉的话,你会发现蓝牙也属于最底层的驱动模块里,那么要使用蓝牙的东西就需要使用(BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE)来获取了。不过看是把图贴一下。
BluetoothAdapter:表示本地设备蓝牙适配器。 BluetoothAdapter允许您执行基本的蓝牙任务,例如启动设备发现,查询已绑定(配对)设备的列表,使用已知MAC地址实例化BluetoothDevice,并创建一个BluetoothServerSocket以监听来自其他设备的连接请求,并启动扫描蓝牙LE设备。这个类时关键类,后面会大量使用到它。
BluetoothAdapter.LeScanCallback:用于提供LE扫描结果的回调界面。这个类就是扫描的回调接口,不过再Android 5.0以上使用抽象类ScanCallback。
BluetoothLeScanner:该类提供了对蓝牙LE设备执行扫描相关操作的方法。应用程序可以使用ScanFilter扫描特定类型的蓝牙LE设备。它还可以请求不同类型的回调来传递结果。不过这个类是在Android 5.0(API21)以上才出现的,也就是对于Android 4.3的以上我们只会使用BluetoothAdapter来进行扫描等操作,当然如果是5.0以上的可以使用这个类来代替的。
ScanCallback:蓝牙LE扫描回调,使用这些回调报告扫描结果。这个是抽象类与BluetoothLeScanner配套使用。
BluetoothDevice:表示远程蓝牙设备,BluetoothDevice允许您创建与相应设备的连接或关于它的查询信息,例如名称,地址,类和绑定状态。
BluetoothProfile:配置文件代理。每个公共配置文件实现这个接口。它有几个直接子类,每个子类再不同场景中使用,如BluetoothA2dp, BluetoothGatt, BluetoothGattServer, BluetoothHeadset, BluetoothHealth。在当前例子中使用到的是BluetoothGatt。
BluetoothGatt:该类提供蓝牙GATT功能,以实现与蓝牙智能或智能就绪设备的通信。后续使用该类做连接、断开、关闭等操作。
BluetoothGattCallback:文档没有直接的解释,只说了被用在连接设备时候的回调。虽然文档没有详细说明,但是这个回调会在后续的连接、断开、通信中起到关键作用。
好了,主要的几个类介绍的差不多了。接下来我们使用这几个类练习一下。
1、获取蓝牙管理服务和适配器
bluetoothManager = (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE); bluetoothAdapter = bluetoothManager.getAdapter(); 获取了适配器后,我们可以通过适配器是否为null来判断是否支持蓝牙功能(想必现在的手机应该都支持吧)。另外适配器还提供了以下的方法:
/** * 启用蓝牙 */ public boolean enableBluetooth() { if (isSupportBluetooth()) { return bluetoothAdapter.enable(); } else { return false; } }
/** * 是否启用了 * * @return */ public boolean isEnabled() { if (isSupportBluetooth()) { return bluetoothAdapter.isEnabled(); } return false; } /** * 禁用蓝牙 */ public boolean disableBluetooth() { if (isSupportBluetooth()) { return bluetoothAdapter.disable(); } else { return false; } } /** * 扫描设备 */ public void scanLeDevice() { if (isSupportBluetooth() && isEnabled()) { setScanning(true); getBluetoothAdapter().startLeScan(leScanCallback); getHandler().postDelayed(new Runnable() { @Override public void run() { stopLeScan(); } }, getScanPeriod()); } } /** * 停止扫描 */ public void stopLeScan() { if (isSupportBluetooth() && isEnabled()) { setScanning(false); getBluetoothAdapter().stopLeScan(leScanCallback); } } 上面的就是启用、关闭、扫描、停止的几个方法。需要注意的是在启用蓝牙的时候在不同手机会弹出启用对话框,比如魅族。那么这个时候你需要通过isEnabled方法来判断是否启用了,如果没有启用则使用startActivity来启用,然后通过Activity或者Fragment的onActivityResult回调方法来做余下的操作了。
[java] view plain copy print ? startActivityForResult(new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE), REQUEST_ENABLE); 好了,使用上面的扫描方法就可以扫描到蓝牙设备了,是不是很简单啊。再说明一下,上面的leScanCallback就是上面提到的 BluetoothAdapter.LeScanCallback,你需要实现它就可以了。
2、权限配置
不过使用蓝牙也是要配置权限的,如果你没有在Manifest中配置,在Android Studio中使用上面的方法的时候它会报错要求你加入权限。我在这里列一下我使用到的权限:
<!-- 需要硬件支持低功耗蓝牙 --> <uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/> <!-- 蓝牙权限 --> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/> <uses-permission android:name="android.permission.BLUETOOTH"/> <!-- Android 5.0以上蓝牙还需要位置权限 --> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/> 是的,你会发现使用蓝牙还需要位置权限?我开始也觉得一个蓝牙也要位置权限,后来在翻看官方文档的时候才知道的,不然你在5.0以上的手机上扫不出蓝牙的!详情请翻阅官方文档: https://developer.android.google.cn/guide/topics/connectivity/bluetooth-le.html#user-permission 。
3、连接设备
有了蓝牙设备后接下来我们连接下,然后发发数据看看。连接设备其实也很简单,就是通过扫描到的蓝牙设备对象来连接即可。蓝牙设备就是上面提到的BluetoothDevice,这个类有一个方法:
[java] view plain copy print ? public BluetoothGatt connectGatt(Context context, boolean autoConnect, BluetoothGattCallback callback) { return (connectGatt(context, autoConnect,callback, TRANSPORT_AUTO)); } 第一个Context就不说了;第二个参数autoConnect的解释是“ 是否直接连接到远程设备(false)或一旦远程设备可用即可自动连接(true)”;第三个参数也是上面提到的抽象类,这个类在连接过程中起到关键作用。最后方法会返回一个BluetoothGatt对象,后续我们通过这个对象可以重连、断开、关闭设备。贴下我例子中的连接方法:
/** * 连接设备,如果服务未开启或者地址为空的话就返回false;如果地址存在是否连接成功取决与蓝牙底层 * * @param address * @return 是否连接到 */ @Override public boolean connectDevice(String address) { if (arshowBluetooth.getBluetoothAdapter() == null || address == null) { return false; } //如果之前有连接过就直接连接,重新连接 if (address.equals(mAddress) && mBluetoothGatt != null) { return mBluetoothGatt.connect(); } BluetoothDevice device = arshowBluetooth.getBluetoothAdapter().getRemoteDevice(address); if (device == null) { return false; } //false表示直接连接,true表示远程设备可用之后连接 mBluetoothGatt = device.connectGatt(context, false, mGattCallback); mAddress = address; return true; }
抽象类
/** * 连接、发现、通信回调 */ private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() { /** * 连接状态改变回调 * @param gatt * @param status * @param newState */ @Override public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { if (newState == BluetoothProfile.STATE_CONNECTED) { //阅读连接的远程设备的RSSI。 gatt.readRemoteRssi(); // 发现远程设备提供的服务及其特性和描述符 gatt.discoverServices(); } bluetoothCallback.connectionStateChange(gatt.getDevice(), newState); } /** * 当远程设备的远程服务列表,特征和描述符已被更新,即已发现新服务时,调用回调。表示可以与之通信了。 * @param gatt * @param status */ @Override public void onServicesDiscovered(BluetoothGatt gatt, int status) { if (status == BluetoothGatt.GATT_SUCCESS) { // 得到服务对象 BluetoothGattService service = gatt.getService(BluetoothConstant.UUID_SERVICE); if (service == null) { return; } // 得到此服务结点下Characteristic对象 final BluetoothGattCharacteristic gattCharacteristic = service .getCharacteristic(BluetoothConstant.UUID_CHARACTERISTIC); if (gattCharacteristic == null) { return; } gatt.setCharacteristicNotification(gattCharacteristic, true); BluetoothGattDescriptor descriptor = gattCharacteristic .getDescriptor(BluetoothConstant.UUID_DESCRIPTOR); descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE); gatt.writeDescriptor(descriptor); bluetoothCallback.serviceDiscoveryed(gatt.getDevice(), status); } else { LogUtils.w(getClass().getSimpleName(), "onServicesDiscovered received: " + status); } } /** * 由于远程特征通知而触发回调。 * @param gatt * @param characteristic */ @Override public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { try { bluetoothCallback.valueChanged(new String(characteristic.getValue(), "UTF-8")); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } } /** * 返回远程设备的信号强度,最大值理论值为0 * @param gatt * @param rssi * @param status */ @Override public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) { super.onReadRemoteRssi(gatt, rssi, status); } }; 这个类里面有很多方法,不过都被这个类实现了,我们根据自己的情况来重写。解释下上面列出的方法:onConnectionStateChange:监测蓝牙设备的连接、断开、读取设备信号强度值、发现服务(翻译的好别扭)。连接和断开可以通过newState参数来判断,注意是和BluetoothProfile的常量值来对比的,这个类也是上面提到过的。其实这个类提供了4个状态值,只不过在使用过程中只有连接(状态值2)和断开(状态值0),其他的没用到。
/** The profile is in disconnected state */ public static final int STATE_DISCONNECTED = 0; /** The profile is in connecting state */ public static final int STATE_CONNECTING = 1; /** The profile is in connected state */ public static final int STATE_CONNECTED = 2; /** The profile is in disconnecting state */ public static final int STATE_DISCONNECTING = 3; 至于后面的 读取设备信号强度值、发现服务这两个分别用在获取信号强度和通信的。怎么解释?要知道蓝牙也是无线传输的,既然是无线就跟移动网、WIFI一样都存在信号强弱的情况,那么通过这个就可以获取了。信号强度值是负数,负数越大信号越好(最大理论值是0),单位是dBm。而发现服务,我们在成功连接后调用gatt.discoverServices();方法既可以获取与连接的设备进行通信了,最后回调onServicesDiscovered方法。
onServicesDiscovered:这个方法里面我们需要做的就是使用蓝牙的UUID获取服务、特征、描述等对象,然后就可以给手机发送数据了。这里需要注意的是UUID不能随便写,而是设备厂商或者通用的UUID,否则你是无法使用的。说到这个UUID,其实我也比较迷惑的,很多文章在介绍UUID的时候都是一笔带过或者直接拿来用根本不解释这是干嘛的,而且也没找到一个比较全面介绍蓝牙协议等专业文档(谁找到了,麻烦给我留一个地址)。其实说到这,里面提到的特征、描述等对象,我也没详细去说请原谅。
onCharacteristicChanged:远程特性通知,回调触发。就是上面执行完后,只要连接的设备触发某些动作(比如我的蓝牙芯片按钮按下、抬起),这个方法就会被调用了。然后我们在这个方法中就可以获取远程设备发过来的信息。
好了,其实写到我这个例子基本的使用已经差不多了,剩下的无法就是蓝牙的断开、连接、关闭(释放资源)
/** * 断开设备 */ @Override public void disconnectDevice() { if (mBluetoothGatt != null) { mBluetoothGatt.disconnect(); } } @Override public void closeDevice() { if (mBluetoothGatt != null) { mBluetoothGatt.close(); } } 最后,贴下我的例子截图以及我使用的蓝牙芯片,是不是感觉高大上啊,哈哈~
好了,我这个蓝牙芯片在连接之前的LED指示灯是一闪一闪的,而连接成功后就会常亮的。上图的3个按钮也是对应芯片的按键的。不够在这里说明一下,由于为了保密,我这里不会放出与蓝牙芯片的例子,而是单独写了一个简单的例子,不过底层的东西是不变的。另外这个是单方面的通信交互,如何做到双向通信呢?由于本例子只需要单向通信,所以就没涉及到,不够也是上面的例子的范畴,大家可以自己去研究下。另外说明下官方提供的低功耗的例子在使用“发现服务”的那个回调方法存在问题(连接了接收不到数据),我这里跟官方的例子还是不一样的。
另外说明下,我的Android Studio版本是2.4 preview 3版本,对应的gradle版本是gradle-3.4.1-all,所有低版本的自行修改配置。
GitHub地址:https://github.com/Xanthuim/BluetoothSample
最后说明下Android系统的低功耗是在4.3版本才支持的,低版本只能使用传统蓝牙啊。麻烦你们还是先看下谷歌官方文档最好。谷歌蓝牙主要分3个版本4.3以前是传统蓝牙,从4.3开始支持低功耗蓝牙(BLE),从5.0(记得是)又有新的功能了,但是没去研究。