Android 获取已连接的经典蓝牙和BLE设备[ 2022年了 ]

Android 获取已连接的经典蓝牙和BLE设备

需求

公司需要获取Android已经连接的蓝牙设备。从公开的API看,无法获取。
从网络上查询资料,看到这个 《Android检查设备连接状态》

我验证其中的代码结果。对于经典蓝牙,因为基本上已连接的设备都会出现在已配对列表,从列表里拿到设备后逐个去判断,确实可行。
但BLE的设备,你只能知道有设备连接在系统里,但无法知道是哪个?

解决思路

  • 从上面的文章,我看到经典蓝牙是对 BluetoothDevice 对象反射调用 isConnected 方法判断的,那如果我能拿到BLE的device对象,应该也能判断。我尝试直接用BLE的一个MAC地址使用 BluetoothAdapter.getRemoteDevice(macAddress) 获取到对象,也使用上面的反射方法isConnected,结果证明可行。

  • 现在的问题是如何拿到所有可能的device,包括那些已经被连接的BLE或者经典蓝牙。查找BluetoothAdapter源码中,看到一个 getMostRecentlyConnectedDevices,返回的是一个List。大概的意思是返回最近连接过的设备,我理解是:这也包括目前已经连接的设备。 尝试之,此方法有效。

编程实现

  1. 上面调用会用到反射,由于Android10以后反射调用,会报错:Accessing hidden method ‘xxxx.xxxxxx.xxxxx’ (blacklist, reflection, denied),所以这里我使用 github 上的RestrictionBypass 绕过反射的报错,具体参考它的代码实现。它的库通过内建ContentProvider的attachInfo来调用绕过反射的方法,所以你的app不需要任何一行代码就能生效。

    注意: 反射调用BluetoothDevice#isConnected并不会触发这个错误,但BluetoothAdapter#getMostRecentlyConnectedDevices会。如果你只使用我下文中的V2版本,可以不做这一步

  2. 代码封装

    //TODO 根据mac地址判断是否已连接(这里参数可以直接用BluetoothDevice对象)
    //但这么写其实更通用。
    public boolean isConnected(String macAddress){
        if (!BluetoothAdapter.checkBluetoothAddress(macAddress)){
            return false;
        }
        final BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
        BluetoothDevice device = bluetoothAdapter.getRemoteDevice(macAddress);
    
        Method isConnectedMethod = null;
        boolean isConnected;
        try {
            isConnectedMethod = BluetoothDevice.class.getDeclaredMethod("isConnected", (Class[]) null);
            isConnectedMethod.setAccessible(true);
            isConnected = (boolean) isConnectedMethod.invoke(device, (Object[]) null);
        } catch (NoSuchMethodException e) {
            isConnected = false;
        } catch (IllegalAccessException e) {
            isConnected = false;
        } catch (InvocationTargetException e) {
            isConnected = false;
        }
        return isConnected;
    }
    
    /**
     * 获取系统中已连接的蓝牙设备
     * @return 
     */
     public Set<BluetoothDevice> getConnectedDevicesV1() {
        Class<BluetoothAdapter> bluetoothAdapterClass = BluetoothAdapter.class;//得到BluetoothAdapter的Class对象
        Set<BluetoothDevice> deviceSet = new HashSet<>();
        //是否存在连接的蓝牙设备
        try {
            Method method = bluetoothAdapterClass.getDeclaredMethod("getMostRecentlyConnectedDevices", (Class[]) null);
            //打开权限
            method.setAccessible(true);
            List<BluetoothDevice> list= (List<BluetoothDevice>) method.invoke(BluetoothAdapter.getDefaultAdapter(), (Object[]) null);
            Log.d("zbh","最近连接过的设备:");
            for (BluetoothDevice dev:list
            ) {
                String Type = "";
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
                    switch (dev.getType()){
                        case BluetoothDevice.DEVICE_TYPE_CLASSIC:
                            Type = "经典";
                            break;
                        case BluetoothDevice.DEVICE_TYPE_LE:
                            Type = "BLE";
                            break;
                        case BluetoothDevice.DEVICE_TYPE_DUAL:
                            Type = "双模";
                            break;
                        default:
                            Type = "未知";
                            break;
                    }
                }
                String connect = "设备未连接";
                if (isConnected(dev.getAddress())){
                    deviceSet.add(dev);
                    connect = "设备已连接";
                }
                Log.d("zbh", connect+", address = "+dev.getAddress() + "("+ Type + "), name --> "+dev.getName());
    
            }
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
        return deviceSet;
    }
    
  3. 测试结果截图:
    在这里插入图片描述

测试机型

从最终测试结果看,不管是TWS蓝牙耳机,还是普通的BLE设备,或者是经典蓝牙都能正确拿到状态。但当我测试了多个平板后,我发现华为系的Android或者鸿蒙大概率报错。

目前我测试的机型有:

  • 小米10 (android 11), 测试OK

  • 荣耀View7 (Android 11),测试OK

  • 华为M6平板SCM-AL09 (Android 9),报错 找不到 getMostRecentlyConnectedDevices方法

  • 华为C5平板BZT-AL10 (android 8)报错 找不到 getMostRecentlyConnectedDevices方法

  • 华为C5平板BZT3-W09 (android 10)报错 找不到 getMostRecentlyConnectedDevices方法

  • 华为荣耀手机TEL-TN00(鸿蒙2.0)报错 找不到 getMostRecentlyConnectedDevices方法

  • 华为手机ALP-AL00(鸿蒙2.0)报错 找不到 getMostRecentlyConnectedDevices方法

改进方法

反射getMostRecentlyConnectedDevices方法还是有风险, 不保证能通用。
继续找方法。
终于,我吐了,秃了,佛了
又过了三天,我找到了一个API (BluetoothManager#getConnectedDevices),姑且一试吧
V2版本方法:

	/**
     * 获取系统中已连接的蓝牙设备
     * @return
     */
    public Set<BluetoothDevice> getConnectedDevicesV2(Context context){

        Set<BluetoothDevice> result = new HashSet<>();
        Set<BluetoothDevice> deviceSet = new HashSet<>();

        BluetoothManager bluetoothManager = (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE);
        //获取BLE的设备, profile只能是GATT或者GATT_SERVER
        List GattDevices = bluetoothManager.getConnectedDevices(BluetoothProfile.GATT);
        if (GattDevices!=null && GattDevices.size()>0){
            deviceSet.addAll(GattDevices);
        }
        //获取已配对的设备
        Set ClassicDevices = bluetoothManager.getAdapter().getBondedDevices();
        if (ClassicDevices!=null && ClassicDevices.size()>0){
            deviceSet.addAll(ClassicDevices);
        }

        for (BluetoothDevice dev:deviceSet
        ) {
            String Type = "";
            switch (dev.getType()){
                case BluetoothDevice.DEVICE_TYPE_CLASSIC:
                    Type = "经典";
                    break;
                case BluetoothDevice.DEVICE_TYPE_LE:
                    Type = "BLE";
                    break;
                case BluetoothDevice.DEVICE_TYPE_DUAL:
                    Type = "双模";
                    break;
                default:
                    Type = "未知";
                    break;
            }
            String connect = "设备未连接";
            if (isConnected(dev.getAddress())){
                result.add(dev);
                connect = "设备已连接";
            }
            Log.d("zbh", connect+", address = "+dev.getAddress() + "("+ Type + "), name --> "+dev.getName());
        }
        return result;
    }

结论

  1. V1 方法在华为平板上可能不生效,但它能找到所有系统连接的蓝牙设备,包括不需要配对的经典设备

  2. V2 方法目前在华为平板上能生效,但经典蓝牙通过 createInsecureRfcommSocketToServiceRecord建立的连接,在配对列表里看不到它,也就找不出来这个设备。也就是它能找到所有已连接的BLE设备和在配对列表里已连接的设备。

3 当然,可以结合V1 和 V2 做一个兼容的版本。V1版本中一旦找不到getMostRecentlyConnectedDevices就启用V2的方式。

不过对于我来说, 我们都要求客户必须配对,所以V2的方法已经满足我的需求了。

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值