
官方文档:https://developer.android.com/guide/topics/connectivity/bluetooth
Android 中将蓝牙分为传统蓝牙和低功耗蓝牙(Bluetooth low energy)两种。后者的优势在于快速搜索,快速连接,超低功耗保持连接和数据传输,同时低功耗带来的缺点是数据传输速率低,所以多用在可穿戴式设施。
在这里我们主要详情使用传统蓝牙来实现一个聊天的数据传输 demo。以下内容基本都是基于官方文档的二次阐述,以及少量疑惑的查找到的解答,最后在 demo 里面有对蓝牙的相关操作进行了封装。先贴个图看看效果吧:
蓝牙.pngBluetoothAdapter: 本地蓝牙适配器,我们在发现设施,配对的时候都得用上它。
BluetoothDevice: 远程蓝牙设施,就是代表着你可以连接的一个设施,里面存储名字,MAC地址等信息。
BluetoothSocket 和 BluetoothServerSocket: 蓝牙套接字,和 TCP 的 Socket 类似。一台设施开启一个 ServerSocket 并监听,另一台设施开启 Socket 进行连接,以此实现一个端对端的连接和数据传输。
UUID: 唯一识别符。它被用于唯一标识应用的蓝牙服务(不是表示蓝牙设施)。
Q1:为什么网上的大多数例子都是使用
00001101-0000-1000-8000-00805F9B34FB这个UUID?
A1:这是由于一个蓝牙设施里面可以提供诸多服务,如A2DP(蓝牙音频传输)、HEADFREE(免提)、SPP(串口通信) 等等。而上面的字符串码就是 SPP 的 UUID,基本蓝牙板上默认就是这个值,我们可以通过UUID.fromString("00001101-0000-1000-8000-00805F9B34FB")来将字符串转成 UUID。
在连接蓝牙串口板我们往往就会使用上面的UUID,但是假如 Android 端对端的话,建议自己自己设定 UUID,这样别人的 UUID 就连不上了。
要实现一个蓝牙聊天demo,首先我们有两台有蓝牙功能的设施,这里我用了两台手机。按照流程一般来说要开启蓝牙-搜索设施-配对设施-连接-通信。如此就能实现一个基本的蓝牙通信。
在 Android 中没有权限寸步难行。要使用蓝牙,还需要公告相应的权限。
<manifest ... > <uses-permission android:name="android.permission.BLUETOOTH" /> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/> ...</manifest>BLUETOOTH 是基本的权限,用于你的蓝牙连接,数据传输等。
BLUETOOTH_ADMIN 一般应只用于发现本地蓝牙设施。
除非该应用是将要应客户请求修改蓝牙设置的“超级管理员”,否则不应使用此权限所授予的其余能力。
另:假如要使用
BLUETOOTH_ADMIN权限,则还必需拥有BLUETOOTH权限。
此外会发现我这里比官方文档还多了个 ACCESS_COARSE_LOCATION,这是由于我在实测过程中,我的测试机Android 8.0 系统中,蓝牙扫描没有扫描出信息,但是系统是有的。在网上一番寻觅之后发现在 Android 6.0 之后还需要一个模糊定位的权限,否则扫描功能无效。
关于动态权限申请在此不作累述,小伙伴们可以自己去实现。
1、获取 BluetoothAdapter
BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();if (mBluetoothAdapter == null) { //TODO 设施不支持蓝牙,阻断客户操作}2、启动蓝牙
if(!mBlueAdapter.isEnabled()){ //请求蓝牙 Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);}系统将会弹窗提醒客户能否开启蓝牙,客户的选择将在 onActivityResult() 中得到反馈。同意的时候收到 RESULT_OK,拒绝的时候收到 RESULT_CANCELED。
1、查找已配对设施
Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices();// If there are paired devicesif (pairedDevices.size() > 0) { // Loop through paired devices for (BluetoothDevice device : pairedDevices) { // Add the name and address to an array adapter to show in a ListView mArrayAdapter.add(device.getName() + "\n" + device.getAddress()); }}2、查找未知设施
mBlueAdapter.startDiscovery()查找未知设施只要要调用 startDiscovery() 就可,这是一个异步操作,系统一般会在后端进程进行一个 12 秒的查询扫描。查找出来的信息我们需要在广播中进行监听才可得知。
private final BroadcastReceiver mReceiver = new BroadcastReceiver() { public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (BluetoothDevice.ACTION_FOUND.equals(action)) { BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); mUnpaireList.add(device); mUnpaireAdapter.notifyDataSetChanged(); } }};//注册广播IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);registerReceiver(mBtReceiver, filter);//同时别忘了销毁时注销广播在这里我们往往需要一台设施做服务器端一台做用户端,实际上就是 app 开启了一个服务器线程让蓝牙的 socket 可以连接。连接完成后再使用 I/O Stream 进行数据交互。
1、服务器线程
我们需要用 listenUsingInsecureRfcommWithServiceRecord(String,UUID) 获取 BluetoothServerSocket。
Q2:
listenUsingRfcommWithServiceRecord()和listenUsingInsecureRfcommWithServiceRecord()有什么区别?
A2:从名字来看似乎是安全不安全的区别,但是实际上我并没有找到相关资料佐证。也有文章形容用户端的 socket 创立createRfcommSocketToServiceRecord是安卓2.3系统及以下用的,新的安卓要用createInsecureRfcommSocketToServiceRecord,所以对应着服务器端也用Insercure吧。
服务器监听中,因为 accept() 方法是阻塞的,所以需要子线程中解决。
private class AcceptThread extends Thread { private final BluetoothServerSocket mmServerSocket; public AcceptThread() { BluetoothServerSocket tmp = null; try { tmp = mBluetoothAdapter.listenUsingInsecureRfcommWithServiceRecord(NAME, MY_UUID); } catch (IOException e) { } mmServerSocket = tmp; } public void run() { BluetoothSocket socket = mmServerSocket.accept(); mmServerSocket.clost(); mInputStream = socket.getInputStrem(); mOutputStream = socket.getOutputStream(); byte[] buffer = new byte[1024]; int bytes; while (true) { try{ //读取buffer信息打印出来 bytes = mInputStream.read(buffer); String s = new String(buffer, 0, bytes); sendHandlerMsg(s); } carch(IOException e){ break; } } }}2、用户端连接
用户端连接和服务端连接类似。当然首先你要获取到要配对的设施 BluetoothDevice,而后获取 BluetoothSocket ,使用 mSocket.connect() 连接就可。他们的逻辑基本相同,在官方文档中也有相关的形容。
在这里由于实际上我的需求是使用手机连接一个硬件设施,所以我选择封装了一个蓝牙工具类,把蓝牙开启连接等用户端相关操作封装到 BluetoothManager 中。其中 ConnectThread 和 ReadThread 抽成两个Runnable 放在线程池中解决。当 socket 连接成功后获取到 IO 流来进行读写操作。读操作由于属于阻塞操作放在子线程。代码这里就不贴了,文末有此 demo 的地址。有兴趣的也可以自己去实现一下。
剩下的就是布局和交互逻辑的实现,这里就不在逐个阐述了。
蓝牙的相关操作感觉和 Socket 非常地类似,都是进行端对端绑定,而后进行数据传输。所以同理也应该会存在相似 Socket 的各种问题,比方说丢包,断开连接需要心跳检测,重连机制等等。这个demo只是对API进行了肯定程度的整合,还存有不少的问题。
github 地址