BlueTooth
根据官方DOC翻译
(本人英语4级没过,看不懂请自行脑补)
Bluetooth API操作流程:
- 搜索其他蓝牙设备
- 查询本地匹配器已经匹配的蓝牙设备
- 建立RFCOMM通道
- 通过Service发现并连接其他设备
- 与其他蓝牙设备进行数据交互
- 管理多个连接
Permission:
- android.permission.BLUETOOTH:Allows applications to connect to paired bluetooth devices
- android.permission.BLUETOOTH_ADMIN:Allows applications to discover and pair bluetooth devices
Setting Up Bluetooth:
确保设备支持蓝牙模块,并已开启
- 获取BluetoothAdapter。整个系统只有一个BluetoothAdapter。如果返回null表示设备不支持蓝牙。
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
// 设备是否支持蓝牙
if (mBluetoothAdapter == null) {
Toast.makeText(this, R.string.not_support, Toast.LENGTH_SHORT).show();
}
- 开启蓝牙:如果没用开启蓝牙,会弹出系统对话框,请求用户开启蓝牙功能。如果开启成功,则返回:Activity.RESULT_OK,否则返回:Activity.RESULT_CANCELED。
- 监听状态:你可以通过监听BluetoothAdapter. ACTION_STATE_CHANGED广播获得蓝牙状态变化信息。广播包含以下额外字段: EXTRA_STATE和EXTRA_PREVIOUS_STATE。常见值包括:STATE_TURNING_ON,STATE_ON,STATE_TURNING_OFF和STATE_OFF。
Finding Devices:
使用BluetoothAdapter,你可以找到远程蓝牙设备。通过查找设备,或查询配对(绑定)的设备列表。查找设备会搜索区域内的蓝牙设备,如果该设备设定为可被发现,则会返回一些信息,通过该信息则可连接设备。当首次建立连接,该设备信息会被保存并可通过API获得,使用MAC地址不需要重新搜索设备(假设设备在可连接范围内)。
- 配对(Pair): 意味着两个设备都知道彼此的存在,有一个共同的链路密钥,可用于进行认证,并且能够建立与彼此的加密连接的。
- 连接(Connect):该装置当前共享一个RFCOMM信道,并能够相互传送数据。Android Bluetooth API's要求设备配对前需要先建立RFCOMM连接(当你通过API建立加密连接配对会自动执行)。
Querying paired devices:
通过getBondedDevices()可返回一组蓝牙配对清单(BluetoothDevice)。
Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices();
if (pairedDevices.size() > 0) {
mDeviceInfoList = new ArrayList<HashMap<String, Object>>();
for (BluetoothDevice device : pairedDevices) {
mDeviceInfoList.add(device.getName() + "\n" + device.getAddress());
}
}
Discovering devices:
通过startDiscovery()可搜索设备,此方法会立即返回Boolean提示是否成功开始搜索。这是一个异步操作,搜索时间大约12秒然后会返回发现的蓝牙设备信息。对于每个搜索到的设备,系统会广播一个ACTION_FOUND Intent,装载的信息包括:EXTRA_DEVICE和EXTRA_CLASS(包含BluetoothDevice和BluetoothClass)。所以你需要注册BroadcastReceiver接收ACTION_FOUND Intent。
// 搜索到蓝牙设备时接收广播提示信息
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
registerReceiver(mReceiver, filter);
// 搜索完成时接收广播提示信息
filter = new IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
registerReceiver(mReceiver, filter);
private BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
// TODO Auto-generated method stub
String action = intent.getAction();
if (BluetoothDevice.ACTION_FOUND.equals(action)) {
// 如果查找到设备
BluetoothDevice device = intent
.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
} else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED
.equals(action)) {
// 查找完成
}
}
};
protected void onDestroy() {
// TODO Auto-generated method stub
super.onDestroy();
if (mBluetoothAdapter != null) {
// 停止搜索设备
mBluetoothAdapter.cancelDiscovery();
}
// 注销广播监听
this.unregisterReceiver(mReceiver);
}
需要注意的是,搜索蓝牙设备非常消耗资源,所以一旦找到对应设备,应该使用cancelDiscovery()停止搜索。如果你已有该设备的连接再执行搜索会节约带宽。
如果你想使本地设备能被其他设备检测到,调用startActivityForResult(Intent,INT)和ACTION_REQUEST_DISCOVERABLE action Intent.。这将发出一个请求,以使通过系统设置发现模式(无需停止应用程序)。默认可见时间为120秒,也可以能过EXTRA_DISCOVERABLE_DURATION Intent extra来修改可见时间(最大值为3600秒,0表示设备始终可见)。系统会弹出对话框提示用户设置可见,并调用onActivityResult()返回结果,如果用户选择确定,则返回时间,如果用户选择否或者出现错误,则返回RESULT_CANCELED。如果未开启蓝牙,使设备可见会自动开启。如果你希望监听到设备可见状态的改变,可以注册BroadcastReceiver ACTION_SCAN_MODE_CHANGED Intent,包含:EXTRA_SCAN_MODE和EXTRA_PREVIOUS_SCAN_MODE。
// 设置设备对外可见,持续30秒
Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
intent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);
startActivityForResult(intent, REQUEST_DISCOVERABLE);
Connectting Devices:
服务器端创建Server Socket,客户端使用服务器端的MAC地址创建连接,当服务器端与与客户端连接时,双方的BluetoothSocket会在同一RFCOMM通道中。此时双方都各自拥有input和output stream,并能实现数据传输。服务器端和客户端分别以不同的方式获得BluetoothSocket。服务器端:当传入连接Accepted时,接收到BluetoothSocket;
客户端:当客户端打开RFCOMM通道时,接收到BluetoothSocket。
有一种实现方法是每台设备均准备作为服务器,均打开Server Socket并监听连接。然后其它设备则以客户端的形式创建连接。
注意:如果两个设备先前未配对,Android框架将发送配对请求通知对话框给用户,应用程序不需要关注的设备是否被配对。您的RFCOMM连接尝试将阻塞,直到用户成功配对,或者如果用户拒绝配对,或者如果配对失败或者超时就会失败。
Connecting as a server:
当你尝试连接两台设备,其中一台需要充当服务器端打开并持有BluetoothServerSocket。Server Socket目的是监听来自客户端的连接请求,当客户端的连接请求被通过,服务器端将提供BluetoothSocket。一旦取得BluetoothSocket,BluetoothServerSocket应该被移除,除非你打算接受更多连接。
设置Server Socket和接收连接请求的基本流程:
- 使用listenUsingRfcommWithServiceRecord(String, UUID)获得BluetoothServerSocket。String是服务器端的名称。UUID它是用来唯一标识应用程序的蓝牙服务。如果与蓝牙串口(Bluetooth serial board)连接可以尝试使用标准的SPP UUID:00001101-0000-1000-8000-00805F9B34FB。但是如果连接的是Android Peer,你需要生成自己的UUID。
- 通过accept()开始监听连接请求。这是一个阻塞调用,当任意连接被同意或者发生异常时则会退出。只有当客户端发送连接请求,并且请求数据中的UUID与Server Socket的UUID才能被同意。如果连接成功,则返回BluetoothSocket。
- 除非你打算接受其他的连接,否则连接成功后,调用close()。这将释放Server Socket及其所有资源,但不会关闭accept()返回的BluetoothSocket。RFCOMM只允许每个通道一个连接的客户端的时间,因此,在大多数情况下,在BluetoothServerSocket通过连接请求后立即调用close()。
- Accept()不能运行于主线程,应该创建子线程执行些方法,另外BluetoothServerSocket和BluetoothSocket所有方法都是线程安全的。
private class AcceptThread extends Thread {
private final BluetoothServerSocket mmServerSocket;
private String mSocketType;
public AcceptThread(boolean secure) {
BluetoothServerSocket tmp = null;
mSocketType = secure ? "Secure" : "Insecure";
// 创建一个新的server socket监听,secure表示是否使用加密方式
try {
if (secure) {
tmp = mAdapter.listenUsingRfcommWithServiceRecord(
NAME_SECURE, MY_UUID_SECURE);
} else {
tmp = mAdapter.listenUsingInsecureRfcommWithServiceRecord(
NAME_INSECURE, MY_UUID_INSECURE);
}
} 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) {
// 新创建一个线程使用Socket
manageConnectedSocket (socket);
mmServerSocket.close();
break;
}
}
}
public void cancel() {
try {
mmServerSocket.close();
} catch (IOException e) {
}
}
}
请注意,当accept()返回的BluetoothSocket,socket已经连接,所以客户端不需要调用connect()。manageConnectedSocket()用于启动线程传输数据。你可能还需要在线程里提供public方法用于关闭BluetoothSocket。
Connecting as a client:
客户端连接服务器端首先要从服务器端获得BluetoothDevice(参考Finding Devices),然后从BluetoothDevice中获得BluetoothSocket并创建连接。基本流程如下:
- 通过createRfcommSocketToServiceRecord(UUID)从BluetoothDevice中获得BluetoothSocket。UUID必须与服务器端的UUID一致。
- 通过connect()启动连接。connect()连接服务器时,服务器端会匹配UUID,如果匹配成功,服务器会同意连接请求并提供RFCOMM能道,此时connect()会返回。Connect()是阻塞方法,所以需要创建子线程执行,如果连接出现任何问题或者超时(12秒),将会抛出错误。需要注意的是,调用connect()时,确保已经停止搜索(cancelDiscovery()),否则性能会明显降低。也可以使用isDiscovering()检查是否正在搜索中。
private class ConnectThread extends Thread {
private final BluetoothSocket mmSocket;
private final BluetoothDevice mmDevice;
private String mSocketType;
public ConnectThread(BluetoothDevice device, boolean secure) {
mmDevice = device;
BluetoothSocket tmp = null;
mSocketType = secure ? "Secure" : "Insecure";
// 通过BluetoothDevel和UUID获得BluetoothSocket
try {
if (secure) {
// 使用加密方式
tmp = device
.createRfcommSocketToServiceRecord(MY_UUID_SECURE);
} else {
// 不使用加密方式,需要API Level 10支持
tmp = device
.createInsecureRfcommSocketToServiceRecord(MY_UUID_INSECURE);
}
} catch (IOException e) {
}
mmSocket = tmp;
}
public void run() {
// 停止搜索,搜索设备会占用大量资源
mAdapter.cancelDiscovery();
// 创建Bluetooth连接
try {
// connect阻塞方法,只会返回成功, 否则抛出异常
mmSocket.connect();
} catch (IOException e) {
try {
mmSocket.close();
} catch (IOException e2) {
}
return;
}
// 完成后重置线程
synchronized (BluetoothChatService.this) {
mConnectThread = null;
}
// 创建一个独立的线程使用Sokcet
manageConnectedSocket(mmSocket);
}
public void cancel() {
try {
mmSocket.close();
} catch (IOException e) {
}
}
}
当使用完BluetoothSocket,调用close()关闭连接释放资源。
Managing a Connection:
当两(多)台设备连接成功后,两台设备都持有BluetoothSocket,这时可以进行数据交互了。使用BluetoothSocket传输数据流程:
- 通过Socket使用InputStream和OutputStream数据。
- 通过read(byte[])和write(byte[])读写数据流。需要注意的是,你要为读写流创建线程,因为read(byte[])和write(byte[])是阻塞方法,read(byte[])会一直阻塞直到所有数据被读出;write(byte[])通常不会阻塞,但如果远和设备没有迅速调用read(byte[])又或者缓冲区已经满,则会造成阻塞。所以你的线程run主要用于读取InputStream。另外提供public方法,以启动写入输出流。
private class ConnectedThread extends Thread {
private final BluetoothSocket mmSocket;
private final InputStream mmInStream;
private final OutputStream mmOutStream;
public ConnectedThread(BluetoothSocket socket, String socketType) {
mmSocket = socket;
InputStream tmpIn = null;
OutputStream tmpOut = null;
// 获取输入和输出流,使用临时对象,因为成员流是final的
try {
tmpIn = socket.getInputStream();
tmpOut = socket.getOutputStream();
} catch (IOException e) {
}
mmInStream = tmpIn;
mmOutStream = tmpOut;
}
public void run() {
Log.i(TAG, "BEGIN mConnectedThread");
byte[] buffer = new byte[1024];
int bytes;
// 当连接成功后,一直监听InputStream
while (true) {
try {
// 从InputStream读取数据
bytes = mmInStream.read(buffer);
// 发送获得的数据至UI Activity
mHandler.obtainMessage(BluetoothChat.MESSAGE_READ, bytes,
-1, buffer).sendToTarget();
} catch (IOException e) {
break;
}
}
}
// 写入数据并发送
public void write(byte[] buffer) {
try {
mmOutStream.write(buffer);
} catch (IOException e) {
}
}
// 关闭连接
public void cancel() {
try {
mmSocket.close();
} catch (IOException e) {
}
}
}
Working with Profiles:
从Android 3.0开始,Bluetooth API包括了对Bluetooth Profiles的支持。Bluetooth Profile是一种基于设备间的蓝牙通信无线协议规范。其中一个例子是Hands-Free profile(免提模式),手机连接无线耳机,两个设备都必须要支持Hands-Free profile。你可以通过实现BluetoothProfile接口编写自己的类来支持特定的Bluetooth profiles。
- Headset:Headset profile提供对手机使用蓝牙耳机的支持。Android提供BluetoothHeadset类,通过进程通讯(IPC)代理控制蓝牙耳机服务。这里包括蓝牙耳机和免提(v1.5)模式。BluetoothHeadset包括支持AT命令。
- A2DP:The Advanced Audio Distribution Profile (A2DP高级音频传输模式)定义了多高质量的音频可以通过蓝牙传输。Android提供BluetoothA2dp类,通过进程通讯(IPC)代理控制蓝牙A2DP服务。
- Health Device:Android 4.0 (API level 14) 支持Bluetooth Health Device Profile (HDP)。它使你能够创建应用,通过蓝牙与Health Device通讯。例如:heart-rate monitors(心脏速率监视器),blood meters(血压计),thermometers(体温计),scales(身体均衡器),等等。
基本步骤如下:
- 获得Adapter(参考Setting Up Bluetooth)
- 通过方法getProfileProxy()建立与模式关联的理对象的连接。在下面的例子中,配置文件代理对象是BluetoothHeadset的一个实例。
- 设置BluetoothProfile.ServiceListener用于监听服务器端连接或断开连接的事件,并通知BluetoothProfile IPC。
- 在方法onServiceConnected()中获得profile proxy的handle对象。
- 一旦获得profile prox对象,你可以用它来监测连接状态,并进行其它相关的操作。
例如,以下代码显示了如何连接到一个BluetoothHeadset代理对象,这样就可以控制Headset profile:
BluetoothHeadset mBluetoothHeadset;
// Get the default adapter
BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
// Establish connection to the proxy.
mBluetoothAdapter.getProfileProxy(context, mProfileListener, BluetoothProfile.HEADSET);
private BluetoothProfile.ServiceListener mProfileListener = new BluetoothProfile.ServiceListener() {
public void onServiceConnected(int profile, BluetoothProfile proxy) {
if (profile == BluetoothProfile.HEADSET) {
mBluetoothHeadset = (BluetoothHeadset) proxy;
}
}
public void onServiceDisconnected(int profile) {
if (profile == BluetoothProfile.HEADSET) {
mBluetoothHeadset = null;
}
}
};
// ... call functions on mBluetoothHeadset
// Close proxy connection after use.
mBluetoothAdapter.closeProfileProxy(mBluetoothHeadset);
Vendor-specific AT commands:
从Android 3.0开始应用程序可以通过注册来接收来自耳机的pre-defined vendor-specific AT commands系统广播(例如:Plantronics +XEVENT command),例如应用程序可以通过广播接收所连接设备的电量水平,并通知用户。创建广播接收器并通过ACTION_VENDOR_SPECIFIC_HEADSET_EVENT intent处理来自耳机的vendor-specific AT commands。
Health Device Profile: