You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android高版本Thread类调用cancelDiscovery需BLUETOOTH_SCAN权限问题

问题

我正在开发一款通过蓝牙连接第三方设备的Android应用,项目中使用继承自Thread的BTClient类实现设备连接,代码如下:

public class BTClient extends Thread {
    UUID MY_UUID = UUID.fromString("00001101-0000");

    private BluetoothAdapter _adapter;

    private BluetoothSocket _btStream;
    private String _connectionString;

    //private OnSocketClosedErrorListener _socketClosedErrorListener;

    private BluetoothDevice _device;

    public BluetoothDevice getDevice() {
        return _device;
    }

    private BTComms _comms;

    public BTComms getComms() {
        return _comms;
    }


    private boolean _isConnected = false;

    public boolean IsConnected() {
        return _isConnected;
    }

    public boolean _isValidBlueToothAddress = false;

    public void IsValidBlueToothAddress() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ECLAIR) {
            _isValidBlueToothAddress = _adapter.checkBluetoothAddress(_connectionString);
        }
    }

    private Vector<ConnectedListener<BTClient>> eventSubscribers = new Vector<ConnectedListener<BTClient>>();

    /*This is an array of objects that implement the ConnectedListener interface. Right now the only class that
    implements it is ConnectListenerImpl class and it implements the Connected method*/
    private void OnConnected() {
        @SuppressWarnings("unchecked")
        //Create a new  Vector with exactly same elements as eventSubscribers
                Vector<ConnectedListener<BTClient>> handler = (Vector<ConnectedListener<BTClient>>) eventSubscribers.clone();
        Iterator<ConnectedListener<BTClient>> iter = handler.iterator();
        while (iter.hasNext()) {
            iter.next().Connected(new ConnectedEvent<BTClient>(this));
        }
    }

    public void addConnectedEventListener(ConnectedListener<BTClient> listener) {
        eventSubscribers.add(listener);
    }

    public void removeConnectedEventListener(ConnectedListener<BTClient> listener) {
        eventSubscribers.remove(listener);
    }

    @SuppressLint("MissingPermission")
    public BTClient(BluetoothAdapter adapter, String connectionString/*, OnSocketClosedErrorListener listener*/) throws IOException {
       // _socketClosedErrorListener = listener;
        //Initialize the class variables
        _adapter = adapter; //Assign the adaptor
        // Initializing all other variables to null
        _isConnected = false;
        _comms = null;
        _btStream = null;
        _device = null;
        _connectionString = connectionString;
        IsValidBlueToothAddress();
        //Get a device for the given BT MAC address
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ECLAIR) {
            _device = _adapter.getRemoteDevice(_connectionString);
        }
        /* try {*/
            //Create a Secure RFComm Bluetooth socket for secure outgoing connection with device
            //using a given "UUID--using well known UUID SPP suggested by Android??"

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ECLAIR) {
            _btStream = _device.createRfcommSocketToServiceRecord(MY_UUID);
        }
        if (_btStream != null) {    //Attempt to connect to a remote device...Needs an exception check here to
                //see if it fails
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ECLAIR) {
                _btStream.connect();
            }
            _isConnected = true;
            }
        /*} catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }*/
    }

    @Override
    public void run() {
        //Needs to call this to be sure enough to cancel a discovery service
        //since it is heavy weight service
        //if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ECLAIR) {
        _adapter.cancelDiscovery();
        //}
        if (_isConnected)
            StartCommunication();
        else
            System.out.println("Can't connect to the BioHarness.");
    }



    private void StartCommunication() {
        //Create a new commmunication thread and run it
        _comms = new BTComms(_btStream/*, _socketClosedErrorListener*/);
        _comms.start();

        //Calls Connected method which calls ReceivedPacketMethod that prints information on the phone's text box based on the message type
        OnConnected();
    }

    public void Close() {
        if (_btStream != null) {
            try {
                if (_comms != null)
                    _comms.Close();
                if (_btStream != null)
                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ECLAIR) {
                        _btStream.close();
                    }
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        _isConnected = false;
        //_btStream = null;
    }
}

该代码在Android 8系统上可正常运行,但适配更高版本Android系统时,尝试连接设备会抛出如下错误:

FATAL EXCEPTION: Thread-3
Process: it.rmountain, PID: 10445
java.lang.SecurityException: Need android.permission.BLUETOOTH_SCAN permission for android.content.AttributionSource@b09f4322: AdapterService cancelDiscovery
    at android.os.Parcel.createExceptionOrNull(Parcel.java:2426)
    at android.os.Parcel.createException(Parcel.java:2410)
    at android.os.Parcel.readException(Parcel.java:2393)
    at android.os.Parcel.readException(Parcel.java:2335)
    at android.bluetooth.IBluetooth$Stub$Proxy.cancelDiscovery(IBluetooth.java:3192)
    at android.bluetooth.BluetoothAdapter.cancelDiscovery(BluetoothAdapter.java:1876)
    at zephyr.android.BioHarnessBT.BTClient.run(BTClient.java:119)

错误出现在BTClient.java的_adapter.cancelDiscovery();行,我已在AndroidManifest.xml中配置了相关权限:

<uses-permission
        android:name="android.permission.BLUETOOTH"
        tools:ignore="ManifestOrder" />
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
    <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
    <uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
    <uses-permission android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS"/>
    <uses-permission android:name="android.permission.BLUETOOTH_PRIVILEGED"
        tools:ignore="ProtectedPermissions" />

请问该如何解决这个权限异常问题?

解决方案
  • 申请危险权限的运行时授权
    Android 12(API 31)及以上版本中,BLUETOOTH_SCAN、BLUETOOTH_CONNECT属于危险权限,仅在Manifest中声明无法生效,必须在代码中主动向用户申请。可以在启动蓝牙连接前,执行权限申请逻辑:

    private static final int REQUEST_BLUETOOTH_PERMISSIONS = 1;
    
    // 在Activity或Fragment中调用该方法
    private void requestBluetoothPermissions() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
            String[] permissions = {
                Manifest.permission.BLUETOOTH_SCAN,
                Manifest.permission.BLUETOOTH_CONNECT
            };
            requestPermissions(permissions, REQUEST_BLUETOOTH_PERMISSIONS);
        }
    }
    

    必须等用户授权通过后,再初始化BTClient并执行连接操作。

  • 移除权限抑制注解并添加权限检查
    你的BTClient构造函数标注了@SuppressLint("MissingPermission"),这会跳过系统的权限校验,导致权限缺失时直接崩溃。需要移除该注解,并在调用蓝牙相关API前添加权限检查:
    修改run()方法:

    @Override
    public void run() {
        // 针对Android 12+版本检查BLUETOOTH_SCAN权限
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
            if (ContextCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.BLUETOOTH_SCAN) != PackageManager.PERMISSION_GRANTED) {
                // 权限未获取,可通过回调通知上层申请,此处直接返回避免崩溃
                return;
            }
        }
        _adapter.cancelDiscovery();
        if (_isConnected)
            StartCommunication();
        else
            System.out.println("Can't connect to the BioHarness.");
    }
    

    同样,在构造函数中调用_btStream.connect()等蓝牙方法前,也要添加对应的权限检查。

  • 优化Manifest权限配置
    删除重复声明的ACCESS_FINE_LOCATION和ACCESS_COARSE_LOCATION权限;另外BLUETOOTH_PRIVILEGED是系统级权限,普通应用无法获取,建议移除该声明,避免混淆。

  • 适配版本差异
    Android 12及以上的蓝牙权限模型和旧版本差异明显:

    • 扫描设备需BLUETOOTH_SCAN权限
    • 连接设备需BLUETOOTH_CONNECT权限
    • 旧版本的BLUETOOTH和BLUETOOTH_ADMIN在API 31+已变为普通权限,无需运行时申请,但仍需在Manifest中声明。

内容的提问来源于stack exchange,提问作者bircastri

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 17:32:03