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: