即使应用程序关闭,搭载 Android 12 的设备也会保持蓝牙 LE 连接

SMG*_*ost 5 android android-bluetooth google-pixel

我遇到一个问题,我可以连接到蓝牙设备一次,但断开连接后,在扫描蓝牙设备时我不再看到该设备。如果我完全关闭应用程序,设备仍然无法被发现,但如果我关闭手机,设备将再次被发现。

我还注意到这个问题发生在 Pixel、华为和 xiomi 设备上,但似乎适用于运行 Android 12 的三星。

我的假设是,Android 12 中有一些奇怪的功能,可以以某种方式保持连接与应用程序分开。在我的应用程序中,我调用此代码来断开连接:

gatt.close()
Run Code Online (Sandbox Code Playgroud)

还有其他方法可以确保设备完全断开连接吗?

编辑: 打电话

bluetoothManager.getConnectedDevices(BluetoothProfile.GATT)
Run Code Online (Sandbox Code Playgroud)

断开连接并关闭后仍然返回我连接的设备。

EDIT2: 我可以使用以下代码重现此问题:

    private var gatt: BluetoothGatt? = null
    @SuppressLint("MissingPermission")
    fun onDeviceClick(macAddress: String) {
        logger.i(TAG, "onDeviceClick(macAddress=$macAddress)")
        val bluetoothManager: BluetoothManager =
            context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
        if (gatt != null) {
            logger.i(TAG, "Disconnecting")
            gatt?.close()
            gatt = null
            printConnectedDevices(bluetoothManager)
            return
        }
        printConnectedDevices(bluetoothManager)
        val btDevice = bluetoothManager.adapter.getRemoteDevice(macAddress)
        logger.d(TAG, "Device to connect: $btDevice")
        gatt = btDevice.connectGatt(context, false, object : BluetoothGattCallback() {
            override fun onConnectionStateChange(gatt: BluetoothGatt?, status: Int, newState: Int) {
                super.onConnectionStateChange(gatt, status, newState)
                logger.d(TAG, "Connection state changed to status: $status, sate: $newState")
                when (newState) {
                    BluetoothProfile.STATE_CONNECTED -> {
                        logger.d(TAG, "Connected")
                        printConnectedDevices(bluetoothManager)
                    }
                    BluetoothProfile.STATE_DISCONNECTED -> {
                        logger.d(TAG, "Disconnected")
                        printConnectedDevices(bluetoothManager)
                    }
                }
            }
        })
    }

    @SuppressLint("MissingPermission")
    private fun printConnectedDevices(bluetoothManager: BluetoothManager) {
        val btDevices = bluetoothManager.getConnectedDevices(BluetoothProfile.GATT)
        logger.d(TAG, "Currently connected devices: $btDevices")
    }
Run Code Online (Sandbox Code Playgroud)

只需调用一次 onDeviceClick 即可连接到设备,再次单击即可断开连接。断开连接后,我可以在日志中看到,对于 Pixel 手机,我的蓝牙适配器仍显示为已连接:

I/SelectDeviceViewModel: onDeviceClick(macAddress=00:1E:42:35:F0:4D)
D/SelectDeviceViewModel: Currently connected devices: []
D/SelectDeviceViewModel: Device to connect: 00:1E:42:35:F0:4D
D/BluetoothGatt: connect() - device: 00:1E:42:35:F0:4D, auto: false
D/BluetoothGatt: registerApp()
D/BluetoothGatt: registerApp() - UUID=ae98a387-cfca-43db-82f0-45fd141979ee
D/BluetoothGatt: onClientRegistered() - status=0 clientIf=12
D/BluetoothGatt: onClientConnectionState() - status=0 clientIf=12 device=00:1E:42:35:F0:4D
D/SelectDeviceViewModel: Connection state changed to status: 0, sate: 2
D/SelectDeviceViewModel: Connected
D/SelectDeviceViewModel: Currently connected devices: [00:1E:42:35:F0:4D]
D/BluetoothGatt: onConnectionUpdated() - Device=00:1E:42:35:F0:4D interval=6 latency=0 timeout=500 status=0
D/BluetoothGatt: onConnectionUpdated() - Device=00:1E:42:35:F0:4D interval=36 latency=0 timeout=500 status=0
D/BluetoothGatt: onConnectionUpdated() - Device=00:1E:42:35:F0:4D interval=9 latency=0 timeout=600 status=0
I/SelectDeviceViewModel: onDeviceClick(macAddress=00:1E:42:35:F0:4D)
I/SelectDeviceViewModel: Disconnecting
D/BluetoothGatt: close()
D/BluetoothGatt: unregisterApp() - mClientIf=12
D/SelectDeviceViewModel: Currently connected devices: [00:1E:42:35:F0:4D]
Run Code Online (Sandbox Code Playgroud)

EDIT3登录三星,一切正常:

I/SelectDeviceViewModel: onDeviceClick(macAddress=00:1E:42:35:F0:4D)
D/SelectDeviceViewModel: Currently connected devices: []
D/SelectDeviceViewModel: Device to connect: 00:1E:42:35:F0:4D
I/BluetoothAdapter: STATE_ON
D/BluetoothGatt: connect() - device: 00:1E:42:35:F0:4D, auto: false
I/BluetoothAdapter: isSecureModeEnabled
D/BluetoothGatt: registerApp()
D/BluetoothGatt: registerApp() - UUID=931b9526-ffae-402a-a4b4-3f01edc76e46
D/BluetoothGatt: onClientRegistered() - status=0 clientIf=17
D/BluetoothGatt: onTimeSync() - eventCount=0 offset=346
D/BluetoothGatt: onClientConnectionState() - status=0 clientIf=17 device=00:1E:42:35:F0:4D
D/SelectDeviceViewModel: Connection state changed to status: 0, sate: 2
D/SelectDeviceViewModel: Connected
D/SelectDeviceViewModel: Currently connected devices: [00:1E:42:35:F0:4D]
D/BluetoothGatt: onConnectionUpdated() - Device=00:1E:42:35:F0:4D interval=6 latency=0 timeout=500 status=0
D/BluetoothGatt: onConnectionUpdated() - Device=00:1E:42:35:F0:4D interval=38 latency=0 timeout=500 status=0
D/BluetoothGatt: onConnectionUpdated() - Device=00:1E:42:35:F0:4D interval=9 latency=0 timeout=600 status=0
I/SelectDeviceViewModel: onDeviceClick(macAddress=00:1E:42:35:F0:4D)
I/SelectDeviceViewModel: Disconnecting
D/BluetoothGatt: close()
D/BluetoothGatt: unregisterApp() - mClientIf=17
D/SelectDeviceViewModel: Currently connected devices: []
Run Code Online (Sandbox Code Playgroud)

EDIT4我尝试修改上面的代码以首先调用disconnect(),并且仅在蓝牙连接状态更改为断开连接时调用close(),但仍然存在相同的问题。

Koz*_*nik 1

仅调用 gatt.close 是不够的。为了正确地断开与gatt服务器的连接;您需要首先调用BlueotoothGatt.disconnect,然后在onConnectionStateChange回调中您必须调用BluetoothGatt.close

您活动中的某些地方

// Somewhere in your activity where you want to disconnect
// might be adequate in onPause callback
if(bleAdapter != null && bleAdapter.isConnected) {
    bleAdapter.disconnect(); // In bleAdapter you're supposed to hold a reference to the gatt object.
}
Run Code Online (Sandbox Code Playgroud)

在您的 BLE 适配器实现中

private void disconnect() {;
    if (bluetooth_adapter == null || bluetooth_gatt == null) {
        Log.d("disconnect: bluetooth_adapter|bluetooth_gatt null");
        return;
    }
    if (bluetooth_gatt != null) {
        bluetooth_gatt.disconnect();
    }
}

// And finally in your BluetoothGattCallback.onConnectionStateChange implementation you call the close method on your BluetoothGatt object reference
private final BluetoothGattCallback gatt_callback = new BluetoothGattCallback() {
    @Override
    public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
        Log.d(TAG, "onConnectionStateChange: status=" + status);
        if (newState == BluetoothProfile.STATE_CONNECTED) {
            Log.d(TAG, "onConnectionStateChange: CONNECTED");
            connected = true;
        }
        else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
            Log.d(TAG, "onConnectionStateChange: DISCONNECTED");
            connected = false;
            if (bluetooth_gatt != null) {
                Log.d(TAG,"Closing and destroying BluetoothGatt object");
                bluetooth_gatt.close();
                bluetooth_gatt = null;
            }
        }
    }

    // Other callbacks
};
Run Code Online (Sandbox Code Playgroud)

更新:我对最小可重现示例的测试结果

我将你的MRE kotlin代码转换为java,以下2种情况的代码是:

// This listener is defined in the onCreate callback
binding.fab.setOnClickListener(view -> {
    final String target = "50:8C:B1:69:E5:63";
    BluetoothManager bluetoothManager = getSystemService(BluetoothManager.class);
    if(gatt != null) {
        Log.d(TAG, "Disconnecting");
        gatt.close();
        gatt = null;
        printConnectedDevices(bluetoothManager);
        return;
    }
    printConnectedDevices(bluetoothManager);
    BluetoothDevice bd = bluetoothManager.getAdapter().getRemoteDevice(target);
    if(bd != null) {
        gatt = bd.connectGatt(this, false, new BluetoothGattCallback() {
            @Override
            public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
                Log.d(TAG, "onConnectionStateChange: status "+status+", new state "+newState);
                if(newState == BluetoothProfile.STATE_CONNECTED) {
                    Log.d(TAG, "onConnectionStateChange: Connected");
                    printConnectedDevices(bluetoothManager);
                }
                else if(newState == BluetoothProfile.STATE_DISCONNECTED) {
                    Log.d(TAG, "onConnectionStateChange: Disconnected");
                    printConnectedDevices(bluetoothManager);
                }
            }
        });
    }
});

private void printConnectedDevices(BluetoothManager bluetoothManager) {
    @SuppressLint("MissingPermission") List<BluetoothDevice> devices = bluetoothManager.getConnectedDevices(BluetoothProfile.GATT);
    if(devices != null && !devices.isEmpty()) {
        for(BluetoothDevice bd: devices) {
            Log.d(TAG, "printConnectedDevices: "+bd.getAddress());
        }
    } else {
        Log.d(TAG, "printConnectedDevices: no devices");
    }
}
Run Code Online (Sandbox Code Playgroud)

以下是 2 台设备的结果...

摩托罗拉 G5S Plus - Android 11(运行 LineageOS 18.1)

D/MainActivity: printConnectedDevices: no devices
D/BluetoothGatt: connect() - device: 50:8C:B1:69:E5:63, auto: false
D/BluetoothGatt: registerApp()
D/BluetoothGatt: registerApp() - UUID=2c71d08a-1fb3-411c-8d85-26f49749c932
D/BluetoothGatt: onClientRegistered() - status=0 clientIf=6
D/BluetoothGatt: onClientConnectionState() - status=0 clientIf=6 device=50:8C:B1:69:E5:63
D/MainActivity: onConnectionStateChange: status 0, new state 2
D/MainActivity: onConnectionStateChange: Connected
D/MainActivity: printConnectedDevices: 50:8C:B1:69:E5:63
D/BluetoothGatt: onConnectionUpdated() - Device=50:8C:B1:69:E5:63 interval=6 latency=0 timeout=500 status=0
D/BluetoothGatt: onConnectionUpdated() - Device=50:8C:B1:69:E5:63 interval=30 latency=0 timeout=600 status=0
D/MainActivity: Disconnecting
D/BluetoothGatt: close()
D/BluetoothGatt: unregisterApp() - mClientIf=6
D/MainActivity: printConnectedDevices: no devices
Run Code Online (Sandbox Code Playgroud)

三星 Tab A8 SM-T290 - Android 12 通用系统映像

D/MainActivity: printConnectedDevices: no devices
D/BluetoothGatt: connect() - device: 50:8C:B1:69:E5:63, auto: false
D/BluetoothGatt: registerApp()
D/BluetoothGatt: registerApp() - UUID=558b17e5-6770-4f46-a4d8-4f8d0024d86c
D/BluetoothGatt: onClientRegistered() - status=0 clientIf=5
D/BluetoothGatt: onClientConnectionState() - status=0 clientIf=5 device=50:8C:B1:69:E5:63
D/MainActivity: onConnectionStateChange: status 0, new state 2
D/MainActivity: onConnectionStateChange: Connected
D/MainActivity: printConnectedDevices: 50:8C:B1:69:E5:63
D/BluetoothGatt: onConnectionUpdated() - Device=50:8C:B1:69:E5:63 interval=6 latency=0 timeout=500 status=0
D/BluetoothGatt: onConnectionUpdated() - Device=50:8C:B1:69:E5:63 interval=30 latency=0 timeout=600 status=0
D/MainActivity: Disconnecting
D/BluetoothGatt: close()
D/BluetoothGatt: unregisterApp() - mClientIf=5
D/MainActivity: printConnectedDevices: no devices
Run Code Online (Sandbox Code Playgroud)