Android 11应用USB重连时自动授予权限方案咨询
问题
我有一款搭载在自有设备上的Android 11应用,该设备不会安装其他应用,且因硬件老旧不会接收系统更新。应用需通过UART通信实现功能,目前每次连接USB线都会弹出权限申请弹窗,授权后可正常工作,但USB断开重连时会重复弹窗。我已实现弹窗抑制并在本地存储权限记录,但仍无法实现重连时自动授权,直接打开串口会抛出SecurityException。
Manifest代码
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"> <uses-feature android:name="android.hardware.usb.host" /> <application android:allowBackup="true" android:dataExtractionRules="@xml/data_extraction_rules" android:fullBackupContent="@xml/backup_rules" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/Theme.***themename***" android:directBootAware="true" tools:targetApi="31"> <activity android:name=".MainActivity" android:screenOrientation="landscape" android:exported="true"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <receiver android:name=".USBReceiver.UsbDeviceReceiver" android:enabled="true" android:exported="true"> <intent-filter> <action android:name="com.example.packagename.USB_PERMISSION" /> <action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" /> <action android:name="android.hardware.usb.action.USB_DEVICE_DETACHED" /> </intent-filter> <meta-data android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" android:resource="@xml/device_filter" /> </receiver> </application> </manifest>
UsbDeviceReceiver代码
public class UsbDeviceReceiver extends BroadcastReceiver { private static final String ACTION_USB_PERMISSION = "com.example.packagename.USB_PERMISSION"; private static final String ACTION_USB_ATTACHED = "android.hardware.usb.action.USB_DEVICE_ATTACHED"; private static final String ACTION_USB_DETACHED = "android.hardware.usb.action.USB_DEVICE_DETACHED"; @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); UsbManager usbManager = (UsbManager) context.getSystemService(Context.USB_SERVICE); if (ACTION_USB_ATTACHED.equals(action)) { // Handle USB device attachment showToast(context, "USB Device Attached"); Log.d("USB PERMISSION","has permission:- "+usbManager.hasPermission(device)); // Check if permission is already granted for this device synchronized (this) { if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) { if (device != null) { Log.d("USB PERMISSION", "permission exists"); // Permission granted, open the device SerialPortManagerSingleton.getInstance(context, null).openSerialPort((UsbManager) context.getSystemService(Context.USB_SERVICE)); } } else { Log.d("USB PERMISSION", "permission does not exist"); // Permission denied PendingIntent permissionIntent = PendingIntent.getBroadcast( context, 0, new Intent(ACTION_USB_PERMISSION), PendingIntent.FLAG_UPDATE_CURRENT | (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S ? PendingIntent.FLAG_IMMUTABLE : 0) ); usbManager.requestPermission(device, permissionIntent); } } } else if (ACTION_USB_DETACHED.equals(action)) { // Handle USB device detachment showToast(context, "USB Device Detached"); SerialPortManagerSingleton.releaseInstance(); } else if (ACTION_USB_PERMISSION.equals(action)) { synchronized (this) { if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) { if (device != null) { Log.d("USB PERMISSION", "granted permission"); Log.d("USB PERMISSION","has permission:- "+usbManager.hasPermission(device)); // Permission granted, open the device SerialPortManagerSingleton.getInstance(context, null).openSerialPort((UsbManager) context.getSystemService(Context.USB_SERVICE)); } } else { Log.d("USB PERMISSION", "Permission denied for device " + device); // Handle permission denial } } } } private void showToast(Context context, String message) { Toast.makeText(context, message, Toast.LENGTH_SHORT).show(); } }
SerialPortManagerSingleton代码
public class SerialPortManagerSingleton { private static SerialPortManager instance; private static SerialPortManager.CombinedSerialListener savedListener; public static synchronized SerialPortManager getInstance(Context appContext, SerialPortManager.CombinedSerialListener listener) { if (instance == null) { // savedListener = listener; Log.d("saved","listener "+savedListener); instance = new SerialPortManager(savedListener); instance.openSerialPort((UsbManager) appContext.getSystemService(Context.USB_SERVICE)); } else { if (listener != null) { savedListener = listener; Log.d("saved","listener set"+savedListener); instance.setListener(savedListener); } else if (savedListener != null) { instance.setListener(savedListener); } } return instance; } public static synchronized void releaseInstance() { if (instance != null) { instance.closeSerialPort(); instance = null; // savedListener = null; } } }
解决方案
1. 修正当前代码的权限检查逻辑
你当前在ACTION_USB_ATTACHED分支里错误地通过intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)判断权限——这个Extra仅在ACTION_USB_PERMISSION的Intent中存在,ATTACHED的Intent里并没有该值,导致每次都会触发权限申请。
修改ACTION_USB_ATTACHED的处理逻辑,直接调用usbManager.hasPermission(device)判断权限:
if (ACTION_USB_ATTACHED.equals(action)) { showToast(context, "USB Device Attached"); if (device == null) return; Log.d("USB PERMISSION","has permission:- "+usbManager.hasPermission(device)); if (usbManager.hasPermission(device)) { // 已有权限,直接打开串口 SerialPortManagerSingleton.getInstance(context, null).openSerialPort(usbManager); } else { // 无权限,申请权限 PendingIntent permissionIntent = PendingIntent.getBroadcast( context, 0, new Intent(ACTION_USB_PERMISSION), PendingIntent.FLAG_UPDATE_CURRENT | (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S ? PendingIntent.FLAG_IMMUTABLE : 0) ); usbManager.requestPermission(device, permissionIntent); } }
注意:该方法仅能解决同一应用生命周期内重连的权限问题,重启设备或应用后权限会失效,仍需重新授权。
2. 将应用设为系统特权应用(彻底解决弹窗)
因为设备是自有设备,最彻底的方案是将应用签名为系统应用,直接调用UsbManager.grantPermission()自动授权,无需用户交互:
- 用设备厂商/自定义ROM的系统签名密钥给应用签名,安装到
/system/app或/system/priv-app目录,并在Manifest中添加android:sharedUserId="android.uid.system"。 - 修改USB连接逻辑:
if (ACTION_USB_ATTACHED.equals(action)) { showToast(context, "USB Device Attached"); if (device == null) return; // 直接授予权限 usbManager.grantPermission(device); // 打开串口 SerialPortManagerSingleton.getInstance(context, null).openSerialPort(usbManager); }
此方案可解决所有场景下的权限弹窗问题,包括设备重启后的重连。
3. 利用设备所有者模式(企业设备方案)
如果设备支持设备所有者模式,可通过DevicePolicyManager设置USB权限白名单:
- 通过adb命令将应用设为设备所有者:
dpm set-device-owner com.example.packagename/.YourDeviceAdminReceiver - 在代码中添加USB权限规则:
DevicePolicyManager dpm = (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE); ComponentName adminComponent = new ComponentName(context, YourDeviceAdminReceiver.class); // 创建USB设备规则 UsbDevicePolicy usbPolicy = new UsbDevicePolicy.Builder() .setDeviceId(device.getDeviceId()) .setVendorId(device.getVendorId()) .setProductId(device.getProductId()) .build(); // 添加规则,允许应用访问该USB设备 dpm.addUsbDevicePermissionRule(adminComponent, usbPolicy, true);
该方案无需系统签名,适合企业批量管理的设备。
内容的提问来源于stack exchange,提问作者Kumar Rakesh
相关产品推荐
相关产品推荐

