一、点对点蓝牙通信
无论WiFi还是4G/5G网络,建立网络连接后都会访问互联网资源,并不能直接访问局域网资源。比如两个人在一起,甲要把手机上的视频传给乙,通常情况是打开微信,通过微信传文件给对方。不过上传视频很耗流量,如果现场没有可用的WiFi,手机的数据流量又不足,就只能干瞪眼了。为解决这种邻近传输文件的问题,蓝牙技术应运而生。它是一种无线技术标准,可实现设备之间的短距离数据交换。
1、Android为蓝牙技术提供了4个工具类
分别是蓝牙适配器BuletoothAdapter、蓝牙设备BluetoothDevice、蓝牙服务端套接字BluetoothServerSocket和蓝牙客户端套接字BluetoothSocket。
(1)蓝牙适配器BuletoothAdapterBuletoothAdapter的常用方法在上面已经写了一部分。下面补充为剩余的几个常见方法:
- setName:设置本机的蓝牙名称。
- getName:获取本机的蓝牙名称。
- getAddress:获取本机的蓝牙地址。
- getState:获取本地蓝牙适配器的状态。值为BluetoothAdapter.STATE_ON时表示蓝牙可用。
- listenUsingRfcommWithServiceRecord:根据名称和UUID创建并返回BluetoothServerSocket。
- listenUsingRfcommOn:根据信道编号创建并返回BluetoothServerSocket。
(2)蓝牙设备BluetoothDeviceBluetoothDevice用于指代某个蓝牙设备,通常表示对方设备,相对应的,BuletoothAdapter用于管理本机的蓝牙设备。下面是BluetoothDevice的常用方法说: - getName:获得该设备的名称。
- getAddress:获得该设备的地址。
- getBondState:获得该设备的绑定状态。
- createBond:创建配对请求。配对结果通过广播返回。
- createRfcommSocketToServiceRecord:根据UUID创建并返回一个BluetoothSocket。
(3)蓝牙服务端套接字BluetoothServerSocketBluetoothServerSocket是蓝牙服务端的Socket(套接字),用来接收蓝牙客户端的Socket连接请求。下面是它的常用方法: - accept:监听外部的蓝牙连接请求。一旦有请求接入,就返回一个BluetoothSocket对象。
- close:关闭服务端的蓝牙监听。
(4)蓝牙客户端套接字BluetoothSocketBluetoothSocket是蓝牙客户端的Socket,用于与对方设备进行数据通信。下面是它的常用方法: - connect:建立蓝牙的Socket连接。
- close:关闭蓝牙的Socket连接。
- getInptuStream:获取Socket连接的输入流对象。
- getOutputStream:获取Socket连接的输出流对象。
-
getRemoteDevice:获取远程设备信息,即与本设备建立Socket连接的远程蓝牙设备。
2、使用蓝牙建立连接、发送消息的完整流程,完整流程主要分为以下4个步骤:
(1)开启蓝牙功能
准备两部手机,各自安装蓝牙演示App。首先打开演示App的蓝牙页面,一开始两部手机的蓝牙功能均为关闭状态,然后分别点击两部手机左上角的开关按钮,准备开启手机的蓝牙功能。两部手机都会弹出一个确认对话框,如下图【蓝牙权限的选择对话框】所示,提醒用户是否允许其他蓝牙设备检测到本手机。
蓝牙权限的选择对话框.png
点击“允许”按钮确认开启蓝牙功能。稍等一会儿,两部手机分别检测到了对方设备,并在界面上显示对方设备名称,且状态为“未绑定”。此时A手机的检测界面如图【A手机发现对方】所示,B手机的检测界面如图【B手机发现对方】所示。
A手机发现对方.png
B手机发现对方.png
(2)确认配对并完成绑定
在任意一部手机上点击对方的设备名称,表示发起配对请求。此时两部手机都会弹出一个确认对话框,提示用户是否将本机与对方设备进行配对如图所示
A手机的配对弹窗.png
B手机的配对弹窗.png
两边分别点击“配对”按钮,确认与对方配对。配对完成后,检测界面将设备状态改为“已绑定”
A手机完成配对.png
B手机完成配对.png
(3)建立蓝牙连接
在任意一部手机上点击已绑定的设备记录,表示发起连接请求。具体而言,首先是客户端的BluetoothSocket调用connect方法,然后服务端BluetoothServerSocket的accept方法接收连接请求,于是双方成功建立连接。有的手机可能会弹窗提示“应用想与设备进行通信”,点击弹窗的“确定”按钮即可放行。建立蓝牙连接后,设备记录右边的状态值改为“已连接”。
B手机与对方建立连接.png
(4)通过蓝牙发送消息
在A手机上点击已连接的设备记录,表示想要发送消息。于是A手机弹出文字输入对话框,提示用户输入待发送的消息文本,文字输入框如图【A手机准备向对方发送消息】所示。点击“确定”按钮发送消息,然后B手机接收到A手机发来的消息,就把该消息文本通过弹窗显示出来,B手机的消息弹窗如图【B手机收到对方发来的消息】所示。
A手机准备向对方发送消息.png
B手机收到对方发来的消息.png
一个完整的蓝牙应用过程全部呈现出来。上面的流程仅实现了简单的字符串传输,真实场景更需要文件传输。当然,使用输入输出流操作文件也不是什么难事。
两部手机之间通过蓝牙分享数据也要先进行搜索与配对操作,然后才能开展后续的设备连接和数据传输,本节直接进入双方设备连接和数据传输的环节。
正如网络通信中的Socket通信,蓝牙Socket同样存在服务端与客户端的概念,服务端负责侦听指定端口,客户端只管往该端口发送数据。因此,作为服务端的手机要先开启蓝牙侦听线程,守株待兔。下面是服务端的蓝牙手机处理侦听事务的示例代码:BlueAcceptTask完整代码
//TAG log的标记符
private static final String TAG = "BlueAcceptTask";
private static final String NAME_SECURE = "BluetoothChatSecure";
private static final String NAME_INSECURE = "BluetoothChatInsecure";
private static BluetoothServerSocket mServerSocket; // 声明一个蓝牙服务端套接字对象
private Activity mAct; // 声明一个活动实例
private BlueAcceptListener mListener; // 声明一个蓝牙侦听的监听器对象
@SuppressLint("MissingPermission")
public BlueAcceptTask(final Activity act, final boolean secure,
final BlueAcceptListener listener) {
mAct = act;
mListener = listener;
Log.d(TAG, "init");
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
// 以下提供了三种侦听方法,使得在不同情况下都能获得服务端的Socket对象
try {
if (mServerSocket != null) {
mServerSocket.close();
}
if (secure) { // 安全连接
mServerSocket = adapter.listenUsingRfcommWithServiceRecord(
NAME_SECURE, BluetoothConnector.uuid);
} else { // 不安全连接
mServerSocket = adapter.listenUsingInsecureRfcommWithServiceRecord(
NAME_INSECURE, BluetoothConnector.uuid);
}
} catch (Exception e) { // 遇到异常则尝试第三种侦听方式
e.printStackTrace();
mServerSocket = BluetoothUtil.listenServer(adapter);
}
}
private static final int EXCEPTION_TIME = 1000;//定义异常休眠时间
@Override
public void run() {
Log.d(TAG, "run");
while (true) {
try {
// 如果accept方法有返回,则表示某部设备过来打招呼了
BluetoothSocket socket = mServerSocket.accept();
if (socket != null) { // socket非空,表示名花有主了,赶紧带去见公婆
mAct.runOnUiThread(() -> mListener.onBlueAccept(socket));
break;
}
} catch (Exception e) {
e.printStackTrace();
try {
Thread.sleep(EXCEPTION_TIME);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
}
}
// 定义一个蓝牙侦听的监听器接口,在获得响应之后回调onBlueAccept方法
public interface BlueAcceptListener {
void onBlueAccept(BluetoothSocket socket);
}
首先客户端要与服务端建立连接并打通信道,核心是调用对方设备对象的createRfcommSocket相关方法,从而获得该设备的蓝牙Socket实例。建立蓝牙连接的示例代码如下:BlueConnectTask完整代码
public class BlueConnectTask extends Thread{
private static final String TAG = "BlueConnectTask";
private Activity mAct; // 声明一个活动实例
private BlueConnectListener mListener; // 声明一个蓝牙连接的监听器对象
private BluetoothDevice mDevice; // 声明一个蓝牙设备对象
public BlueConnectTask(Activity act, BluetoothDevice device, BlueConnectListener listener) {
mAct = act;
mListener = listener;
mDevice = device;
}
@Override
public void run() {
// 创建一个对方设备的蓝牙连接器,第一个输入参数为对方的蓝牙设备对象
BluetoothConnector connector = new BluetoothConnector(mDevice, true,
BluetoothAdapter.getDefaultAdapter(), null);
Log.d(TAG, "run");
// 蓝牙连接需要完整的权限,有些机型弹窗提示"***想进行通信",这就不行,日志会报错:
// read failed, socket might closed or timeout, read ret: -1
try {
// 开始连接,并返回对方设备的蓝牙套接字对象BluetoothSocket
BluetoothSocket socket = connector.connect().getUnderlyingSocket();
mAct.runOnUiThread(() -> mListener.onBlueConnect(socket));
} catch (Exception e) {
e.printStackTrace();
}
}
// 定义一个蓝牙连接的监听器接口,用于在成功连接之后调用onBlueConnect方法
public interface BlueConnectListener {
void onBlueConnect(BluetoothSocket socket);
}
}
双方建立连接之后,客户端拿到了蓝牙Socket实例,于是调用getOutputStream方法获得输出流对象,然后即可进行数据交互。客户端发送信息的代码如下所示:BluetoothUtil完整代码
// 向对方设备发送信息
public static void writeOutputStream(BluetoothSocket socket, String message) {
Log.d(TAG, "begin writeOutputStream message=" + message);
try {
OutputStream os = socket.getOutputStream(); // 获得输出流对象
os.write(message.getBytes()); // 往输出流写入字节形式的数据
} catch (Exception e) {
e.printStackTrace();
}
Log.d(TAG, "end writeOutputStream");
}
服务端也没闲着,早在双方建立连接之时便早早开启了消息接收线程,随时准备倾听客户端的呼声。该线程内部调用蓝牙Socket实例的getInputStream方法获得输入流对象,接着从输入流读取数据并送给主线程处理。接收线程处理代码如下:BlueReceiveTask完整代码
@Override
public void run() {
byte[] buffer = new byte[1024];
int bytes;
while (true) {
try {
// 从蓝牙Socket获得输入流,并从中读取输入数据
bytes = mSocket.getInputStream().read(buffer);
// 把字节数据转换为字符串
String message = new String(buffer, 0, bytes);
Log.d(TAG, "message=" + message);
// 将读到的数据通过处理器送回给UI主线程处理
mAct.runOnUiThread(() -> mListener.onBlueReceive(message));
} catch (Exception e) {
e.printStackTrace();
break;
}
}
}
// 定义一个蓝牙接收的监听器接口,在获得响应之后回调onBlueAccept方法
public interface BlueReceiveListener {
void onBlueReceive(String message);
}
此时回到蓝牙主页面,得到消息接收线程传来的数据,把字节形式的数据转换为原始字符串,这样便可在另一部手机上看到发出来的消息。主线程收到消息后的操作代码:BluetoothTransActivity完整代码
// 启动蓝牙消息的接收任务
private void startReceiveTask(BluetoothSocket socket) {
if (socket == null) {
return;
}
tv_discovery.setText("连接成功");
mBlueSocket = socket;
refreshDevice(mBlueSocket.getRemoteDevice(), BlueListAdapter.CONNECTED);
// 创建一个蓝牙消息的接收线程
BlueReceiveTask receiveTask = new BlueReceiveTask(this, mBlueSocket, message -> {
if (!TextUtils.isEmpty(message)) {
// 弹出收到消息的提醒对话框
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("我收到消息啦").setMessage(message);
builder.setPositiveButton("确定", null);
builder.create().show();
}
});
receiveTask.start();
}
网友评论