Android Bluetooth
参考
- Android 蓝牙开发(1)
- android蓝牙耳机下的语音(输入/识别)及按键监听
工具类
C_BluetoothUtils
概念
相关权限
权限 | 说明 | 动态申请 |
---|---|---|
android.permission.BLUETOOTH | 蓝牙使用权限 | 否 |
android.permission.BLUETOOTH_ADMIN | 蓝牙管理权限 | 否 |
android.permission.BLUETOOTH_PRIVILEGED | 允许用户未操作情况下配对蓝牙 - 可忽略 https://www.jianshu.com/p/1bb4edb00479 | 否 |
相关类
常用类
类 | 说明 |
---|---|
BluetoothAdapter | 本地蓝牙适配器,蓝牙交互的入口,整个系统只有一个 |
BluetoothDevice | 蓝牙设备 |
BluetoothSocket | 与蓝牙的套接字接口,与TCPSocket类似,可通过InputStream 和OutputStream 与蓝牙进行数据交换 |
BluetoothServerSocket | 用于作为侦听传入请求的套接字服务器,与ServerSocket类似,两个Android手机需要配对,那么其中一个就需要开启BluetoothServerSocket等待另一手机发出连接请求 |
BluetoothClass | 描述蓝牙的一般特性和功能,一组只读属性,用于定义设备的主要和次要设备类及其服务 |
配置文件相关
类 | 说明 |
---|---|
BluetoothProfile | 蓝牙配置文件的接口。蓝牙配置文件是适用于设备间蓝牙通信的无线接口规范。 |
BluetoothHeadset | 提供蓝牙耳机支持,以便与手机配合使用。其中包括蓝牙耳机和免提(1.5版)配置文件。 BluetoothProfile 的实现类 |
BluetoothGatt | 与低功耗蓝牙通信有关的配置文件代理。 BluetoothProfile 的实现类 |
BlutoothA2dp | 定义高质量音频如何通过蓝牙连接和流式传输,从一台设备传输到另一台设备。“A2DP”代表高级音频分发配置文件。 BluetoothProfile 的实现类 |
BluetoothHealthCallback | 用于实现 BluetoothHealth 回调的抽象类。必须扩展此类并实现回调方法,以接收关于应用注册状态和蓝牙通道状态变化的更新内容。 |
BluetoothHealthAppConfiguration | 表示第三方蓝牙健康应用注册的应用配置,以便与远程蓝牙健康设备通信 |
BluetoothProfile.ServiceListener | 在 BluetoothProfile IPC 客户端连接到服务(即,运行特定配置文件的内部服务)或断开服务连接时向其发送通知的接口。 |
相关术语
相关广播
不同配置文件有不同广播,可以在 android.bluetooth.BluetoothProfile
的子类中看到对应的广播
需要等做到什么再完善
常用
广播 | 子字段 | 说明 |
---|---|---|
BluetoothAdapter.ACTION_DISCOVERY_STARTED | 开始扫描 | |
BluetoothAdapter.ACTION_DISCOVERY_FINISHED | 结束扫描 | |
BluetoothAdapter.ACTION_STATE_CHANGED | EXTRA_STATE: 当前状态 | 蓝牙开关状态更改 |
BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED | EXTRA_DEVICE:获取BluetoothDevice EXTRA_CONNECTION_STATE: 当前连接状态 |
设备连接状态改变 |
BluetoothAdapter.ACTION_SCAN_MODE_CHANGED | EXTRA_SCAN_MODE:当前模式 | 扫描模式更改 |
BluetoothDevice.ACTION_FOUND | EXTRA_DEVICE:获取BluetoothDevice EXTRA_CLASS:获取BluetoothClass |
找到蓝牙设备 |
BluetoothDevice.ACTION_BOND_STATE_CHANGED | EXTRA_DEVICE:获取BluetoothDevice EXTRA_BOND_STATE:当前绑定状态 |
绑定状态改变 |
配置文件
广播 | 说明 |
---|---|
BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED | 录音状态更改 |
设备类型
BluetoothDevice.getType()
类型 | 值 | 说明 |
---|---|---|
DEVICE_TYPE_UNKNOWN | 0 | 未知类型 |
DEVICE_TYPE_CLASSIC | 1 | 普通BR/EDR设备 |
DEVICE_TYPE_LE | 2 | LE低功耗 |
DEVICE_TYPE_DUAL | 3 | BR/EDR 和 LE低功耗双类型 |
设备主要类型
BluetoothDevice.getBluetoothClass().getMajorDeviceClass()
类型 | 值 | 说明 |
---|---|---|
COMPUTER | 0x0100 | 电脑 |
PHONE | 0x0200 | 手机 |
AUDIO_VIDEO | 0x0400 | 媒体耳机之类的 |
WEARABLE | 0x0700 | 可穿戴手表 |
PERIPHERAL | 0x0900 | 外设 |
HEALTH MISC NETWORKING IMAGING TOY UNCATEGORIZED |
0x0900 0x0000 0x0300 0x0600 0x0800 0x1F00 |
其他(不能确定干嘛的) |
常用固定的UUID
蓝牙串口服务(SPP)
SerialPortServiceClass_UUID = '{00001101-0000-1000-8000-00805F9B34FB}'
LANAccessUsingPPPServiceClass_UUID = '{00001102-0000-1000-8000-00805F9B34FB}'
拨号网络服务
DialupNetworkingServiceClass_UUID = '{00001103-0000-1000-8000-00805F9B34FB}'
信息同步服务
IrMCSyncServiceClass_UUID = '{00001104-0000-1000-8000-00805F9B34FB}'
SDP_OBEXObjectPushServiceClass_UUID = '{00001105-0000-1000-8000-00805F9B34FB}'
文件传输服务
OBEXFileTransferServiceClass_UUID = '{00001106-0000-1000-8000-00805F9B34FB}'
IrMCSyncCommandServiceClass_UUID = '{00001107-0000-1000-8000-00805F9B34FB}'
SDP_HeadsetServiceClass_UUID = '{00001108-0000-1000-8000-00805F9B34FB}'
CordlessTelephonyServiceClass_UUID = '{00001109-0000-1000-8000-00805F9B34FB}'
SDP_AudioSourceServiceClass_UUID = '{0000110A-0000-1000-8000-00805F9B34FB}'
SDP_AudioSinkServiceClass_UUID = '{0000110B-0000-1000-8000-00805F9B34FB}'
SDP_AVRemoteControlTargetServiceClass_UUID = '{0000110C-0000-1000-8000-00805F9B34FB}'
SDP_AdvancedAudioDistributionServiceClass_UUID = '{0000110D-0000-1000-8000-00805F9B34FB}'
SDP_AVRemoteControlServiceClass_UUID = '{0000110E-0000-1000-8000-00805F9B34FB}'
VideoConferencingServiceClass_UUID = '{0000110F-0000-1000-8000-00805F9B34FB}'
IntercomServiceClass_UUID = '{00001110-0000-1000-8000-00805F9B34FB}'
蓝牙传真服务
FaxServiceClass_UUID = '{00001111-0000-1000-8000-00805F9B34FB}'
HeadsetAudioGatewayServiceClass_UUID = '{00001112-0000-1000-8000-00805F9B34FB}'
WAPServiceClass_UUID = '{00001113-0000-1000-8000-00805F9B34FB}'
WAPClientServiceClass_UUID = '{00001114-0000-1000-8000-00805F9B34FB}'
蓝牙打印服务
HCRPrintServiceClass_UUID = '{00001126-0000-1000-8000-00805F9B34FB}'
HCRScanServiceClass_UUID = '{00001127-0000-1000-8000-00805F9B34FB}'
CommonISDNAccessServiceClass_UUID = '{00001128-0000-1000-8000-00805F9B34FB}'
VideoConferencingGWServiceClass_UUID = '{00001129-0000-1000-8000-00805F9B34FB}'
UDIMTServiceClass_UUID = '{0000112A-0000-1000-8000-00805F9B34FB}'
UDITAServiceClass_UUID = '{0000112B-0000-1000-8000-00805F9B34FB}'
AudioVideoServiceClass_UUID = '{0000112C-0000-1000-8000-00805F9B34FB}'
SIMAccessServiceClass_UUID = '{0000112D-0000-1000-8000-00805F9B34FB}'
PnPInformationServiceClass_UUID = '{00001200-0000-1000-8000-00805F9B34FB}'
GenericNetworkingServiceClass_UUID = '{00001201-0000-1000-8000-00805F9B34FB}'
GenericFileTransferServiceClass_UUID = '{00001202-0000-1000-8000-00805F9B34FB}'
GenericAudioServiceClass_UUID = '{00001203-0000-1000-8000-00805F9B34FB}'
GenericTelephonyServiceClass_UUID = '{00001204-0000-1000-8000-00805F9B34FB}'
个人局域网服务
PANUServiceClass_UUID = '{00001115-0000-1000-8000-00805F9B34FB}'
NAPServiceClass_UUID = '{00001116-0000-1000-8000-00805F9B34FB}'
GNServiceClass_UUID = '{00001117-0000-1000-8000-00805F9B34FB}'
DirectPrintingServiceClass_UUID = '{00001118-0000-1000-8000-00805F9B34FB}'
ReferencePrintingServiceClass_UUID = '{00001119-0000-1000-8000-00805F9B34FB}'
ImagingServiceClass_UUID = '{0000111A-0000-1000-8000-00805F9B34FB}'
ImagingResponderServiceClass_UUID = '{0000111B-0000-1000-8000-00805F9B34FB}'
ImagingAutomaticArchiveServiceClass_UUID = '{0000111C-0000-1000-8000-00805F9B34FB}'
ImagingReferenceObjectsServiceClass_UUID = '{0000111D-0000-1000-8000-00805F9B34FB}'
SDP_HandsfreeServiceClass_UUID = '{0000111E-0000-1000-8000-00805F9B34FB}'
HandsfreeAudioGatewayServiceClass_UUID = '{0000111F-0000-1000-8000-00805F9B34FB}'
DirectPrintingReferenceObjectsServiceClass_UUID = '{00001120-0000-1000-8000-00805F9B34FB}'
ReflectedUIServiceClass_UUID = '{00001121-0000-1000-8000-00805F9B34FB}'
BasicPringingServiceClass_UUID = '{00001122-0000-1000-8000-00805F9B34FB}'
PrintingStatusServiceClass_UUID = '{00001123-0000-1000-8000-00805F9B34FB}'
人机输入服务
HumanInterfaceDeviceServiceClass_UUID = '{00001124-0000-1000-8000-00805F9B34FB}'
HardcopyCableReplacementServiceClass_UUID = '{00001125-0000-1000-8000-00805F9B34FB}'
操作
获取蓝牙适配器
BluetoothManager bluetoothmanger = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
BluetoothAdapter bluetoothadapter = bluetoothmanger.getAdapter();
或
BluetoothAdapter defaultAdapter = BluetoothAdapter.getDefaultAdapter();
if(defaultAdapter == null){
// 当前设备不支持蓝牙
}
获取蓝牙状态,打开蓝牙
有三种方式
申请蓝牙权限,打开蓝牙
弹窗请求打开蓝牙
请求打开蓝牙,并设置可见状态
// 蓝牙是否打开
boolean isOpen = defaultAdapter.isEnabled();
if(!isOpen){
// 1. 申请蓝牙权限 并打开蓝牙
boolean enable = defaultAdapter.enable();
// 2. 弹窗请求打开蓝牙
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBtIntent,REQUEST_OPEN_CODE);
// 3. 如果蓝牙未打开,会自动申请打开蓝牙
// 如果用户拒绝,会在 onActivityResult 中返回 RESULT_CANCELED
Intent discoverableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION,300);// 设置可见时长,默认120秒
startActivityForResult(discoverableIntent,REQUEST_CODE);
}
查找设备
/**
* 查找设备
* 如果返回 false 尝试动态申请 {@link android.Manifest.permission#ACCESS_FINE_LOCATION} 权限
*
* @return 查找成功返回 true,否则返回 false
*/
public boolean discoveryDevice() {
if (defaultAdapter.isDiscovering()){
defaultAdapter.cancelDiscovery();
}
return defaultAdapter.startDiscovery();
}
取消查找
/**
* 取消查找
* @return 取消结果
*/
public boolean cancelDiscovery() {
return defaultAdapter.cancelDiscovery();
}
获取已绑定的设备
/**
* 查找已经绑定的设备
* @return 设备列表
*/
public List getBoundDevice() {
Set bondedDevices = defaultAdapter.getBondedDevices();
return new ArrayList<>(bondedDevices);
}
配对
/**
* 配对设备
* @param device 目标设备
* @return 配对结果
*/
public boolean createBound(BluetoothDevice device) {
if (defaultAdapter.isDiscovering()) {
defaultAdapter.cancelDiscovery();
}
try {
Method createBond = device.getClass().getMethod("createBond");
createBond.invoke(device);
return true;
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
return false;
}
}
取消配对
/**
* 取消与设备的配对
* @param device 目标设备
* @return 配对结果
*/
public boolean cancelBound(BluetoothDevice device) {
if (defaultAdapter.isDiscovering()) {
defaultAdapter.cancelDiscovery();
}
try {
Method removeBond = device.getClass().getMethod("removeBond");
removeBond.invoke(device);
return true;
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
return false;
}
获取当前连接的设备
/**
* 获取当前连接中的设备
* 如果有多个设备连接,仅返回第一个
*
* @return 连接中的设备
*/
public BluetoothDevice getConnectedDevice() {
List boundDevice = BluetoothUtils.getInstance().getBoundDevice();
for (int i = 0; i < boundDevice.size(); i++) {
BluetoothDevice device = boundDevice.get(i);
boolean connected = BluetoothUtils.getInstance().isConnected(device);
if (connected) {
return device;
}
}
return null;
}
获取设备电量
/**
* 获取设备电量
* @param device 设备
* @return 电量
*/
public int getDeviceBatteryLevel(BluetoothDevice device) {
int level = 0;
if (device == null) {
return level;
}
try {
Method getBatteryLevel = device.getClass().getMethod("getBatteryLevel", new Class[]{});
getBatteryLevel.setAccessible(true);
level = (int) getBatteryLevel.invoke(device);
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
return level;
}
获取设备主要类型
/**
* 获取主要设备类型
* 通过 {@link BluetoothDevice#getBluetoothClass()} 获取 BluetoothClass
* 通过 {@link android.bluetooth.BluetoothClass#getMajorDeviceClass()} 获取设备的主要类型
* 根据主要类型设置蓝牙列表的icon
* 或判断设备类型是否为需要的设备等
* @param device {@link BluetoothDevice}
* @return 设备类型
*/
public String getDeviceClassIc(BluetoothDevice device) {
// 电脑
final int COMPUTER = 0x0100;
// 手机
final int PHONE = 0x0200;
// 媒体设备
final int AUDIO_VIDEO = 0x0400;
// 可穿戴设备
final int WEARABLE = 0x0700;
// 健康设备(BLE?)
final int HEALTH = 0x0900;
// 外设
final int PERIPHERAL = 0x0500;
// 其他
final int MISC = 0x0000;
final int NETWORKING = 0x0300;
final int IMAGING = 0x0600;
final int TOY = 0x0800;
final int UNCATEGORIZED = 0x1F00;
String deviceClassType = null;
switch (device.getBluetoothClass().getMajorDeviceClass()) {
case PERIPHERAL:
deviceClassType = "外接设备";
break;
case COMPUTER:
deviceClassType = "电脑";
break;
case PHONE:
deviceClassType = "手机";
break;
case AUDIO_VIDEO:
deviceClassType = "媒体设备";
break;
case WEARABLE:
deviceClassType = "健康设备";
break;
default:
deviceClassType = "其他";
break;
}
return deviceClassType;
}
设备通信
需要像套接字一样作为客户端或服务器与其他设备连接
如果两个设备之前没有配对,那么系统会自动发起配对
配对成功后即可像TCP一样进行数据传输
作为服务器
try {
UUID uuid = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
/**
* 创建一个不安全的服务器,
* 在2.3版本之前连接未加密,如果想加密则使用 listenUsingRfcommWithServiceRecord 创建服务器
* 在2.3版本之后连接被加密
* @name 自定义的可识别名称, 系统会将其写入设备上的新服务发现协议(SDP)数据库条目中
* @uuid 同样被写入SDP中, 客户端需要持有相同的UUID才能进行连接
*/
final BluetoothServerSocket bluetoothServerSocket = defaultAdapter.listenUsingInsecureRfcommWithServiceRecord(context.getPackageName(), uuid);
new Thread(new Runnable() {
@Override
public void run() {
try {
// 等待设备连接
BluetoothSocket accept = bluetoothServerSocket.accept();
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
} catch (IOException e) {
e.printStackTrace();
}
作为客户端
需要先通过扫描找到附近的 BluetoothDevice
public class BluetoothBroadcast extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (Objects.equals(intent.getAction(), BluetoothDevice.ACTION_FOUND)){ // 找到设备
// 获取设备
BluetoothDevice bluetoothDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
if ("MyDevice".equals(bluetoothDevice.getName())){ // 比较是否为通信的设备
try {
// 需要指定与服务端相同的 UUID
UUID uuid = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
// 创建客户端
BluetoothSocket socket = bluetoothDevice.createRfcommSocketToServiceRecord(uuid);
// 连接
socket.connect();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
使用配置文件
Android 3.0开始 Bluetooth API 就支持使用蓝牙配置文件。蓝牙配置文件是适用于设备间蓝牙通信的无线接口规范。
Android 提供了以下几种蓝牙配置文件的实现
耳机:耳机配置文件提供了蓝牙耳机的支持。也就是这个配置文件提供了手机和蓝牙耳机进行通信的一种规范。使用 BluetoothHeadset 类,用于进程间通信来控制蓝牙耳机服务的代理。这个类包含 AT 命令支持。
A2DP: 高级音频分发配置文件(A2DP)。定义了高质量音频如何通过蓝牙连接和流式传输,从一个设备传输到另一个设备。BluetoothAdp 类,是用于通过进程间通信(IPC)来控制蓝牙 A2DP 服务的代理。
健康设备: Android 4.0(API 14)引入了对蓝牙健康设备配置文件(HDP)的支持。这样就允许我们创建的应用可以使用蓝牙与支持蓝牙功能的健康设备进行通信。(心率检测仪、血糖仪、温度计等等)。详解的配置要查看健康设备配置文件。
使用步骤
获取
BluetoothAdapter
通过
getProfileProxy()
建立到配置文件所关联的配置文件代理对象的连接设置监听
BluetoothProfile.ServiceListener
。这个监听会在客户端连接到服务或者断开服务连接的时候发送通知。在
onServiceConnected()
中获取配置文件代理对象的句柄。获取配置文件代理对象后,可以将其用于监听连接状态和执行其他与该配置文件相关的操作。
以 BluetoothHeadset 为例
// 蓝牙耳机的BluetoothDevice
final BluetoothDevice finalT3sDevice = t3sDevice;
defaultAdapter.getProfileProxy(context, new BluetoothProfile.ServiceListener() {
BluetoothHeadset headset = null;
@Override
public void onServiceConnected(int profile, BluetoothProfile proxy) {
if (profile == BluetoothProfile.HEADSET){
headset = (BluetoothHeadset) proxy;
// 使用蓝牙录音
boolean audioConnected = headset.startVoiceRecognition(finalT3sDevice);
}
}
@Override
public void onServiceDisconnected(int profile) {
// 停止录音
headset.stopVoiceRecognition(finalT3sDevice);
// 关闭配置文件代理与服务的连接
defaultAdapter.closeProfileProxy(profile,headset);
}
},BluetoothProfile.HEADSET);
关于 VoiceRecognition
使用蓝牙耳机音频传输由两种方式
SCO:一种双向的音频数据的传输链路,该链路只支持8K及16K单声道的音频数据,默认是关闭的
A2DP:一种单向的高品质音频数据传输链路,通常用于播放立体声音乐,默认是打开的
打开SCO有两种方式
- 使用 AudioManager
AudioManager mAudioManager = (AudioManager)getSystemService(Context.AUDIO_SERVICE);
// 打开 SCO
mAudioManager.setBluetoothScoOn(true);
mAudioManager.startBluetoothSco();
/*
* 录音操作
*/
// 关闭SCO
mAudioManager.setBluetoothScoOn(false);
mAudioManager.stopBluetoothSco();
-
使用 BluetoothHeadset
- 如像上面那样
低功耗蓝牙
没设备,待补充,待验证
蓝牙低功耗概览
经典蓝牙与低功耗蓝牙的区别
Android 蓝牙开发(2)——低功耗蓝牙
Android-BLE蓝牙原理