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 中央设备与外围设备

蓝牙中有两种角色CentralPeripheral,也就是中央设备与外围设备。中央设备可以主动连接外围设备,外围设备发送广播然后中央设备搜索到该广播并连接,广播中带有外围设备自身的相关信息。

android bluetooth 三方通话 android.bluetooth_外围设备

一个中央设备可以连接多个外围设备,但一个外围设备只能连接一个中央设备,Android手机可以作为中央设备也可以作为外围设备。

1.6蓝牙相关常量

  • 中央设备
  1. 开关状态

常量


描述

BluetoothAdapter.STATE_OFF

10

蓝牙模块处于关闭状态

BluetoothAdapter.STATE_TURNING_ON

11

蓝牙模块正在开启中

BluetoothAdapter.STATE_ON

12

蓝牙模块处于开启状态

BluetoothAdapter.STATE_TURNING_OFF

13

蓝牙模块正在关闭中

  1. 扫描状态

常量


描述

BluetoothAdapter.SCAN_MODE_NONE

20

查询扫描和页面扫描都失效,该状态下蓝牙模块既不能扫描其它设备,也不可见。

BluetoothAdapter.SCAN_MODE_CONNECTABLE

21

查询扫描失效,页面扫描有效,该状态下蓝牙模块可以扫描其它设备,从可见性来说只对已配对的蓝牙设备可见,只有配对的设备才能主动连接本设备

BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE

23

查询扫描和页面扫描都有效

  1. 连接状态
    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;
  • 外围设备
  1. 配对状态

常量


描述

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 蓝牙常用方法

  1. 判断设备是否支持蓝牙
//设备是否支持蓝牙
public static boolean isSupportBluetooth(){
    BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
    if(bluetoothAdapter!=null){
        return true;
    }
    return false;
}
  1. 如果设备支持蓝牙则判断蓝牙是否已打开
//蓝牙是否已经启动
public static boolean isBluetoothOpen(){
    BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
    if(bluetoothAdapter!=null){
        return bluetoothAdapter.isEnabled();
    }
    return false;
}
  1. 如果蓝牙没有打开则打开蓝牙
public static void openBluetooth(){
    BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
    if(isSupportBluetooth()){
        bluetoothAdapter.enable();
    }
}

如果手机是root状态,可以通过adb命令打开蓝牙

adb shell svc bluetooth enable
  1. 如果蓝牙打开则获取已配对列表
//查询配对设备
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;
}
  1. 获取远端的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();
}
  1. 判断已配对的远端设备中是否有指定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;
}
  1. 如果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值只能设置0100,其它值会返回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版本开始,蓝牙才开始支持BluetoothProfileBluetoothProfile是蓝牙设备间数据通信的无线接口规范。想要使用蓝牙无线技术,设备必须能够翻译特定蓝牙配置文件。配置文件定义了可能的应用,蓝牙的一个很重要的特性就是蓝牙产品无需实现全部的蓝牙规范。为了更容易的保持蓝牙设备之间的兼容,蓝牙规范中定义了profile,一个蓝牙设备可以包含多个profileprofile定义了设备如何实现一种连接或者应用,可以把profile理解为连接层或者应用层协议。两个蓝牙设备具有相同的profile,这两个设备才能相互连接。这里指的是具有相同的profile,并不是说实现了相同profile的功能或应用。

6 蓝牙UUID

UUID是根据一定算法,计算得到的一长串数字,这串数字不会重复,每次生成都会产生不一样的序列,所以可以用来作为唯一标识。在蓝牙协议中,UUID被用来标识蓝牙设备所提供的服务,并非是标识蓝牙设备本身,一个蓝牙设备可以提供多种服务,比如A2DP(蓝牙音频传输),HEADFREE(免提)、PBAP(电话本)、SPP(串口通信)等待,每种服务都对应一个UUID,其中在蓝牙协议栈里,这些默认提供的profile是都有对应的UUID,也就是默认的UUID,比如SPP00001101-0000-1000-8000-00805F9B34FB就是一个非常知名的UUID,基本上所有的蓝牙板不修改的话都是这个值,所以如果是与一个蓝牙开发板进行串口通信,而蓝牙侧又不是自己可以控制的,可以试试这个值。

7 RSSI

接收信息强度指示(RSSI,Received Signal Strength Indicator)无线发送层的可选部分,用来判定连接质量,以及是否增大广播发送强度。

dBm用于表达功率的绝对值,计算公式为
android bluetooth 三方通话 android.bluetooth_外围设备_02
P为接收到的信号功率,单位W(瓦)
【例1】如果发射功率P1mW,折算为dbm后为0dbm 【例2】对于发射功率40W,按其计算公式得46dbm