最近项目上需要蓝牙通讯功能,所以自己私下里学习了一下蓝牙通讯相关的知识。一直以为蓝牙通讯是调用系统层的API实现的,比如我们平时用的利用蓝牙传照片和文件,我们只需要选择一下接受设备就可以了,但是自己实现蓝牙远程通信之后,才明白其中的一些细节问题,有些流程还是需要我们自己去做的。
1、首先是蓝牙通信机制
蓝牙通信也是采用Socket机制,通信双方有一方为服务器端,另一方为客户端,可能有
人会觉得通信双方都一样的android设备,怎么还有服务器端和客户端之分呢,哪一方应该是服务器端,哪一方应该是客户端呢?答案是主动发起通信请求的一方为客户端,另一方自然为服务器端了。
2、蓝牙通信的工作流程
2.1 服务端先建立一个服务端套接字Socket,然后该套接字开始监听客户端的连接;
2.2 客户端也建立一个socket,然后向服务端发起连接,这时候如果没有异常就算两个设备连接成功了;
2.3 这时候客户端和服务端都会持有一个Socket,利用该Socket可以发送和接收消息。
上面大概介绍了蓝牙通信的工作流程,下面该讲点干货了。
1、蓝牙通信涉及到的类
BluetoothAdapter
该类是提供蓝牙服务的接口,普通的用户app可以利用该类使用系统的蓝牙服务,如果我们想要获取该类的对象,用如下语句:
BluetoothAdapterbluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
该类比较常见的API有:
IsEnabled(),该方法判断蓝牙是否打开,如果蓝牙已经打开,该方法返回true,否则返回false。
如果蓝牙没有打开,我们想要打开蓝牙,有两种方案,一种是跳转到系统蓝牙设置页面
Intent intent = new Intent(Settings.ACTION_BLUETOOTH_SETTINGS);
startActivity(intent);
另一种是直接在我们的应用里面打开,这里也有两种方法,一种是给用户提示的,用户可以选择是否打开蓝牙,用户体验要好一些,写法如下:
Intent enabler = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivity(enabler);
另一种方法是直接打开蓝牙,不让用户选择,比较简单粗暴哈,写法如下:
bluetoothAdapter.enable()
enable()方法是有返回值的,如果打开成功,返回true,否则返回false,可以根据返回值判断是否打开成功,不过这种方法需要Manifest.permission.BLUETOOTH_ADMIN权限。
listenUsingRfcommWithServiceRecord(Stringname, UUID uuid)方法,该方法是创建一个BluetoothServerSocket对像。
getBondedDevices(),该方法返回与该设备已经配对完成的远程设备的BluetoothDevice集合。
还有些方法是用于蓝牙搜索、配对的API,如果只是做蓝牙通信,这些API用不上,本文这里先不写了。
BluetoothDevice
这个类包含了蓝牙设备的一些信息,比如mac地址、名称和类型等,我们在蓝牙设置界面经常会看到搜索到的蓝牙设备信息的前面都有一个图标,这个图标有手机、耳机、电脑等,这个图标就是根据BluetoothDevice对象的类型信息来区别的。这个类中还有一个很重要的API,正是用这个API来创建客户端的BluetoothSocket.
createRfcommSocketToServiceRecord(UUIDuuid)根据UUID创建并返回一个BluetoothSocket.
BluetoothServerSocket
这个类一种只有三个方法,两个重载的accept(),accept(inttimeout)两者的区别在于后面的方法指定了过时时间,需要注意的是,执行这两个方法的时候,直到接收到了客户端的请求(或是过期之后),都会阻塞线程,应该放在新线程里运行!这两个方法都会返回BluetoothSocket对象,即服务端的Socket对象。
BluetoothSocket
蓝牙通信服务端和客户端都持有一个该类对象,该类的主要API有:
close(),关闭
connect()连接
getInptuStream()获取输入流
getOutputStream()获取输出流
getRemoteDevice()获取远程设备,这里指的是获取bluetoothSocket指定连接的那个远程蓝牙设备
上面是类的讲述,下面讲一下这些类在蓝牙通信中是怎么运用的。蓝牙通信主要涉及到两个环节:通信连接的建立、发送数据和接收数据。
2、具体用法
2.1 通信连接的建立
要想建立蓝牙通信,根据上面的流程图,要先建立服务器,然后客户端去请求连接。
服务器端建立套接字,等待客户端连接,调用BluetoothAdapter的listenUsingRfcommWithServiceRecord方法,产生一个BluetoothServerSocket对象,然后调用BluetoothServerSocket对象的accept方法,注意accept方法会产生阻塞,直到一个客户端连接建立,所以服务器端的socket的建立需要在一个子线程中去做,代码如下:
public class ServerThread extends Thread
{
@Override public void run() {
super.run();
UUID uuid = UUID.fromString(SPP_UUID);
try {
BluetoothServerSocket serverSocket = bluetoothAdapter. listenUsingRfcommWithServiceRecord(SERVER_SOCKET_NAME,uuid);
mSocket = serverSocket.accept();
Log.d(TAG,"server create success");
//连接建立之后即启动一个接收数据线程,该逻辑属于自定义业务,不是强制性流程,接收数据的时间点可以根据自己的业务逻辑去定。
new ReadThread().start();
} catch (IOException e) {
e.printStackTrace();
}
}
}
客户端去连接服务器端,需要先持有服务器端的BluetoothDevice对象,先调用BluetoothDevice的createRfcommSocketToServiceRecord方法,这个方法会产生一个客户端的BluetoothSocket对象,然后调用该对象的connect方法,该过程最好也是单独起一个线程去做,代码如下:
private class ClientThread extends Thread
{
@Override public void run() {
super.run();
UUID uuid = UUID.fromString(SPP_UUID);
try {
mSocket = bondedDevice.createRfcommSocketToServiceRecord(uuid);
mSocket.connect();
Log.d(TAG,"connect server success");
new ReadThread().start();
} catch (IOException e) {
e.printStackTrace();
}
}
}
2.2 发送数据和接收数据
发送数据和接收数据对于服务端和客户端来讲都是一样的,使用方法也一样,因为使用的都是通过一个类。发送数据需要调用BluetoothSocket的getOutputStream(),接收数据需要调用getInputStream()方法。具体代码如下:
// 读取数据
private class ReadThread extends Thread {
public void run() {
byte[] buffer = new byte[1024];
int bytes;
InputStream is = null;
try {
is = mSocket.getInputStream();
while (true) {
if ((bytes = is.read(buffer)) > 0) {
byte[] buf_data = new byte[bytes];
for (int i = 0; i < bytes; i++) {
buf_data[i] = buffer[i];
}
}
} catch (IOException e1) {
e1.printStackTrace();
} finally {
try {
is.close();
} catch (IOException e1) {
e1.printStackTrace();
}
}
}
} private class WriteThread extends Thread
{
private String msg;
public WriteThread(String msg)
{
this.msg = msg;
}
@Override public void run() {
super.run();
if (mSocket == null) {
Toast.makeText(BluetoothActivity.this, "没有连接", Toast.LENGTH_SHORT).show();
return;
}
try {
OutputStream os = mSocket.getOutputStream();
os.write(msg.getBytes());
Log.d(TAG,"send over");
} catch (IOException e) {
e.printStackTrace();
}
}
}
上面只是本人关于蓝牙通信的一些经验,如果应用到项目里面还需要好好设计一下架构,有许多细节问题需要根据自己的业务去处理,比如通信异常的处理、通信方式(是先发再读还是边发边读)、各工作线程何时结束等。有问题欢迎留言,一起讨论。