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

