Android 使用经典蓝牙
Android 使用经典蓝牙
本文内容基本是按照安卓官方文档来进行经典蓝牙的学习,大体都从官方文档粘贴而来,英文部分做了写粗略的翻译,作为自己学习安卓经典蓝牙的一个记录。官方文档可参考https://developer.android.com/guide/topics/connectivity/bluetooth.html
基础知识
Android Bluetooth基础类、API,包含在android.bluetooth包下
- BluetoothAdaper 表示本地蓝牙适配器,是所有蓝牙交互的入口点,
BluetoothAdapter
是所有蓝牙交互的入口点。 利用它可以发现其他蓝牙设备,查询绑定(配对)设备的列表,使用已知的 MAC 地址实例化BluetoothDevice
,以及创建BluetoothServerSocket
以侦听来自其他设备的通信。 - BluetoothDevice 表示远程蓝牙设备。利用它可以通过
BluetoothSocket
请求与某个远程设备建立连接,或查询有关该设备的信息,例如设备的名称、地址、类和绑定状态等。 - BluetoothSocket 表示蓝牙套接字接口(与 TCP
Socket
相似)。这是允许应用通过 InputStream 和 OutputStream 与其他蓝牙设备交换数据的连接点。 - BluetoothServerSocket 表示用于侦听传入请求的开放服务器套接字(类似于 TCP
ServerSocket
)。 要连接两台 Android 设备,其中一台设备必须使用此类开放一个服务器套接字。 当一台远程蓝牙设备向此设备发出连接请求时,BluetoothServerSocket
将会在接受连接后返回已连接的BluetoothSocket
。 - BluetoothClass 描述蓝牙设备的一般特征和功能。 这是一组只读属性,用于定义设备的主要和次要设备类及其服务。 不过,它不能可靠地描述设备支持的所有蓝牙配置文件和服务,而是适合作为设备类型提示
- BluetoothProfile 表示蓝牙配置文件的接口。 蓝牙配置文件是适用于设备间蓝牙通信的无线接口规范。 免提配置文件便是一个示例。 如需了解有关配置文件的详细讨论,请参阅使用配置文件
使用蓝牙
获取蓝牙权限
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
设置蓝牙
-
获取BluetoothAdapter
BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); if (mBluetoothAdapter == null) { // 若为Null,则说明不支持蓝牙,在这里做出相应处理 }
-
启用蓝牙
if (!mBluetoothAdapter.isEnabled()) { Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT); }
若蓝牙为启用,则将显示对话框,请求用户允许启用蓝牙
查找蓝牙
-
查询配对的设备:在执行设备发现之前,有必要查询已配对的设备及,故使用getBondedDevices()方法,其返回一组已配对的BluetoothDevice
Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices(); // 如果有配对的设备 if (pairedDevices.size() > 0) { // 遍历匹配设备组 for (BluetoothDevice device : pairedDevices) { // 将已配对设备的名称及地址存储在一个ArrayAdapter中 mArrayAdapter.add(device.getName() + "\n" + device.getAddress()); } }
-
发现设备:要开始发现设备,只需调用
startDiscovery()
。该进程为异步进程,并且该方法会立即返回一个布尔值,指示是否已成功启动发现操作。 发现进程通常包含约 12 秒钟的查询扫描,之后对每台发现的设备进行页面扫描,以检索其蓝牙名称。您的应用必须针对ACTION_FOUND
Intent 注册一个 BroadcastReceiver,以便接收每台发现的设备的相关信息。 针对每台设备,系统将会广播ACTION_FOUND
Intent。此 Intent 将携带额外字段EXTRA_DEVICE
和EXTRA_CLASS
,二者分别包含BluetoothDevice
和BluetoothClass
。 例如,下面说明了在发现设备时如何注册以处理广播。// 为 ACTION_FOUND 创建一个广播接收器 private final BroadcastReceiver mReceiver = new BroadcastReceiver() { public void onReceive(Context context, Intent intent) { String action = intent.getAction(); //当发现一个蓝牙设备时 if (BluetoothDevice.ACTION_FOUND.equals(action)) { //利用EXTRA_DEVICE字段, 从intent中获取该设备实例 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); // 使用getName和getAddress方法获取蓝牙设备名和地址 mArrayAdapter.add(device.getName() + "\n" + device.getAddress()); } } }; //注册该广播 IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND); //搜索完成 filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED); registerReceiver(mReceiver, filter); // 要在onDestry方法中取消注册该广播
//取消注册该广播 @Override protected void onDestroy() { super.onDestroy(); unregisterReceiver(mBtFoundReceiver); }
-
启用本地可检测性(可选)
-
如果您希望将本地设备设为可被其他设备检测到,请使用
ACTION_REQUEST_DISCOVERABLE
操作 Intent 调用startActivityForResult(Intent, int)
。 这将通过系统设置发出启用可检测到模式的请求(无需停止您的应用)。 默认情况下,设备将变为可检测到并持续 120 秒钟。 您可以通过添加EXTRA_DISCOVERABLE_DURATION
Intent Extra 来定义不同的持续时间。 应用可以设置的最大持续时间为 3600 秒,值为 0 则表示设备始终可检测到。 任何小于 0 或大于 3600 的值都会自动设为 120 秒。//设置持续时间为300秒 Intent discoverableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE); discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300); startActivity(discoverableIntent);
上述操作将显示对话框,请求用户允许设备设为可检测到,若同意,则您的 Activity 将会收到对
onActivityResult())
回调的调用,其结果代码等于设备可检测到的持续时间。 如果用户响应“No”或出现错误,结果代码将为RESULT_CANCELED
。如果设备尚未启用蓝牙,则在启用设备可检测性将会自动启用蓝牙。仅当您希望您的应用托管将用于接受传入连接的服务器套接字时,才有必要启用可检测性,因为远程设备必须能够发现该设备,然后才能发起连接。
-
连接设备
要在两台设备上的应用之间创建连接,必须同时实现服务器端和客户端机制,因为其中一台设备必须开放服务器套接字,而另一台设备必须发起连接(使用服务器设备的 MAC 地址发起连接)。 当服务器和客户端在同一 RFCOMM 通道上分别拥有已连接的 BluetoothSocket
时,二者将被视为彼此连接。 这种情况下,每台设备都能获得输入和输出流式传输,并且可以开始传输数据,在有关管理连接的部分将会讨论这一主题。 本部分介绍如何在两台设备之间发起连接。
连接为服务器
当您需要连接两台设备时,其中一台设备必须通过保持开放的 BluetoothServerSocket
来充当服务器。 服务器套接字的用途是侦听传入的连接请求,并在接受一个请求后提供已连接的 BluetoothSocket
。 从 BluetoothServerSocket
获取 BluetoothSocket
后,可以(并且应该)舍弃 BluetoothServerSocket
,除非您需要接受更多连接。
-
基本流程
-
通过调用
listenUsingRfcommWithServiceRecord(String, UUID)
获取BluetoothServerSocket
。- UUID: 通用唯一标识符,是用于唯一标识信息的字符串ID的128为标准化格式
- UUID将作为与客户端设备连接协议的基础,即当客户端尝试连接此设备时,他会携带一个想要连接的服务的UUID,两个UUID必须匹配
-
调用accept()开始侦听连接请求,操作成功,accept将返回已连接的BluetoothSocket
-
调用
close()
。这将释放服务器套接字及其所有资源,但不会关闭accept()
所返回的已连接的BluetoothSocket
-
注意:accept()不应再主Activity UI线程中执行,因为它是阻塞调用。应开启一个新线程,使用
BluetoothServerSocket
或BluetoothSocket
完成所有工作。要终止accept()
等被阻塞的调用,请通过另一个线程在BluetoothServerSocket
(或BluetoothSocket
)上调用close()
,被阻塞的调用将会立即返回//一个用于接受传入连接的服务器组件的简化线程 private class AcceptThread extends Thread { private final BluetoothServerSocket mmServerSocket; public AcceptThread() { // 使用一个临时变量,之后再将它赋值给mmServerSocket,因为mmServerSocket是一个final变量 BluetoothServerSocket tmp = null; try { // MY_UUID是此应用的UUID串,同时也被客户端使用 tmp = mBluetoothAdapter.listenUsingRfcommWithServiceRecord(NAME, MY_UUID); } catch (IOException e) { } //赋值 mmServerSocket = tmp; } public void run() { BluetoothSocket socket = null; // 持续侦听直到返回了一个socket或者发生异常 while (true) { try { socket = mmServerSocket.accept(); } catch (IOException e) { break; } // 连接建立成功 if (socket != null) { // 在一个单独的线程中管理连接,manageConnectedSocket为虚构的方法,它将启动用于传输数据的线程 manageConnectedSocket(socket); mmServerSocket.close(); break; } } } /** 将取消侦听socket,并使进程关闭 */ public void cancel() { try { mmServerSocket.close(); } catch (IOException e) { } } }
-
连接为客户端
要发起与远程设备(保持开放的服务器套接字的设备)的连接,必须首先获取表示该远程设备的 BluetoothDevice
对象。(在前面有关查找设备的部分介绍了如何获取 BluetoothDevice
)。 然后必须使用 BluetoothDevice
来获取 BluetoothSocket
并发起连接。
基本过程
-
使用
BluetoothDevice
,通过调用createRfcommSocketToServiceRecord(UUID)
获取BluetoothSocket
。此处使用的UUID必须与服务器设备在使用listenUsingRfcommWithServiceRecord(String, UUID)
开放其BluetoothServerSocket
时所用的 UUID 相匹配。 要使用相同的 UUID,只需将该 UUID 字符串以硬编码方式编入应用,然后通过服务器代码和客户端代码引用该字符串。 -
通过connect()发起连接。执行此调用时,系统将会在远程设备上执行 SDP 查找,以便匹配 UUID。 如果查找成功并且远程设备接受了该连接,它将共享 RFCOMM 通道以便在连接期间使用,并且
connect()
将会返回。 此方法为阻塞调用。 如果由于任何原因连接失败或connect()
方法超时(大约 12 秒之后),它将会引发异常。由于
connect()
为阻塞调用,因此该连接过程应始终在主 Activity 线程以外的线程中执行注意:注:在调用
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) { } } }
管理连接
- 获取
InputStream
和OutputStream
,二者分别通过套接字以及getInputStream()
和getOutputStream()
来处理数据传输。 - 使用
read(byte[])
和write(byte[])
读取数据并写入到流式传输。
//传输内容示例
private class ConnectedThread extends Thread {
private final BluetoothSocket mmSocket;
private final InputStream mmInStream;
private final OutputStream mmOutStream;
public ConnectedThread(BluetoothSocket socket) {
//获取socket及输入输出流
mmSocket = socket;
InputStream tmpIn = null;
OutputStream tmpOut = null;
try {
tmpIn = socket.getInputStream();
tmpOut = socket.getOutputStream();
} catch (IOException e) { }
mmInStream = tmpIn;
mmOutStream = tmpOut;
}
public void run() {
byte[] buffer = new byte[1024];
int bytes; 获取read()返回的数据
// 持续侦听输入流,知道发生异常
while (true) {
try {
从输入流中读取数据
bytes = mmInStream.read(buffer);
// 将包含的字节流发送到UI activity
mHandler.obtainMessage(MESSAGE_READ, bytes, -1, buffer)
.sendToTarget();
} catch (IOException e) {
break;
}
}
}
/* 在主Activity调用此方法,用以向远程设备发送数据 */
public void write(byte[] bytes) {
try {
mmOutStream.write(bytes);
} catch (IOException e) { }
}
/* 在主activity调用此方法用以关闭连接*/
public void cancel() {
try {
mmSocket.close();
} catch (IOException e) { }
}
}