之前因为项目需要,做了一个和设备通信的蓝牙模块,开始只是一个简单的发送接收,最近整理了一下,做了一些改进,也算是一种学习和进步吧!
下面看具体内容:
先看效果
江湖规矩,先贴一个扫描的图再将内容吧
文档准备
工欲善其事必先利其器,想要做好一个功能,我觉得最先要做的就是看官方文档,如果官方文档不够详细,再补充一点别人的博客,最后融合成自己的东西,下面是谷歌官方文档,建议先看看,写的非常不错,还是中文的。
https://developer.android.google.cn/guide/topics/connectivity/bluetooth
下面开始介绍代码吧
添加权限
先在 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.permission.ACCESS_COARSE_LOCATION
蓝牙客户端分析
关于客户端我这先整理一下思路,后面再看具体实现。
被连设备状态
首先是我们要知道,当我们想要去连接一个蓝牙设备时,这个蓝牙设备有几种状态?如果看了上面说的官方文档,应该很容易得到答案,状态如下
- 已配对过
- 扫描到了
- 未扫描,且从未连接过
当然这是我的答案,有点牵强,却符合我们设计代码时的业务,下面讲讲这三个状态的一些相关内容。
已配对的设备
我们可以通过下面方法拿到已配对的蓝牙设备。
// 获得和当前Android已经配对的蓝牙设备。
Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices();
扫描蓝牙设备
上面被连接的设备的第二种及第三种状态都与扫描有关,蓝牙设备的扫描通过 BluetoothAdapter 和广播接收器实现的。下面一个一个介绍,先是 BluetoothAdapter 的扫描:
//蓝牙适配器
BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
//开启扫描,会自动停止,好像是120s吧
mBluetoothAdapter.startDiscovery()
再是广播接收器的注册(再扫描前注册吧):
IntentFilter filter = new IntentFilter();
//开始扫描
filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED);
//结束扫描
filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
//发现设备
filter.addAction(BluetoothDevice.ACTION_FOUND);
//状态变化
filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
registerReceiver(mBroadcastReceiver, filter);
广播接收器收到的就是我们上次加的 filter 了,很好理解:
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@SuppressLint("SetTextI18n")
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
switch (action) {
case BluetoothAdapter.ACTION_DISCOVERY_STARTED:
break;
case BluetoothAdapter.ACTION_DISCOVERY_FINISHED:
break;
case BluetoothDevice.ACTION_FOUND:
//发现一个设备
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
//设备可能多次被发现
if (null != device && !deviceList.contains(device)) {
...
}
break;
default:
break;
}
}
};
客户端使用目的
对于一个客户端来说,不就是连接蓝牙设备,发送接收消息么。不管上面被连设备处于什么状态,我们的目的都是去连接它,给它发消息,接收它消息,这就是我们的目的。
也就是说,对于客户端,我们完全将它抽象为三个方法:
- connectDevice
- sendData
- receiveData
明确了我们的目的,后面去细化这三个方法就行了,下面开始实现。
蓝牙客户端实现
连接设备
我们先看连接蓝牙设备的方法:
private BluetoothSocket socket;
try {
socket = device.createRfcommSocketToServiceRecord(UUID.fromString(MY_UUID));
socket.connect();
} catch (IOException e) {
e.printStackTrace();
runOnUiThread(() -> receiveText.append("创建socket失败!\n"));
}
蓝牙的 socket 和 socket 还是挺类似的,就是这里要注意一个 UUID 的问题,随便网上找个方法生成一个就行(官网这么说的…),我用的下面这个,好像很通用:
private final String MY_UUID = "00001101-0000-1000-8000-00805F9B34FB";
上面提到了,一个被连设备可能有三种状态,连接的时候我们就要分别处理了。其实也简单,这里连接不就是要一个 BluetoothSocket 类型的对象么,我们已配对的设备列表还有扫描结果的列表都是这个类型,直接就能使用,对于未扫描且从未连接过的设备,那就没办法了,只能再扫描试试。
- 已配对列表
- 已扫描列表
- 再扫描列表
也就是说对于一个要连接的蓝牙设备,我们先再已配对的设备里找,再从已扫描到的设备里面找,如果都没找到就再开启扫描找,找到了就有 BluetoothSocket 了,去连接就行!
发送数据
都说蓝牙的 socket 和 socket 类似,那发送肯定也差不多啊,事实也是如此:
//发送数据到另一端
private void sendDataToServer(String data) {
new Thread(() -> {
try {
OutputStream os = socket.getOutputStream();
os.write(data.getBytes());
os.flush();
//os.close();
runOnUiThread(() -> receiveText.append("发送消息:" + data + "\n"));
} catch (Exception e) {
e.printStackTrace();
runOnUiThread(() -> receiveText.append("发送消息:" + "失败!" + "\n"));
}
}).start();
}
这里我们拿到 socket 的输出流,开启个线程往里面写内容就可以发出去了。
接收数据
接收数据也和 socket 的使用类似,直接在创建连接那里写个死循环读取数据就可以了
threadPool.execute(() -> {
try {
socket = device.createRfcommSocketToServiceRecord(UUID.fromString(MY_UUID));
socket.connect();
} catch (IOException e) {
e.printStackTrace();
runOnUiThread(() -> receiveText.append("创建socket失败!\n"));
}
if (socket != null && socket.isConnected()) {
runOnUiThread(() -> receiveText.append("创建socket成功!\n"));
try {
InputStream inputStream;
while(socket != null) {
//随便写写
inputStream = socket.getInputStream();
byte[] buffer = new byte[2560];
int len = inputStream.read(buffer, 0, 2560);
if (len <= 0) {
//noinspection BusyWait
Thread.sleep(1000);
continue;
}
runOnUiThread(()->receiveText.append("收到消息:" + new String(buffer, 0, len) + "\n"));
}
} catch(IOException e) {
e.printStackTrace();
Log.e("TAG", "startConnect: " + e.getMessage());
runOnUiThread(() -> receiveText.append("socket异常!\n"));
} catch(Exception e) {
e.printStackTrace();
}
}
});
写的很粗糙,但是能用!根据自己要的功能改进吧。
完整代码太长了,我会在文章最后面贴 demo 的 gitee 地址,下面先看服务端的代码分析及实现。
蓝牙服务端
不同与蓝牙客户端的多种逻辑,蓝牙服务端实际就提供连接功能、接收数据和发送数据,下面我们直接讲解代码实现。
提供连接功能
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
receiveText.setText("等待客户端连接...\n");
//启动服务端
if (mBluetoothAdapter != null) {
threadPool.execute(new Thread(() -> {
try {
serverSocket = mBluetoothAdapter
.listenUsingRfcommWithServiceRecord(tag, UUID.fromString(MY_UUID));
while (true) {
BluetoothSocket remotePeer = serverSocket.accept();
bluetoothSockets.add(remotePeer);
adapter.notifyDataSetChanged();
//获取蓝牙设备
BluetoothDevice device = remotePeer.getRemoteDevice();
runOnUiThread(()->receiveText.append("连接客户端成功:" + device.getName() + "\n"));
//启动线程处理socket连接
threadPool.execute(new ServerThread(remotePeer));
}
} catch (IOException e) {
e.printStackTrace();
}
}));
}
这里也要用到 BluetoothAdapter 这个对象,大概就是蓝牙通信的核心了。创建蓝牙服务端需要提供一个 name 和 UUID,name可以随便填写,UUID 可以用上面提到的那个。拿到 BluetoothServerSocket 对象,接下来的内容就和 socket 服务端类似了,启动一个线程去处理每个 socket 连接。
发送数据
发送数据和客户端差不多,拿着输出流写内容就可以。
//向指定客户端socket发送消息
private void sendDataToClient(BluetoothSocket socket, String data) {
String deviceName = null != socket ? socket.getRemoteDevice().getName() : null;
if (null != socket && socket.isConnected()) {
//异步线程发送
threadPool.execute(() -> {
try {
socket.getOutputStream().write(data.getBytes());
runOnUiThread(()->receiveText.append("发送消息->" + deviceName + ":" + data + "\n"));
} catch (IOException e) {
runOnUiThread(()->receiveText.append("发送消息->" + deviceName + ":" + "失败!"
+ e.getMessage() + "\n"));
e.printStackTrace();
}
});
}else {
runOnUiThread(()->receiveText.append("发送消息->" + deviceName + ":" + "失败!" + "\n"));
}
}
接收数据
这里就是单个连接 socket 的线程执行代码,里面就是接收数据的部分。
//服务端线程
private class ServerThread implements Runnable {
private final BluetoothSocket remotePeer;
private final String deviceName;
private ServerThread(BluetoothSocket remotePeer) {
this.remotePeer = remotePeer;
deviceName = remotePeer.getRemoteDevice().getName();
}
@Override
public void run() {
try {
InputStream inputStream;
while(remotePeer != null && remotePeer.isConnected()) {
//简单写写
inputStream = remotePeer.getInputStream();
byte[] buffer = new byte[2560];
int len = inputStream.read(buffer, 0, 2560);
if (len <= 0) {
//noinspection BusyWait
Thread.sleep(1000);
continue;
}
//打印接收消息
runOnUiThread(()->receiveText.append("收到消息->" + deviceName + ":"
+ new String(buffer, 0, len) + "\n"));
}
} catch(IOException e) {
e.printStackTrace();
} catch(Exception e) {
e.printStackTrace();
}finally {
try {
if (null != remotePeer) {
remotePeer.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
完整代码
完整代码太长了,正好我也是写了一个 demo,已提交到 gitee 上面,有需要的读者可以看看:
结语
本篇文章我自己觉得写的不是很好,可能我的侧重点在介绍思路和我觉得重要的知识吧,蓝牙部分的 demo 我倒是挺满意,有很多我没写出来的东西,希望对读者有帮助,基础知识的话还是建议看官方文档。
最近也算每天一篇博客了,希望能够把自己的思路和语言精炼起来,和读者一起进步。
end