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的方法已经满足我的需求了。

  • 3
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
以下是 Android 手机蓝牙BLE 设备建立连接的代码示例: 1. 在 AndroidManifest.xml 文件中添加蓝牙权限: ```xml <uses-permission android:name="android.permission.BLUETOOTH" /> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> ``` 2. 在 Activity 中初始化 BluetoothAdapter 对象: ```java private BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); ``` 3. 扫描 BLE 设备并建立连接: ```java // 扫描时间 10 秒 private static final long SCAN_PERIOD = 10000; // 扫描回调 private final BluetoothAdapter.LeScanCallback leScanCallback = new BluetoothAdapter.LeScanCallback() { @Override public void onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord) { // 根据设备名称或 MAC 地址过滤 if (device.getName() != null && device.getName().startsWith("BLE")) { // 停止扫描 bluetoothAdapter.stopLeScan(leScanCallback); // 建立连接 device.connectGatt(MainActivity.this, false, gattCallback); } } }; // GATT 回调 private final BluetoothGattCallback gattCallback = new BluetoothGattCallback() { @Override public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { if (newState == BluetoothProfile.STATE_CONNECTED) { // 连接成功,开始发现服务 gatt.discoverServices(); } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { // 连接断开 Log.i(TAG, "Disconnected from GATT server."); } } @Override public void onServicesDiscovered(BluetoothGatt gatt, int status) { if (status == BluetoothGatt.GATT_SUCCESS) { // 服务发现成功,可以进行数据交互 Log.i(TAG, "Services discovered."); } else { Log.w(TAG, "onServicesDiscovered received: " + status); } } @Override public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { if (status == BluetoothGatt.GATT_SUCCESS) { // 读取特征值成功 Log.i(TAG, "Characteristic read successfully."); } else { Log.w(TAG, "Characteristic read failed: " + status); } } }; // 开始扫描 BLE 设备 private void scanBLEDevice() { if (bluetoothAdapter == null) { Log.w(TAG, "Bluetooth not supported."); return; } // 如果正在扫描,则先停止扫描 bluetoothAdapter.stopLeScan(leScanCallback); // 开始扫描 bluetoothAdapter.startLeScan(leScanCallback); // 扫描 SCAN_PERIOD 后停止扫描 new Handler().postDelayed(new Runnable() { @Override public void run() { bluetoothAdapter.stopLeScan(leScanCallback); } }, SCAN_PERIOD); } ``` 注意:在建立连接之前需要先扫描 BLE 设备,扫描到符合条件的设备后才能进行连接。在连接建立成功后,需要调用 `discoverServices()` 方法发现设备的服务,然后才能进行数据交互。以上代码仅供参考,实际开发中需要根据具体需求进行调整。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值