美文网首页蓝牙
Android蓝牙相关知识

Android蓝牙相关知识

作者: 秀叶寒冬 | 来源:发表于2020-02-12 17:23 被阅读0次

    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蓝牙相关常量

    • 中央设备
    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值只能设置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用于表达功率的绝对值,计算公式为
    RSSI = 10*lg(P/0.001)
    P为接收到的信号功率,单位W(瓦)
    【例1】如果发射功率P为1mW,折算为dbm后为0dbm
    【例2】对于发射功率40W,按其计算公式得46dbm

    相关文章

      网友评论

        本文标题:Android蓝牙相关知识

        本文链接:https://www.haomeiwen.com/subject/iohaxhtx.html