1 蓝牙基础知识
1.1 蓝牙相关的权限
<!--想要用蓝牙进行通信则要申明bluetooth权限-->
<uses-permission android:name="android.permission.BLUETOOTH"/>
<!--bluetooth_admin用来操作蓝牙,官方建议除非是用户请求修改蓝牙设置的-->
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<!--扫描蓝牙后会触发广播,如果触发广播需要添加下面权限-->
<uses-permission android:name="android.permission.Access_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
1.2 BluetoothAdapter两种获取对象的方法
//第一种
BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
//第二种
BluetoothManager manager = (BluetoothManager) sContext.getSystemService(Context.BLUETOOTH_SERVICE);
BluetoothAdapter mBluetoothAdapter = manager.getAdapter();
1.3 蓝牙相关广播
- ACTION_DISCOVERY_STARTED
public static final String ACTION_DISCOVERY_STARTED = "android.bluetooth.adapter.action.DISCOVERY_STARTED";
蓝牙适配器开始搜索后,会先有12秒的查询扫描(12s内可见),查询扫描后进行页面扫描(主动搜索),需要BLUETOOTH权限;如果搜索到蓝牙设备,就会收到BluetoothDevice.ACTION_FOUND广播,可以从Intent中获取存放在其中的BluetoothDevice对象,intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);设备查找非常消耗资源,已经存在的连接也会限制带宽,因此,如果想要执行除查找外的其它操作,之前最好调用cancelDiscovery()
- ACTION_DISCOVERY_FINISHED
public static final String ACTION_DISCOVERY_FINISHED = "android.bluetooth.adapter.action.DISCOVERY_FINISHED";
蓝牙适配器完成搜索时发出的广播需要BLUETOOTH权限。
1.4 中央设备与外围设备
蓝牙中有两种角色Central和Peripheral,也就是中央设备与外围设备。中央设备可以主动连接外围设备,外围设备发送广播然后中央设备搜索到该广播并连接,广播中带有外围设备自身的相关信息。
一个中央设备可以连接多个外围设备,但一个外围设备只能连接一个中央设备,Android手机可以作为中央设备也可以作为外围设备。
1.6蓝牙相关常量
- 中央设备
- 开关状态
常量 | 值 | 描述 |
---|---|---|
BluetoothAdapter.STATE_OFF | 10 | 蓝牙模块处于关闭状态 |
BluetoothAdapter.STATE_TURNING_ON | 11 | 蓝牙模块正在开启中 |
BluetoothAdapter.STATE_ON | 12 | 蓝牙模块处于开启状态 |
BluetoothAdapter.STATE_TURNING_OFF | 13 | 蓝牙模块正在关闭中 |
- 扫描状态
常量 | 值 | 描述 |
---|---|---|
BluetoothAdapter.SCAN_MODE_NONE | 20 | 查询扫描和页面扫描都失效,该状态下蓝牙模块既不能扫描其它设备,也不可见。 |
BluetoothAdapter.SCAN_MODE_CONNECTABLE | 21 | 查询扫描失效,页面扫描有效,该状态下蓝牙模块可以扫描其它设备,从可见性来说只对已配对的蓝牙设备可见,只有配对的设备才能主动连接本设备 |
BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE | 23 | 查询扫描和页面扫描都有效 |
- 连接状态
在Android中,两个类里面都有连接状态,一个是BluetoothAdapter里面,一个是BluetoothProfile里面。
BluetoothProfile是一个接口,
public interface BluetoothProfile {
...
//以下连接状态一般用于远端设备(即外围设备)
int STATE_DISCONNECTED = 0;
int STATE_DISCONNECTING = 1;
int STATE_CONNECTED = 2;
int STATE_DISCONNECTING = 3
...
}
在BluetoothAdapter中
//蓝牙处于已断开状态,一般值为0
public static final int STATE_DISCONNECTED = BluetoothProtoEnums.CONNECTION_STATE_DISCONNECTED;
//蓝牙正在连接,一般值为1
public static final int STATE_CONNECTING = BluetoothProtoEnums.CONNECTION_STATE_CONNECTING;
//蓝牙处于已连接状态,一般值为2
public static final int STATE_CONNECTED = BluetoothProtoEnums.CONNECTION_STATE_CONNECTED;
//蓝牙处于正在断开连接,一般值为3
pubilc static final int STATE_DISCONNECTING = BluetoothProtoEnums.CONNECTION_STATE_DISCONNECTING;
- 外围设备
- 配对状态
常量 | 值 | 描述 |
---|---|---|
BluetoothDevice.BOND_NONE | 10 | 远端设备没有配对 |
BluetoothDevice.BOND_BONDING | 11 | 正在和远端设备进行配对 |
BluetoothDevice.BOND_BONDED | 12 | 远端设备已经配对 |
1.7 常用类
类名 | 描述 |
---|---|
BluetoothDevice | 表示远程的蓝牙设备,与中央设备建立连接后,可以查询设备的名称,地址,配对状态,连接状态,电量等等 |
BluetoothProfile | 表示蓝牙配置文件的接口。蓝牙配置文件是适用于蓝牙通信的布线接口规范,两个设备之间具有相同的Profile才能互相连接 |
BluetoothHeadset | 提供蓝牙耳机支持,以便与手机配合使用。其中包括蓝牙耳机和免提(1.5版)配置文件 |
BluetoothA2dp | 定义高质量音频如何通过蓝牙连接和流式传输,从一台设备传输到另一台设备。“A2DP”代表高级音频分发配置文件,是BluetoothProfile的实现类,一般无线蓝牙耳机都实现了该配置文件 |
BluetoothHealth | 表示用于控制蓝牙服务的健康设备配置文件,是BluetoothProfile的实现类 |
BluetoothGatt | BluetoothProfile的实现类,与低功耗蓝牙通信有关的配置文件 |
配置文件(profile)类型
配置文件类型 | 值 | 描述 |
---|---|---|
BluetoothProfile.HEADSET | 1 | 耳机和免提配置文件 |
BluetoothProfile.A2DP | 2 | 蓝牙音频传输模型协定。A2DP规定了使用蓝牙非同步传输信道方式,传输高质量音乐文件数据的协议。基于该协议能通过蓝牙方式传输高品质音乐,这个技术以广泛用于蓝牙耳机 |
BluetoothProfile.HEALTH | 3 | 此常熟在API级别29中已被弃用,不再使用Health设备配置文件(HDP)和MCAP协议。新的应用程序应该使用基于蓝牙低功耗的解决方案 |
BluetoothProfile.HID_DEVICE | 19 | HID设备,一般有蓝牙键盘、蓝牙鼠标、蓝牙游戏手柄等 |
BluetoothProfile.HEARING_AID | 21 | 助听器 |
BluetoothProfile.PAN | 5 | 应用于手机 |
2 蓝牙常用方法
- 判断设备是否支持蓝牙
//设备是否支持蓝牙
public static boolean isSupportBluetooth(){
BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if(bluetoothAdapter!=null){
return true;
}
return false;
}
- 如果设备支持蓝牙则判断蓝牙是否已打开
//蓝牙是否已经启动
public static boolean isBluetoothOpen(){
BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if(bluetoothAdapter!=null){
return bluetoothAdapter.isEnabled();
}
return false;
}
- 如果蓝牙没有打开则打开蓝牙
public static void openBluetooth(){
BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if(isSupportBluetooth()){
bluetoothAdapter.enable();
}
}
如果手机是root状态,可以通过adb命令打开蓝牙
adb shell svc bluetooth enable
- 如果蓝牙打开则获取已配对列表
//查询配对设备
public static List<BluetoothDevice> checkDevices(){
BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
List<BluetoothDevice> devices = new ArrayList<>();
if(bluetoothAdapter!=null){
Set<BluetoothDevice>pairedDevices = bluetoothAdapter.getBondedDevices();
if(pairedDevices!=null&&pairedDevices.size()>0){
for(BluetoothDevice device : pairedDevices){
devices.add(device);
}
}
}
return devices;
}
- 获取远端的Profile对象
private void setBluetoothProfile(){
ConditionVariable variable = new ConditionVariable();
mBluetoothAdapter.getProfileProxy(sContext,new BluetoothProfile.ServiceListener(){
@Override
public void onServiceConnected(int profile,BluetoothProfile proxy){
mBluetoothA2dp = (BluetoothA2dp)proxy;
if(variable!=null){
variable.open();
}
}
@Override
public void onServiceDisconnected(int profilel){
if(variable!=null){
variable.open();
}
}
},BluetoothProfile.A2DP);
variable.block(5000);
variable.close();
}
- 判断已配对的远端设备中是否有指定profile类型
private BluetoothDevice getRemoteBluetoothDevice(){
BluetoothDevice remoteDevice = null;
//checkDevices()方法在上面
List<BluetoothDevice>bondedDeviceList = checkDevices();
for(BluetoothDevice device : bondedDeviceList){
if(device.getBluetoothClass!=null){
//可以通过下面方法判断远端设备是什么类型设备
if(device.getBluetoothClass().getMajorDeviceClass() == BluetoothClass.Major.AUDIO_VIDEO){
remoteDevice = device;
}
}
}
return remoteDevice;
}
- 如果BluetoothA2dp类型没有连接上,可以通过以下方法进行连接
//以下方法是不能实现的,因为BluetoothA2dp下的connect方法是hide方法,可以通过反射来调用
mBluetoothA2dp.connect(remoteDevice);
3 蓝牙设备类型
public final class BluetoothClass implements Parcelable{
...
public static class Device {
private static final int BITMASK = 0x1FFC;
/**
* Defines all major device class constants.
* <p>See {@link BluetoothClass.Device} for minor classes.
*/
public static class Major {
private static final int BITMASK = 0x1F00;//比特掩码
public static final int MISC = 0x0000;//麦克风
public static final int COMPUTER = 0x0100;//电脑
public static final int PHONE = 0x0200;//电话
public static final int NETWORKING = 0x0300;//网络
public static final int AUDIO_VIDEO = 0x0400;//音频
public static final int PERIPHERAL = 0x0500;//外部设备
public static final int IMAGING = 0x0600;//镜像,映像
public static final int WEARABLE = 0x0700;//穿戴设备
public static final int TOY = 0x0800;//玩具
public static final int HEALTH = 0x0900;//健康
public static final int UNCATEGORIZED = 0x1F00;//未分类的、未知的
}
// Devices in the COMPUTER major class
public static final int COMPUTER_UNCATEGORIZED = 0x0100;
public static final int COMPUTER_DESKTOP = 0x0104;
public static final int COMPUTER_SERVER = 0x0108;
public static final int COMPUTER_LAPTOP = 0x010C;
public static final int COMPUTER_HANDHELD_PC_PDA = 0x0110;
public static final int COMPUTER_PALM_SIZE_PC_PDA = 0x0114;
public static final int COMPUTER_WEARABLE = 0x0118;
// Devices in the PHONE major class
public static final int PHONE_UNCATEGORIZED = 0x0200;
public static final int PHONE_CELLULAR = 0x0204;
public static final int PHONE_CORDLESS = 0x0208;
public static final int PHONE_SMART = 0x020C;
public static final int PHONE_MODEM_OR_GATEWAY = 0x0210;
public static final int PHONE_ISDN = 0x0214;
// Minor classes for the AUDIO_VIDEO major class
public static final int AUDIO_VIDEO_UNCATEGORIZED = 0x0400;
public static final int AUDIO_VIDEO_WEARABLE_HEADSET = 0x0404;
public static final int AUDIO_VIDEO_HANDSFREE = 0x0408;
//public static final int AUDIO_VIDEO_RESERVED = 0x040C;
public static final int AUDIO_VIDEO_MICROPHONE = 0x0410;
public static final int AUDIO_VIDEO_LOUDSPEAKER = 0x0414;
public static final int AUDIO_VIDEO_HEADPHONES = 0x0418;
public static final int AUDIO_VIDEO_PORTABLE_AUDIO = 0x041C;
public static final int AUDIO_VIDEO_CAR_AUDIO = 0x0420;
public static final int AUDIO_VIDEO_SET_TOP_BOX = 0x0424;
public static final int AUDIO_VIDEO_HIFI_AUDIO = 0x0428;
public static final int AUDIO_VIDEO_VCR = 0x042C;
public static final int AUDIO_VIDEO_VIDEO_CAMERA = 0x0430;
public static final int AUDIO_VIDEO_CAMCORDER = 0x0434;
public static final int AUDIO_VIDEO_VIDEO_MONITOR = 0x0438;
public static final int AUDIO_VIDEO_VIDEO_DISPLAY_AND_LOUDSPEAKER = 0x043C;
public static final int AUDIO_VIDEO_VIDEO_CONFERENCING = 0x0440;
//public static final int AUDIO_VIDEO_RESERVED = 0x0444;
public static final int AUDIO_VIDEO_VIDEO_GAMING_TOY = 0x0448;
// Devices in the WEARABLE major class //穿戴设备细分
public static final int WEARABLE_UNCATEGORIZED = 0x0700;
public static final int WEARABLE_WRIST_WATCH = 0x0704;
public static final int WEARABLE_PAGER = 0x0708;
public static final int WEARABLE_JACKET = 0x070C;
public static final int WEARABLE_HELMET = 0x0710;
public static final int WEARABLE_GLASSES = 0x0714;
// Devices in the TOY major class 玩具细分
public static final int TOY_UNCATEGORIZED = 0x0800;
public static final int TOY_ROBOT = 0x0804;
public static final int TOY_VEHICLE = 0x0808;
public static final int TOY_DOLL_ACTION_FIGURE = 0x080C;
public static final int TOY_CONTROLLER = 0x0810;
public static final int TOY_GAME = 0x0814;
// Devices in the HEALTH major class 健康设备细分
public static final int HEALTH_UNCATEGORIZED = 0x0900;
public static final int HEALTH_BLOOD_PRESSURE = 0x0904;
public static final int HEALTH_THERMOMETER = 0x0908;
public static final int HEALTH_WEIGHING = 0x090C;
public static final int HEALTH_GLUCOSE = 0x0910;
public static final int HEALTH_PULSE_OXIMETER = 0x0914;
public static final int HEALTH_PULSE_RATE = 0x0918;
public static final int HEALTH_DATA_DISPLAY = 0x091C;
// Devices in PERIPHERAL major class
/**
* @hide
*/
public static final int PERIPHERAL_NON_KEYBOARD_NON_POINTING = 0x0500;
/**
* @hide
*/
public static final int PERIPHERAL_KEYBOARD = 0x0540;
/**
* @hide
*/
public static final int PERIPHERAL_POINTING = 0x0580;
/**
* @hide
*/
public static final int PERIPHERAL_KEYBOARD_POINTING = 0x05C0;
}
...
}
使用方式
int deviceType = device.getBluetoothClass().getMajorDeviceClass();
if(deviceType == BluetoothClass.Device.Major.PHONE){
...
}
注:需要注意的是,device.getBluetoothClass().getMajorDeviceClass()有时候获取不到正确的值,这应该跟远端设备的硬件有关。手机与手机连接,其中一个需要打开“蓝牙共享网络”或“蓝牙网络共享”,不同手机名称可能不一样。打开“蓝牙网络共享”的手机则为中央设备,可以被多个手机连接。
4 重要方法
4.1 getProfileProxy
mBluetoothAdapter.getProfileProxy(sContext,new BluetoothProfile.ServiceListener(){
@Override
public void onServiceConnected(int profile,BluetoothProfile proxy){
mBluetoothA2dp = (BluetoothA2dp)proxy;
if(variable!=null){
variable.open();
}
}
@Override
public void onServiceDisconnected(int profilel){
if(variable!=null){
variable.open();
}
}
},BluetoothProfile.A2DP);
可以看到源码:
public boolean getProfileProxy(Context context, BluetoothProfile.ServiceListener listener,
int profile) {
if (context == null || listener == null) {
return false;
}
if (profile == BluetoothProfile.HEADSET) {
BluetoothHeadset headset = new BluetoothHeadset(context, listener);
return true;
} else if (profile == BluetoothProfile.A2DP) {
BluetoothA2dp a2dp = new BluetoothA2dp(context, listener);
return true;
} else if (profile == BluetoothProfile.A2DP_SINK) {
BluetoothA2dpSink a2dpSink = new BluetoothA2dpSink(context, listener);
return true;
} else if (profile == BluetoothProfile.AVRCP_CONTROLLER) {
BluetoothAvrcpController avrcp = new BluetoothAvrcpController(context, listener);
return true;
} else if (profile == BluetoothProfile.HID_HOST) {
BluetoothHidHost iDev = new BluetoothHidHost(context, listener);
return true;
} else if (profile == BluetoothProfile.PAN) {
BluetoothPan pan = new BluetoothPan(context, listener);
return true;
} else if (profile == BluetoothProfile.HEALTH) {
BluetoothHealth health = new BluetoothHealth(context, listener);
return true;
} else if (profile == BluetoothProfile.MAP) {
BluetoothMap map = new BluetoothMap(context, listener);
return true;
} else if (profile == BluetoothProfile.HEADSET_CLIENT) {
BluetoothHeadsetClient headsetClient = new BluetoothHeadsetClient(context, listener);
return true;
} else if (profile == BluetoothProfile.SAP) {
BluetoothSap sap = new BluetoothSap(context, listener);
return true;
} else if (profile == BluetoothProfile.PBAP_CLIENT) {
BluetoothPbapClient pbapClient = new BluetoothPbapClient(context, listener);
return true;
} else if (profile == BluetoothProfile.MAP_CLIENT) {
BluetoothMapClient mapClient = new BluetoothMapClient(context, listener);
return true;
} else if (profile == BluetoothProfile.HID_DEVICE) {
BluetoothHidDevice hidDevice = new BluetoothHidDevice(context, listener);
return true;
} else if (profile == BluetoothProfile.HEARING_AID) {
BluetoothHearingAid hearingAid = new BluetoothHearingAid(context, listener);
return true;
} else {
return false;
}
}
可以看到,它是一个代理方法,通过Profile数值来得到指定的BluetoothProfile对象,通过回调的方式提供给使用者。getProfileProxy最终操作的是AIDL文件,因此回调里的代码可能比回调外的代码晚执行,所以需要做同步操作。
BluetoothAdapter中还有一个关闭代理的方法,在使用结束时应该调用close方法。
public void closeProfileProxy(int profile,BluetoothProfile proxy){
...
}
4.2 setPriority
/**
* Set priority of the profile
*
* <p> The device should already be paired.
* Priority can be one of {@link #PRIORITY_ON} orgetBluetoothManager
* {@link #PRIORITY_OFF},
*
* <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
* permission.
*
* @param device Paired bluetooth device
* @param priority
* @return true if priority is set, false on error
* @hide
*/
public boolean setPriority(BluetoothDevice device, int priority) {
...
}
每个BluetoothProfile的实现类中都有该方法,该方法priority值只能设置0和100,其它值会返回false。而且该方法的调用必须是蓝牙设备处于配对状态。profile=0时,会停用该profile对应的一切相关功能。
4.3 getProfileConnectionState和getConnectionState
//只针对蓝牙设备的某个profile
int state = mBluetoothAdapter.getProfileConnectionState(A2DP);
//只要蓝牙设备有一种profile处于连接状态,则返回连接状态的state,该方法是hide方法,需要通过反射获得
int state = mBluetoothAdapter.getConnectionState();
5 Profile
从3.0版本开始,蓝牙才开始支持BluetoothProfile。BluetoothProfile是蓝牙设备间数据通信的无线接口规范。想要使用蓝牙无线技术,设备必须能够翻译特定蓝牙配置文件。配置文件定义了可能的应用,蓝牙的一个很重要的特性就是蓝牙产品无需实现全部的蓝牙规范。为了更容易的保持蓝牙设备之间的兼容,蓝牙规范中定义了profile,一个蓝牙设备可以包含多个profile。profile定义了设备如何实现一种连接或者应用,可以把profile理解为连接层或者应用层协议。两个蓝牙设备具有相同的profile,这两个设备才能相互连接。这里指的是具有相同的profile,并不是说实现了相同profile的功能或应用。
6 蓝牙UUID
UUID是根据一定算法,计算得到的一长串数字,这串数字不会重复,每次生成都会产生不一样的序列,所以可以用来作为唯一标识。在蓝牙协议中,UUID被用来标识蓝牙设备所提供的服务,并非是标识蓝牙设备本身,一个蓝牙设备可以提供多种服务,比如A2DP(蓝牙音频传输),HEADFREE(免提)、PBAP(电话本)、SPP(串口通信)等待,每种服务都对应一个UUID,其中在蓝牙协议栈里,这些默认提供的profile是都有对应的UUID,也就是默认的UUID,比如SPP,00001101-0000-1000-8000-00805F9B34FB就是一个非常知名的UUID,基本上所有的蓝牙板不修改的话都是这个值,所以如果是与一个蓝牙开发板进行串口通信,而蓝牙侧又不是自己可以控制的,可以试试这个值。
7 RSSI
接收信息强度指示(RSSI,Received Signal Strength Indicator)无线发送层的可选部分,用来判定连接质量,以及是否增大广播发送强度。
dBm用于表达功率的绝对值,计算公式为
P为接收到的信号功率,单位W(瓦)
【例1】如果发射功率P为1mW,折算为dbm后为0dbm
【例2】对于发射功率40W,按其计算公式得46dbm