Android中使用device filter方案时如何处理USB权限弹窗事件?
我明白你的需求:当通过device_filter.xml让系统自动弹出USB权限弹窗后,你需要知道用户是点击了「确定」还是「取消」,从而实现自动连接功能。之前尝试的方法无效,是因为系统自动触发的权限弹窗,不会通过ACTION_USB_DEVICE_ATTACHED广播返回权限结果,也不会主动发送自定义的ACTION_USB_PERMISSION广播——这个广播只有当你手动调用UsbManager.requestPermission()时才会触发。
下面给你两种可行的解决方案,分别适配不同的Android版本:
方案一:使用UsbDeviceCallback(Android 12+ 推荐)
从Android 12(API 31)开始,UsbManager提供了更直观的回调APIUsbDeviceCallback,可以直接监听设备连接、断开,以及权限授予/拒绝事件。完全可以替代原来的广播接收器,代码更简洁:
public class NativeUsbService { private UsbManager usbManager; private UsbDevice usbDevice; private UsbDeviceConnection connection; private UsbInterface usbDeviceInterface; private UsbEndpoint endpointIn; private UsbEndpoint endpointOut; private static final String TAG = "NativeUsbService"; private ReactApplicationContext reactContext; public NativeUsbService(ReactApplicationContext reactContext) { this.reactContext = reactContext; usbManager = (UsbManager) reactContext.getSystemService(Context.USB_SERVICE); // 注册UsbDeviceCallback UsbDeviceCallback usbDeviceCallback = new UsbDeviceCallback() { @Override public void onUsbDeviceAttached(@NonNull UsbDevice device) { super.onUsbDeviceAttached(device); usbDevice = device; Log.d(TAG, "设备已连接"); // 系统会自动弹出权限框,无需手动请求 } @Override public void onUsbDeviceDetached(@NonNull UsbDevice device) { super.onUsbDeviceDetached(device); Log.d(TAG, "设备已断开"); if (connection != null) { connection.releaseInterface(usbDeviceInterface); connection.close(); } connection = null; usbDevice = null; endpointIn = null; endpointOut = null; sendEvent("onDeviceDetached", true); } @Override public void onUsbDevicePermissionGranted(@NonNull UsbDevice device) { super.onUsbDevicePermissionGranted(device); Log.d(TAG, "用户授予了USB权限"); // 这里执行自动连接逻辑 connectToDevice(device); } @Override public void onUsbDevicePermissionDenied(@NonNull UsbDevice device) { super.onUsbDevicePermissionDenied(device); Log.d(TAG, "用户拒绝了USB权限"); // 处理拒绝后的逻辑,比如提示用户重新授权 sendEvent("onPermissionDenied", true); } }; // 注册回调,指定主线程执行器,传null监听所有设备 usbManager.registerDeviceCallback(reactContext.getMainExecutor(), usbDeviceCallback); } // 自动连接设备的方法 private void connectToDevice(UsbDevice device) { connection = usbManager.openDevice(device); // 示例:获取第一个接口和端点(根据你的设备调整) if (device.getInterfaceCount() > 0) { usbDeviceInterface = device.getInterface(0); if (connection.claimInterface(usbDeviceInterface, true)) { for (int i = 0; i < usbDeviceInterface.getEndpointCount(); i++) { UsbEndpoint endpoint = usbDeviceInterface.getEndpoint(i); if (endpoint.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK) { if (endpoint.getDirection() == UsbConstants.USB_DIR_IN) { endpointIn = endpoint; } else { endpointOut = endpoint; } } } sendEvent("onDeviceAttached", true); } } } // 发送事件到React Native private void sendEvent(String eventName, boolean success) { reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) .emit(eventName, success); } }
这个方案是官方原生支持的,无需轮询或监听系统设置,代码更可靠且易维护。
方案二:兼容Android 12以下版本
如果需要适配更低版本的Android,你可以在收到ACTION_USB_DEVICE_ATTACHED后,通过轮询检查权限状态,或者监听系统USB权限设置的变化:
方法A:轮询检查权限状态
在收到设备连接事件后,启动一个定时任务,每隔几百毫秒检查一次UsbManager.hasPermission(),直到权限状态发生变化:
private final BroadcastReceiver usbReceiver = new BroadcastReceiver() { public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (action.equals(UsbManager.ACTION_USB_DEVICE_ATTACHED)) { synchronized (this) { UsbDevice device = (UsbDevice) intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); if (device != null) { usbDevice = device; // 检查当前是否已有权限 if (usbManager.hasPermission(device)) { connectToDevice(device); } else { // 启动轮询任务,监听权限变化 new Handler(Looper.getMainLooper()).postDelayed(new PermissionCheckRunnable(device), 500); } } else { Log.d(TAG, "onReceive: DEVICE WAS ATTACHED AND WAS NULL :("); sendEvent("onDeviceAttached", false); } } } else if (action.equals(UsbManager.ACTION_USB_DEVICE_DETACHED)) { Log.d(TAG, "onReceive: Device was detached!"); if (connection != null) { connection.releaseInterface(usbDeviceInterface); connection.close(); } connection = null; usbDevice = null; endpointIn = null; endpointOut = null; sendEvent("onDeviceDetached", true); } } }; private class PermissionCheckRunnable implements Runnable { private final UsbDevice device; public PermissionCheckRunnable(UsbDevice device) { this.device = device; } @Override public void run() { if (usbManager.hasPermission(device)) { Log.d(TAG, "用户授予了USB权限"); connectToDevice(device); } else { // 检查是否设备已断开,避免无效轮询 if (usbDevice != null && usbDevice.equals(device)) { new Handler(Looper.getMainLooper()).postDelayed(this, 500); } } } }
方法B:监听系统USB权限设置
通过ContentObserver监听Settings.Secure.USB_PERMISSIONS的变化,这个设置项存储了所有已授权的USB设备信息:
private ContentObserver usbPermissionObserver; public NativeUsbService(ReactApplicationContext reactContext) { this.reactContext = reactContext; usbManager = (UsbManager) reactContext.getSystemService(Context.USB_SERVICE); // 初始化ContentObserver usbPermissionObserver = new ContentObserver(new Handler(Looper.getMainLooper())) { @Override public void onChange(boolean selfChange) { super.onChange(selfChange); if (usbDevice != null) { if (usbManager.hasPermission(usbDevice)) { Log.d(TAG, "用户授予了USB权限"); connectToDevice(usbDevice); // 权限已获取,注销Observer避免重复触发 reactContext.getContentResolver().unregisterContentObserver(this); } else { Log.d(TAG, "用户拒绝了USB权限"); sendEvent("onPermissionDenied", true); reactContext.getContentResolver().unregisterContentObserver(this); } } } }; // 注册广播接收器 IntentFilter filter = new IntentFilter(); filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED); filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED); reactContext.registerReceiver(usbReceiver, filter); } // 在BroadcastReceiver的ACTION_USB_DEVICE_ATTACHED分支中添加: if (device != null) { usbDevice = device; if (!usbManager.hasPermission(device)) { // 开始监听权限变化 reactContext.getContentResolver().registerContentObserver( Settings.Secure.getUriFor(Settings.Secure.USB_PERMISSIONS), false, usbPermissionObserver ); } else { connectToDevice(device); } }
这种方法不需要轮询,更高效,但需要注意在设备断开或权限状态确定后及时注销Observer,避免内存泄漏。
为什么你之前的方法无效?
UsbManager.EXTRA_PERMISSION_GRANTED参数只会出现在你主动调用UsbManager.requestPermission()后系统发送的ACTION_USB_PERMISSION广播中,系统自动弹出的权限弹窗不会触发这个广播。- 自定义的
ACTION_USB_PERMISSION广播只有在你手动发起权限请求时才会被系统发送,系统自动处理的权限流程不会使用这个自定义action。
希望这些方案能帮你解决问题!
内容的提问来源于stack exchange,提问作者Cosby

