以下内容翻译自Android 6.0蓝牙开发者文档
一、前言
安卓平台支持蓝牙网络功能,允许设备和其他蓝牙设备通过蓝牙无线交换数据。它提供了关于蓝牙的Android Bluetooth API,你可以使用这些API跟其他蓝牙设备点对点或点对多无线连接。
应用可以使用蓝牙API做这些事:
- 扫描其他蓝牙设备
- 列举已配对蓝牙设备的本地蓝牙适配器
- 建立RFCOMM通道
- 通过搜索服务连接到其他设备
- 跟其他设备交换数据
- 管理多个连接
本文档描述的是经典(classic)蓝牙,其更适合在不考虑电量策略时进行安卓设备间的通信。如果需要低能耗功能,可以使用Bluetooth Low Energy(Android 4.3以上-API Level 18)
二、基础知识
本节描述了要进行蓝牙通信的四个必要任务:设置蓝牙、发现设备(已配对设备或可见设备)、连接设备、设备间通信。
所有的蓝牙API都在android.bluetooth包中。
下面简要介绍一些在建立蓝牙连接时需要的类和接口。
- BluetoothAdapter
代表本地蓝牙适配器。BluetoothAdapter是所有蓝牙交互的入口。通过它,可以发现其他蓝牙设备、查询已配对设备、使用已知的MAC地址实例化一个BluetoothDevice、生成一个BluetoothServerSocket以监听其他设备的请求 - BluetoothDevice
表示一个远程蓝牙设备。通过它可以跟远程设备建立连接并获取BluetoothSocket,或者查询远程设备的名称、地址、类型、配对状态等信息 - BluetoothSocket
表示一个蓝牙socket接口(比TCP socket轻),通过它可以获取输入输出流,以跟其他蓝牙设备交换数据 - BluetoothServerSocket
表示一个server socket,可以监听接入请求(比TCP serverSocket轻)。为了在两个设备间建立连接,其中一个设备必须用本类开启一个server socket。当远程蓝牙设备请求连接server socket时,若请求被accept,BluetoothServerSocket会返回一个已连接的BluetoothSocket - BluetoothClass
描述蓝牙设备的特征和能力。它是一个只读的集合,定义了设备主要的属性和支持的服务。然而它并不能可靠描述所有的蓝牙属性和服务,当然在说明设备类型时还是有用的(这段不好翻,读源码后发现,它包括设备类型-如打印机、电话、电脑,也包括设备服务-如BITMASK、POSITIONING、RENDER、NETWORKING等内容) - BluetoothProfile
蓝牙标准规范接口。它是设备间基于蓝牙的连接的标准接口的说明 - BluetoothHeadset
移动设备蓝牙耳机规范。包含Headset和Hands-Free两种 - BluetoothA2dp
高质量音频流规范。A2DP即Advanced Audio Distribution Profile - BluetoothHealth
蓝牙健康设备控制规范 - BluetoothHealthCallback
BluetoothHealth回调抽象类。继承并实现回调方法,可以收到应用注册状态和蓝牙通道状态变化的更新 - BluetoothHealthAppConfiguration
注册了跟远程蓝牙健康设备通信的第三方应用的应用配置 - BluetoothProfile.ServiceListener
可以通过此接口进程间通信,通知使用方服务的连接和断开
三、蓝牙权限
应用要使用蓝牙功能,如请求连接、接受连接、传输数据,必须声明BLUETOOTH权限。
如果应用需要扫描设备或者修改蓝牙设置,必须另外声明BLUETOOTH_ADMIN权限。注意,声明BLUETOOTH_ADMIN权限时必须一起声明BLUETOOTH权限
四、蓝牙设置
使用蓝牙通信前,需要先确认设备是否有蓝牙功能,并开启它。
如果不支持蓝牙功能,你应该关闭所有蓝牙操作。如果支持蓝牙功能,但是未开启,你可以向用户请求打开蓝牙(在不离开你的应用的前提下)。开启蓝牙可以使用BluetoothAdapter完成。操作步骤如下:
- 获取BluetoothAdapter
BluetoothAdapter可以被任何activity获取,使用静态的getDefaultAdapter()方法即可。这个方法返回本地唯一的蓝牙适配器。如果此方法返回null,那么代表本机不支持蓝牙功能 开启蓝牙
使用isEnabled()方法可以查看蓝牙是否启用,若未启用(返回false),可以通过startActivityForResult(),并传入ACTION_REQUEST_ENABLE,之后会显示一个系统设置界面指导用户完成蓝牙启用。示例代码:if (!mBluetoothAdapter.isEnabled()) { Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT); }
之后,系统设置界面后通过onActivityResult的resultCode显示用户操作结果,RESULT_OK表示开启蓝牙成功,RESULT_CANCELED表示开启失败或用户选择不开启
另外,你的应用也可以监听ACTION_STATE_CHANGED广播,当蓝牙状态改变时,系统会发送此广播。此广播包含EXTRA_STATE和EXTRA_PREVIOUS_STATE两个extra,分别包含新的和旧的蓝牙状态。可能的值有STATE_TURNING_ON、STATE_ON、STATE_TURNING_OFF、STATE_OFF。通过监听这个广播可以很方便的获取到蓝牙状态变化
tip:开启设备扫描会自动打开蓝牙,如果你计划在显示开启蓝牙的界面前直接扫描设备,你可以跳过这两步
五、发现设备
你可以通过BluetoothAdapter扫描远程设备或者获取已配对设备。
“设备发现”指扫描本地蓝牙设备并获取其信息的过程。只有其他设备的蓝牙是开启的并且状态为“可发现”,才会回应“设备发现”请求。其他设备回应给“设备发现”请求的信息包括设备名、设备类型、MAC地址等。
第一次连接远程设备时,会发送一个配对请求给用户。设备配对后,设备的基本信息就会被保存,并且可以被蓝牙API读取。使用已知的MAC地址,可以在不扫描设备的情况下与远程设备建立连接。
“配对”和“连接”是不同的。配对意味着两个设备感知到了对方的存在,并交换了某种证明,以跟对方建立加密的连接。而连接意味着设备已经建立了RFCOMM通道,并且可以进行数据传输。在建立RFCOMM连接前,必须先配对(当你使用蓝牙API初始化加密连接时,配对过程会自动进行)。
note:安卓设备默认不是“可发现”的,用户可以通过系统设置使设备在一段时间内“可发现”,另外应用可以通过某种方式请求用户开启“可发现”
5.1查询已配对设备
在启动“设备发现”前最好先通过getBondedDevices方法查询已配对设备,看看是否有目标设备。这个方法会返回一个表示已配对设备的BluetoothDevice的集合
从BluetoothDevice可以获得MAC地址,并使用它建立连接。
5.2发现设备
通过startDiscovery()方法可以发现设备。发现设备过程是异步的,这个方法会立刻返回一个表示“发现”是否成功开始的boolean值。“发现”包括设备扫描(通常会持续12秒)和信息扫描(查询被发现设备的蓝牙名称)两个过程。
你的应用可以通过注册ACTION_FOUND广播接收设备扫描信息。每发现一个设备,系统都会发送ACTION_FOUND广播。这个广播包括EXTRA_DEVICE和EXTRA_CLASS两个extra,分别可以获得BluetoothDeivce和BluetoothClass对象。
注意:扫描设备是个很消耗资源的过程。在找到目标设备并打算连接时,请确保调用cancelDiscovery()方法停止扫描。连接建立后,扫描设备也会消耗连接的带宽,所以请不要在连接后扫描设备
5.3启用“可发现”
如果你想要使本地设备可以被其他设备发现,可以调用startActivityForResult方法,并传入ACTION_REQUEST_DISCOVERABLE,它将打开一个系统设置界面并开启“可发现”模式。“可发现”状态默认持续120秒,你可以通过添加EXTRA_DISCOVERABLE_DURATION extra定义不同的持续时间。应用能设置的最长持续时间为3600秒。如果设置为0则表示设备会一直是“可发现”的。任何大于3600或小于0的值将被自动设置为120。示例如下(开启“可发现”模式300秒):
Intent discoverableIntent = new
Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);
startActivity(discoverableIntent);
会出现一个对话框,请求用户允许设置设备为“可发现”。通过onActivityResult方法的resultCode可以获取用户设置结果,如果用户允许,resultCode为你设置的持续时间,如果用户不允许,resultCode为RESULT_CANCELED
note:如果设备的蓝牙功能未开启,那么开启“可发现”模式后会自动开启蓝牙功能
注册ACTION_SCAN_MODE_CHANGED广播可以获取“可发现”模式改变通知。这个广播包括EXTRA_SCAN_MODE和EXTRA_PREVIOUS_SCAN_MODE两个extra,分别表示新的和旧的扫描模式。可能的值有SCAN_MODE_CONNECTABEL_DISCOVERABLE(可被连接也可被发现),SCAN_MODE_CONNECTABLE(不可被发现但可被连接),SCAN_MODE_NONE(不可被发现也不可被连接)
蓝牙连接后不需要开启“可发现”模式,只有当你的应用开启了一个server socket去监听接入的连接时,开启“可发现”模式才是必要的,因为远程设备在连接你前必须发现你
六、连接设备
为了在两台设备间建立蓝牙连接,你必须实现server端和client端,server开启server socket,client端使用server端的MAC地址建立连接。当server端和client端在同一个RFCOMM通道上拥有已连接的BluetoothSocket时,就可以认为它们已建立连接。使用这个连接,两端的设备可以获得输入输出流并传输数据。
server端和client端获取BluetoothSocket的方式是不同的。server端是在接入连接被accept时获取的,而client端是在开启跟server端的RFCOMM信道时获取的
有一种实现蓝牙连接的技术是,两端的设备都作为一个server监听接入请求,这样任何一端都可以申请连接从而成为client端。当然,也可以一端设备明确的作为一个server,而另一端作为client。
note:在连接时,如果两个设备没有配对过,那么Android框架会自动显示一个配对请求对话框(或通知),所以当你试图连接一个设备时,不需要顾及是否已配对。RFCOMM连接请求会阻塞,直到用户成功配对设备,或者用户拒绝配对,或者超时
6.1server端
server端开启一个BluetoothServerSocket监听接入请求,一旦请求被accept,返回一个已连接的BluetoothSocket。当BluetoothSocket获取成功,BluetoothServerSocket就应该被抛弃,除非你想要accept更多的连接
server端工作基本流程:
调用listenUsingRfcommWithServiceRecord(String ,UUID)方法获取BluetoothServerSocket。其中入参String是你的服务的名称,这个string会自动写到设备上的新的服务发现协议(SDP Sservie Discovery Protocol)数据库中。这个string可以是任意的,可以是你的应用名。UUID也会被写入SDP数据库,并且会作为client端设备连接许可的基础。当client端尝试连接一个设备时,它会用UUID唯一地标识它想要连接的服务。必须UUID符合,连接请求才会被accept
调用accept()方法监听连接请求
accept是一个阻塞方法,它会阻塞直到一个连接请求被accept或者发生异常。只有当远程设备发送的UUID与server socket的UUID符合时连接请求才会被accept。成功后,accept方法会返回一个已连接的BluetoothSocket。调用close()方法,除非你想要accept更多的连接
close方法会释放server socket和它占有的资源。close方法不会关闭BluetoothSocket的连接。不像TCP/IP,RFCOMM每个信道每次仅仅允许一个client建立连接,所以大多数情况下,建议在accept一个连接请求后立刻调用close方法
不要在UI线程调用accept()方法,否则会阻塞线程从而影响应用的用户体验。你可以在一个新的线程做BluetoothSocket和BluetoothServerSocket的相关工作。从其他线程调用BluetoothServerSocket的close方法会使accept方法立刻返回,从而退出阻塞状态。BluetoothServerSocket和BluetoothSocket的所有方法都是线程安全的。
6.2client端
为了初始化跟远程设备(此设备开启了server socket)的连接必须持有一个表示该设备的BluetoothDevice。然后可以使用BluetoothDevice获取BluetoothSocket并获取连接。
简单步骤说明如下:
使用BluetoothDevice的createRfcommSocketToServiceRecord(UUID)方法获取BluetoothSocket。
此方法会初始化一个附属于BluetoothDeivce的BluetoothSocket。参数中的UUID必须跟Server端设备初始化BluetoothServerSocket(即调用istenUsingRfcommWithServiceRecord(String,UUID)方法)时使用的UUID匹配。这里的UUID通常是写入到你的应用中的硬编码的UUID字符串调用connect()方法初始化连接
调用此方法后,系统会根据SDP协议在远程设备上查找UUID符合的服务。如果查找成功且远程设备accept了这个连接,那么connect方法会返回,此时在这个连接上RFCOMM信道已经建立成功。connect方法是阻塞的,如果连接失败或者连接超时(12秒),此方法会抛出一个异常
connect是阻塞方法,不要在主线程调用
note:注意在调用connect方法时要确保停止设备扫描,如果设备扫描没有停止,那么建立连接会变慢甚至失败
七、连接管理
跟远程设备的连接建立后,server端和client端都会获得一个BluetoothSocket,然后你可以在两端之间分享数据。使用BluetoothSocket传输数据的过程如下:
- 分别调用BluetoothSocket的getInputStream()方法和getOutputStream()方法获取InputStream和OutputStream
- 使用read(byte[])方法和write(byte[])方法读写数据
由于read和write方法是阻塞的,你应该使用一个专门的线程去读写流数据。read方法会阻塞直到读到数据。write方法通常不会阻塞,但是当远程设备缓存满了也会阻塞。所以,推荐使用子线程的主循环读数据,此线程的public方法写数据。
八、使用Profiles
从Android 3.0开始,蓝牙API添加了蓝牙profiles支持。蓝牙profile是基于蓝牙连接的设备间的无线接口规范。一个典型例子是Hands-Free profile:如果移动电话要连接到无线耳机,双方都要支持Hands-Free profile。
你可以通过实现BluetoothProfile接口写一个你自己的特殊的蓝牙profile。安卓蓝牙API提供的默认蓝牙profile实现如下:
Headset
Headset profile支持蓝牙头戴设备跟移动电话的连接。Android提供了BluetoothHeadset类,其是一个通过进程间通信控制蓝牙Headset服务的代理。它包括Bluetooth Headset和Hands-Free(v1.5) profile。BluetoothHeadset类支持AT命令。更多信息请看Vendor-specific AT commands部分A2DP
A2DP即The Advanced Audio Distribution Profile,其定义了两个蓝牙设备间的高质量音频通信如何进行。Android提供BluetoothA2dp类,其是通过进程间通信控制蓝牙A2DP的代理Health Device
Android 4.0添加了Health Device Profile(HDP)支持。你的应用可以使用蓝牙跟支持蓝牙功能的健康设备(如心率监控器、血压监控器、体温监控器等等)通信。更多支持设备和相应专业化代码请查看Bluetooth Assigned Numbers(www.bluetooth.org)。注意,这些知识引用自ISO/IEEE 11073-20601[7]的Nomenclature Codes Annex的MDC_DEV_SPEC_PROFILE_*部分。更多有关HDP的东西,请看Health Device Profile部分
使用profile的基本过程如下:
- 获取默认蓝牙适配器BluetoothAdapter
- 使用getProfileProxy()方法跟profile代理建立连接
- 设置BluetoothProfile.ServiceListener。这个listener会在跟服务的连接建立或断开时通知BluetoothProfile的IPC客户端
- 从onServiceConnected()方法回调中获取profile代理对象的引用
- 拿到profile代理对象后,可以使用它监控连接状态,并进行其他跟profile有关的操作
代码实例详见Android 6.0蓝牙开发者文档profile部分
8.1Vendor-specific AT commands
从Android 3.0开始,应用可以通过注册广播的方式获取头戴式设备发送的预制的vendor-specific AT commands(如Plantronics+XEVENT command)。比如,应用可以注册广播,然后获取远程设备的电量信息。使用ACTION_VENDOR_SPECIFIC_HEADSET_EVENT注册广播以获取头戴设备的vendor-specific AT commands
8.2健康设备profile
Android 4.0加入了蓝牙Health Device Profile(HDP)支持。提供的蓝牙Health API包括BluetoothHealth、BluetoothHealthCallback、BluetoothHealthAppComfiguration(当然还有其他蓝牙基础API)
蓝牙Health API涉及以下概念:
名称 | 含义 |
---|---|
source | HDP的一个角色定义。指可以传输医疗数据如体重、血糖、温度等信息给小型设备如安卓手机或平板的健康设备 |
sink | HDP的一个角色定义。指接收医疗数据的小型设备。代码中表现为BluetoothHealthAppConfiguration对象 |
registration | 指向特定的健康设备注册sink |
connection | 指建立健康设备和小型设备之间的通信信道 |
8.3开发HDP应用
开发安卓HDP应用的基本过程如下:
- 获取BluetoothHealth代理对象的引用。像头戴设备和A2DP profile设备一样,调用getProfileProxy()方法、设置BluetoothProfile.ServiceListener和健康profile类型,拿到profile代理对象
- 新建一个BluetoothHealthCallback,然后注册一个代表sink的应用配置(BluetoothHealthAppConfiguration)
- 建立跟健康设备的连接。有些设备会自动建立连接,对它们来说这一步是省略的
- 连接成功后,使用文件descriptor进行读写操作。接收到的数据需要使用实现了IEEE 11073-XXXXX规范的health manager解析
- 结束通信时,关闭通信信道并反注册应用