废话不多说,因为工作关系,这次接触到蓝牙功能开发,下面是我第一次蓝牙开发的经验总结,对于将要从事蓝牙开发的人,或许有些帮助。
Android蓝牙分为传统蓝牙开发和ble蓝牙开发。下面就有小伙伴疑问了,我该学习哪个呢,其实不然,只要你学会其中一种开发模式,对于另外一种开发模式无师自通。还有,Android蓝牙开发方式就是固定的模式,跟与什么设备进行连接没有任何关系,切记!代码就是网上你看到的样子,你慢慢调试,能跟任何具有蓝牙模块硬件的设备通信,比如手机,电脑,携带式设备,单片机等等
先讲讲传统蓝牙,传统蓝牙开发按照网上的文档,一步一步照搬就行。就是读API官方文档一开始比较吃力,理解不深。网上文档大多有个缺点,没有说明demo如何测试。这个我必须说清出,找两个Android手机就行。把demo安装在两个手机上蓝牙互联,互相发送信息,就是这么简单。下面会具体说说怎么做。
知识点,我就不说了。如何判断手机是否支持蓝牙,如何判断是否开启权限,蓝牙,定位啥的。一直到蓝牙配对这,相信大家阅读起来毫无难度!
接下来,我只说重点地方,连接以及如何测试:
1. 是自己手机的蓝牙可被别的手机搜索到 ,mBluetoothAdapter不能为空,也就是你的手机有蓝牙模块。然后,你就可以简单的把下面代码放在onResume()里就行,当然了,每次弹窗提示是很烦人的,但是为了测试方便,我们先这样用。
/** * 使蓝牙可被搜索到 */ private void enableBluetooth() { //不在可被搜索的范围 if (mBluetoothAdapter.getScanMode() != BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) { Intent discoverableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE); //值为 0 则表示设备始终可检测到 discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 0); startActivity(discoverableIntent); } }
2. 服务端和客户端,读起来很唬人。下面我就跟新手说清楚这件事。蓝牙,比如我的手机,可以通过蓝牙主动连接别人手机,当然,别人也可以使用自己的手机连接我的手机。发起主动连接的叫做客户端,别动接受别人连接的就叫做服务端。简单的demo测试开发,就可以这么做。一个手机安装只有客户端代码的app(手机A),另一个手机安装只有服务端代码的app(手机B),测试过程:就只能由A向B发起连接,连接成功后,使用BluetoothSocket 的 getOutputStream() 和getInputStream() 发送和接收消息就行,此时手机A与手机B,共同持有一个socket通道,手机A向手机B发,手机B向手机A发,都可以。那大部分情况是,你的手机既可以主动连接别人设备,别人设备也有可能主动连接你。所以啊,每一个app,基本上都要实现客户端和服务端机制的代码。现在大家明白了吧。服务端和客户端机制说白了,就是你的app是否可以连接别的设备,或者被别的设备连接,至于连接成功后,消息收发,那就简单多了。就按照开发API的来。
3.一定要写在线程内。当mBluetoothAdapter不为空时,当所有条件都满足时,如蓝牙已打开,蓝牙可被检索,蓝牙权限以获取,我们接下来就要写 :
mBluetoothSocket = getBluetoothServerSocket().accept(); //随时做好准备,等待其他设备的蓝牙连接请求-这就是服务端机制
if (BltManager.getInstance().getmBluetoothAdapter() != null) {
bluetoothServerSocket = BltManager.getInstance().getmBluetoothAdapter().listenUsingRfcommWithServiceRecord(BltConstant.YQYB, BltConstant.SPP_UUID);
}
mBluetoothSocket 不为空,用于收发信息;getBluetoothServerSocket()里面实现的内容就是listenUsingRfcommWithServiceRecord
SPP_UUID,我就不说了吧,两端保持一致就好,可以使那个串口通用的ID
/** * 蓝牙UUID */ BltConstant.YQYB 是我App的包名,其实你如果没有用到该值,一开始,你随便使用个字符串就行,不影响你测试的。 public static UUID SPP_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
--------------------------------------------------------------------------------------------------------------------------------------------------------------------
listview.setOnItemClickListener(new AdapterView.OnItemClickListener() {
BluetoothDevice curBluetoothDevice = mBluetoothAdapter.getRemoteDevice(myBluBean.getAddress()); connect(device);
}
// 客户端发起连接请求
private void connect(BluetoothDevice bluetoothDevice) { try { mBluetoothSocket = bluetoothDevice.createRfcommSocketToServiceRecord(BltConstant.SPP_UUID); if (mBluetoothSocket != null) { MyApplication.bluetoothSocket = mBluetoothSocket; if (bluetoothAdapter.isDiscovering()) { bluetoothAdapter.cancelDiscovery(); } if (!mBluetoothSocket.isConnected()) { mBluetoothSocket.connect(); //咱们发起来接吧,我都等不及了,哈哈哈! } EventBus.getDefault().post(new BluRxBean(connectsuccess, bluetoothDevice)); } } catch (IOException e) { e.printStackTrace(); try { mBluetoothSocket.close(); } catch (IOException e1) { e1.printStackTrace(); } } }
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------
那接下来,我们可以试试看,下面代码,用之前判断mBluetoothSocket是否为空,界面随便写两个textview,一个发送,一个展示接收的数据。就是没有配对,没有建立连接,也不影响我们先写这段定式代码,你们说呢。
InputStream inputStream = mBluetoothSocket.getInputStream(); // 接收数据 BufferedReader bff = new BufferedReader(new InputStreamReader(inputStream));
或者
OutputStream outputStream = mBluetoothSocket.getOutputStream(); outputStream.write(message.getBytes("utf-8")); outputStream.flush();// 发送数据
4. 展示已连接属性
好多小伙伴,或者网上好多文档,我看了下,写错的不少。在此,我必须给大家说清楚。
if (temp.getLinkState() == 0) { switch (temp.getBondState()) { case BluetoothDevice.BOND_NONE: mHolder.tvState.setText("未配对"); break; case BluetoothDevice.BOND_BONDING: mHolder.tvState.setText("配对中"); break; case BluetoothDevice.BOND_BONDED: mHolder.tvState.setText("已配对"); break; } } else { mHolder.tvState.setText("已连接"); } private int linkState = 0; //连接状态 0表示未连接状态 1表示连接状态
简单的很,我的理解就是蓝牙工作是按一定顺序的,首先你要先配对,BOND_BONDED只表示配对成功意思,绝没有已连接的意思。只有配对成功后,你去连接蓝牙设备,才有“已连接“状态,那大家明白了吧,是的已连接只可能存在于已配对设备中。未连接啊,你像未配对,或者配对中,或者连接失败,你都可以使用未连接状态。所以,当连接成功后,你刷下蓝牙列表呗。
到这里,传统蓝牙开发,已经跟大家讲解清楚了,相信现在好多人,还有各种疑问,反正不管什么,我们先把通道打通,再实现更复杂的功能。
接下来,我再来说说ble蓝牙,这个就简单多了。如果你看过网上的资料,相信大家到connect这个步骤都不会有什么问题。我就从下面开发不是很顺畅地方开始讲
1. 先从服务标识说起,经常浏览ble蓝牙资料,我发现很少有人能把这个说清楚的,估计也是我们上大学课程设计的水平,一句话半吊子。有些文章,作者只是给了思路,代码并不准确,好多留言就问,怎么只能发送数据成功,无法接到数据。嘿嘿,兄弟,其实你已经成功建立起了蓝牙通道,只是代码写的有问题,读取数据是没问题,就是有的通道默认只有20个字节,有的可以多一点。
// 服务标识 这个ID,一般不会错,大家很容易一致,所以connect应该没问题!
private final UUID SERVICE_UUID = UUID.fromString("6E400001-B5A3-F393-E0A9-E50E24DCCA9E");
// 特征标识 NOTIFY (从推送通道过来的数据-默认最多20字节),
private final UUID CHARACTERISTIC_NOTIFY_UUID = UUID.fromString("6E400004-B5A3-F393-E0A9-E50E24DCCA9E");
// 特征标识 WRITE (发送数据)
private final UUID CHARACTERISTIC_WRITE_UUID = UUID.fromString("6E400002-B5A3-F393-E0A9-E50E24DCCA9E");
// 特征标识 READ (自己主动发起要接收的数据)
private final UUID CHARACTERISTIC_READ_UUID = UUID.fromString("6E400003-B5A3-F393-E0A9-E50E24DCCA9E");
https://github.com/wandersnail/easyble, 给大家推荐一个app,别问,下载就是。等会告诉大家什么是真理,跟我们对接的开发,如单片机,pc端啥的,他们跟你说的这个UUID是读数据的,这个UUID是发送数据的,这个UUID是通知用的,总之一句话别太当真,否则坑死你。 其实,大家在应用市场也能搜索到这个app ->BLE蓝牙调试助手,必须下载。
大家有没有看到,这是一个二维层级结构,是的groupItem的UUID就是我们connet时,使用的UUID。
标注一:private final UUID SERVICE_UUID = UUID.fromString("6E400001-B5A3-F393-E0A9-E50E24DCCA9E"),一定是我们connect连接的UUID,就是服务标识。是建立通道的关键ID,与上面代码一致。抱歉,我现在没有蓝牙单片机设备,这是一个华为佩戴设备,所以大家将就看。
标注二:我们看其他人的文档,会有这些代码,名字各异
// notify write private BluetoothGattCharacteristic notifyCharacteristic, writeCharacteristic; // read private BluetoothGattCharacteristic readCharacteristic;
接下来就是关键就是关键地方了:
// 搜索GATT服务 mBluetoothGatt.discoverServices();
在回调函数中需要获取这个GATT服务,干什么用的呢,就是我们收发信息,接收通知用的。就是这么简单!
//通过notify通道接收数据,每次只有20字节(默认) notifyCharacteristic = gattService.getCharacteristic(CHARACTERISTIC_NOTIFY_UUID); if (notifyCharacteristic == null) { LogUtil.e("无法接收数据!"); mBluetoothGatt.setCharacteristicNotification(notifyCharacteristic, true); } //开启系统监听 UUID是系统的,不需要修改 mBluetoothGatt.setCharacteristicNotification(notifyCharacteristic, true); BluetoothGattDescriptor descriptor = notifyCharacteristic.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb")); if (descriptor != null) { descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE); mBluetoothGatt.writeDescriptor(descriptor); } writeCharacteristic = gattService.getCharacteristic(CHARACTERISTIC_WRITE_UUID); if (writeCharacteristic == null) { LogUtil.e("无法发送数据!"); } readCharacteristic = gattService.getCharacteristic(CHARACTERISTIC_READ_UUID); if (writeCharacteristic == null) { LogUtil.e("无法接收数据!"); }
我再把这部分代码copy过来,为了让大家看清楚:
// 特征标识 NOTIFY private final UUID CHARACTERISTIC_NOTIFY_UUID = UUID.fromString("6E400004-B5A3-F393-E0A9-E50E24DCCA9E"); // 特征标识 WRITE (发送数据) private final UUID CHARACTERISTIC_WRITE_UUID = UUID.fromString("6E400002-B5A3-F393-E0A9-E50E24DCCA9E"); // 特征标识 READ (接收数据) private final UUID CHARACTERISTIC_READ_UUID = UUID.fromString("6E400003-B5A3-F393-E0A9-E50E24DCCA9E");
childItem标注二,下面有个小属性Properties下面的属性,read,write,notify 一一对应你的CHARACTERISTIC_READ_UUID ,CHARACTERISTIC_WRITE_UUID ,CHARACTERISTIC_NOTIFY_UUID 。
所以,以后不要纠结单片机,佩戴设备,头戴设备给你的UUID是否正确,我们通过这个app,自己看,就一清二楚了。
剩下是范例:
/** * 发送数据 * * @param data 数据 * @return true:发送成功 false:发送失败 */ public boolean sendData(byte[] data) { //value为上位机向下位机发送的指令 writeCharacteristic.setValue(data); return mBluetoothGatt.writeCharacteristic(writeCharacteristic); } /** * 读取单片机发送的数据 * * @return true:发送成功 false:发送失败 */ public boolean readData() { return mBluetoothGatt.readCharacteristic(readCharacteristic); }
/** * 蓝牙操作回调 * 蓝牙连接状态才会回调 */ private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() { @Override public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { if (newState == BluetoothProfile.STATE_CONNECTED) { // 蓝牙已连接 mConnectionState = STATE_CONNECTED; sendBleBroadcast(ACTION_GATT_CONNECTED); // 搜索GATT服务 mBluetoothGatt.discoverServices(); } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { // 蓝牙已断开连接 mConnectionState = STATE_DISCONNECTED; sendBleBroadcast(ACTION_GATT_DISCONNECTED); } super.onConnectionStateChange(gatt, status, newState); } @Override public void onServicesDiscovered(BluetoothGatt gatt, int status) { super.onServicesDiscovered(gatt, status); // 发现GATT服务 if (status == BluetoothGatt.GATT_SUCCESS) { LogUtil.e("获取GATT服务成功!"); setBleNotification(); } else { LogUtil.e("获取GATT服务失败!"); } } @Override public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { super.onCharacteristicChanged(gatt, characteristic); sendBleBroadcast(ACTION_DATA_AVAILABLE, characteristic); // value为设备发送的数据,根据数据协议进行解析 byte[] value = characteristic.getValue(); String result; try { result = new String(value, "UTF-8"); } catch (UnsupportedEncodingException e) { LogUtil.e("byte转字符串异常:" + e.getMessage()); result = ""; } LogUtil.e("收到蓝牙数据:" + result); } @Override public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) { super.onDescriptorRead(gatt, descriptor, status); if (status == BluetoothGatt.GATT_SUCCESS) { LogUtil.e("开启监听成功!"); sendBleBroadcast(ACTION_OPEN_NOTIFY_SUCCESS); } else { LogUtil.e("开启监听失败!"); } } @Override public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { super.onCharacteristicWrite(gatt, characteristic, status); if (status == BluetoothGatt.GATT_SUCCESS) { LogUtil.e("发送成功!"); readData(); } else { LogUtil.e("发送失败!"); } } @Override public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { super.onCharacteristicRead(gatt, characteristic, status); // App主动读取数据 if (status == BluetoothGatt.GATT_SUCCESS) { byte[] value = characteristic.getValue(); String result; try { result = new String(value, "UTF-8"); } catch (UnsupportedEncodingException e) { LogUtil.e("byte转字符串异常:" + e.getMessage()); result = ""; } LogUtil.e("收到蓝牙数据:" + result); } } };
好了到这里结束了。最后再说一句,其实我对notify属性的理解,就是通知用的,根本不是回传数据,当我的手机向单片机发送一条指令,然后等待结果,这个时候我会先收到通知,告诉我,设备已准备完毕,你可以获取设备上的数据了,这时使用read属性通道,获取单片机发给我的数据。
以上纯粹个人见解,肯定有不对的地方。希望大家多多指教。qq我就不留了,其实蓝牙使用没有你想象中复杂,我感觉难点还是在数据传输上面,如何解决大数据传输,让我最近很头疼。等我研究完毕了,再给大家上传最新见解!