这篇文章总结通过蓝牙实现两台pad间数据传输
文章基于传统蓝牙,而不是BLE。
蓝牙应该是移动设备经常用到的功能模块,Android Bluetooth API 是Android提供的用来实现点到点和多点无线功能。
使用 Bluetooth API,Android 应用可执行以下操作:
- 扫描其他蓝牙设备
- 查询本地蓝牙适配器的配对蓝牙设备
- 建立 RFCOMM 通道
- 通过服务发现连接到其他设备
- 与其他设备进行双向数据传输
- 管理多个连接
蓝牙权限
<manifest ... >
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
...
</manifest>
1.设置蓝牙
您需要验证设备支持蓝牙,并且如果支持确保将其启用,然后您的应用才能通过蓝牙进行通信。
获取 BluetoothAdapter
所有蓝牙 Activity 都需要 BluetoothAdapter。BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); if (mBluetoothAdapter == null) { // Device does not support Bluetooth }
启用蓝牙
下一步,您需要确保已启用蓝牙。if (!mBluetoothAdapter.isEnabled()) { Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT); }
2.搜索蓝牙设备
要开始发现设备,只需调用 startDiscovery()。该进程为异步进程,并且该方法会立即返回一个布尔值,指示是否已成功启动发现操作。 发现进程通常包含约 12 秒钟的查询扫描,之后对每台发现的设备进行页面扫描,以检索其蓝牙名称。
/**
* 发现蓝牙的广播
*/
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if(BluetoothDevice.ACTION_FOUND.equals(action)){
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
//todo处理搜索到的蓝牙设备
}
}
};
在Activity或者Fragment中注册广播
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
registerReceiver(mReceiver, filter);
3.连接设备
要在两台设备上的应用之间创建连接,必须同时实现服务器端和客户端机制,因为其中一台设备必须开放服务器套接字,而另一台设备必须发起连接(使用服务器设备的 MAC 地址发起连接)。
服务器设备和客户端设备分别以不同的方法获得需要的 BluetoothSocket。服务器将在传入连接被接受时收到套接字。 客户端将在其打开到服务器的 RFCOMM 通道时收到该套接字。
注:如果两台设备之前尚未配对,则在连接过程中,Android 框架会自动向用户显示配对请求通知或对话框。因此,在尝试连接设备时,您的应用无需担心设备是否已配对。 您的 RFCOMM 连接尝试将被阻塞,直至用户成功完成配对或配对失败(包括用户拒绝配对、配对失败或超时)。
- 连接为服务器
当您需要连接两台设备时,其中一台设备必须通过保持开放的 BluetoothServerSocket 来充当服务器。
- 1.通过调用 listenUsingRfcommWithServiceRecord(String, UUID) 获取 BluetoothServerSocket。
当客户端尝试连接此设备时,它会携带能够唯一标识其想要连接的服务的 UUID。 两个 UUID 必须匹配,在下一步中,连接才会被接受。 - 通过调用 accept() 开始侦听连接请求。
- 除非您想要接受更多连接,否则请调用 close()。
这将释放服务器套接字及其所有资源,但不会关闭 accept() 所返回的已连接的 BluetoothSocket。 与 TCP/IP 不同,RFCOMM 一次只允许每个通道有一个已连接的客户端,因此大多数情况下,在接受已连接的套接字后立即在 BluetoothServerSocket 上调用 close() 是行得通的。
- 1.通过调用 listenUsingRfcommWithServiceRecord(String, UUID) 获取 BluetoothServerSocket。
以下是一个用于接受传入连接的服务器组件的简化线程:
private class AcceptThread extends Thread {
private final BluetoothServerSocket mmServerSocket;
public AcceptThread() {
// Use a temporary object that is later assigned to mmServerSocket,
// because mmServerSocket is final
BluetoothServerSocket tmp = null;
try {
// MY_UUID is the app's UUID string, also used by the client code
tmp = mBluetoothAdapter.listenUsingRfcommWithServiceRecord(NAME, MY_UUID);
} catch (IOException e) { }
mmServerSocket = tmp;
}
public void run() {
BluetoothSocket socket = null;
// Keep listening until exception occurs or a socket is returned
while (true) {
try {
socket = mmServerSocket.accept();
} catch (IOException e) {
break;
}
// If a connection was accepted
if (socket != null) {
// Do work to manage the connection (in a separate thread)
manageConnectedSocket(socket);
mmServerSocket.close();
break;
}
}
}
/** Will cancel the listening socket, and cause the thread to finish */
public void cancel() {
try {
mmServerSocket.close();
} catch (IOException e) { }
}
}
注意上面是简化线程,也是网上搜索到最多的方法。但是其稳定性真的很差劲。
- 连接为客户端
要发起与远程设备(保持开放的服务器套接字的设备)的连接,必须首先获取表示该远程设备的 BluetoothDevice 对象。(在前面有关查找设备的部分介绍了如何获取 BluetoothDevice)。 然后必须使用 BluetoothDevice 来获取 BluetoothSocket 并发起连接。
- 1.使用 BluetoothDevice,通过调用 createRfcommSocketToServiceRecord(UUID) 获取 BluetoothSocket。
- 2.通过调用 connect() 发起连接。
以下是发起蓝牙连接的线程的基本示例:
private class ConnectThread extends Thread {
private final BluetoothSocket mmSocket;
private final BluetoothDevice mmDevice;
public ConnectThread(BluetoothDevice device) {
// Use a temporary object that is later assigned to mmSocket,
// because mmSocket is final
BluetoothSocket tmp = null;
mmDevice = device;
// Get a BluetoothSocket to connect with the given BluetoothDevice
try {
// MY_UUID is the app's UUID string, also used by the server code
tmp = device.createRfcommSocketToServiceRecord(MY_UUID);
} catch (IOException e) { }
mmSocket = tmp;
}
public void run() {
// Cancel discovery because it will slow down the connection
mBluetoothAdapter.cancelDiscovery();
try {
// Connect the device through the socket. This will block
// until it succeeds or throws an exception
mmSocket.connect();
} catch (IOException connectException) {
// Unable to connect; close the socket and get out
try {
mmSocket.close();
} catch (IOException closeException) { }
return;
}
// Do work to manage the connection (in a separate thread)
manageConnectedSocket(mmSocket);
}
/** Will cancel an in-progress connection, and close the socket */
public void cancel() {
try {
mmSocket.close();
} catch (IOException e) { }
}
}
代码很容易看懂,就不做过多解释了。
4.管理连接
在成功连接两台(或更多台)设备后,每台设备都会有一个已连接的 BluetoothSocket。 这一点非常有趣,因为这表示您可以在设备之间共享数据。 利用 BluetoothSocket,传输任意数据的一般过程非常简单:
- 获取 InputStream 和 OutputStream,二者分别通过套接字以及 getInputStream() 和 getOutputStream() 来处理数据传输。
- 使用 read(byte[]) 和 write(byte[]) 读取数据并写入到流式传输。
ok,至此,我们基本了解了传统蓝牙数据传输常用的API以及一些核心API的使用,接下来具体看我在项目中如何处理数据传输以及解决蓝牙Socket的稳定性。
在了解了上面一套机制以后,最开始我也是使用上面的简单实例进行蓝牙数据的传输与接受,但在传输过程中经常会出现连接异常,Socket断开等各种异常。经过不断的寻找解决方案,最后发现了官方一个通过蓝牙进行设备聊天的demo,仔细研究发现主要是通过一个自己封装的BluetoothChat