官方文档:https://developer.android.com/guide/topics/connectivity/bluetooth
Android 中将蓝牙分为传统蓝牙和低功耗蓝牙(Bluetooth low energy)两种。后者的优势在于快速搜索,快速连接,超低功耗保持连接和数据传输,同时低功耗带来的缺点是数据传输速率低,所以多用在可穿戴式设备。
在这里我们主要介绍使用传统蓝牙来实现一个聊天的数据传输 demo。以下内容基本都是基于官方文档的二次阐述,以及一些疑惑的查找到的解答,最后在 demo 里面有对蓝牙的相关操作进行了封装。先贴个图看看效果吧:
蓝牙.png
基础知识
BluetoothAdapter: 本地蓝牙适配器,我们在发现设备,配对的时候都得用上它。
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
要实现一个蓝牙聊天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 devices
if (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进行了一定程度的整合,还存有不少的问题。
网友评论