Flutter使用usb_serial插件连接USB串口设备遇权限错误求助
解决Flutter USB串口权限问题(TvBox H96Max设备)
问题描述
在TvBox H96Max设备上运行基于usb_serial插件的Flutter USB串口交互应用(使用官方示例代码)时,触发UsbManager.openDevice异常,核心错误为:
java.lang.SecurityException: User has not given 10087/com.example.untitled4 permission to access device /dev/bus/usb/001/002
导致无法打开USB串口端口,无法与设备进行数据交互。
原代码
import 'dart:async'; import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:usb_serial/transaction.dart'; import 'package:usb_serial/usb_serial.dart'; void main() => runApp(MyApp()); class MyApp extends StatefulWidget { @override _MyAppState createState() => _MyAppState(); } class _MyAppState extends State<MyApp> { UsbPort? _port; String _status = "Idle"; List<Widget> _ports = []; List<Widget> _serialData = []; StreamSubscription<String>? _subscription; Transaction<String>? _transaction; UsbDevice? _device; TextEditingController _textController = TextEditingController(); Future<bool> _connectTo(device) async { _serialData.clear(); if (_subscription != null) { _subscription!.cancel(); _subscription = null; } if (_transaction != null) { _transaction!.dispose(); _transaction = null; } if (_port != null) { _port!.close(); _port = null; } if (device == null) { _device = null; setState(() { _status = "Disconnected"; }); return true; } _port = await device.create(); if (await (_port!.open()) != true) { setState(() { _status = "Failed to open port"; }); return false; } _device = device; await _port!.setDTR(true); await _port!.setRTS(true); await _port!.setPortParameters(115200, UsbPort.DATABITS_8, UsbPort.STOPBITS_1, UsbPort.PARITY_NONE); _transaction = Transaction.stringTerminated(_port!.inputStream as Stream<Uint8List>, Uint8List.fromList([13, 10])); _subscription = _transaction!.stream.listen((String line) { setState(() { _serialData.add(Text(line)); if (_serialData.length > 20) { _serialData.removeAt(0); } }); }); setState(() { _status = "Connected"; }); return true; } void _getPorts() async { _ports = []; List<UsbDevice> devices = await UsbSerial.listDevices(); if (!devices.contains(_device)) { _connectTo(null); } print(devices); devices.forEach((device) { _ports.add(ListTile( leading: Icon(Icons.usb), title: Text(device.productName!), subtitle: Text(device.manufacturerName!), trailing: ElevatedButton( child: Text(_device == device ? "Disconnect" : "Connect"), onPressed: () { _connectTo(_device == device ? null : device).then((res) { _getPorts(); }); }, ))); }); setState(() { print(_ports); }); } @override void initState() { super.initState(); UsbSerial.usbEventStream!.listen((UsbEvent event) { _getPorts(); }); _getPorts(); } @override void dispose() { super.dispose(); _connectTo(null); } @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar( title: const Text('USB Serial Plugin example app'), ), body: Center( child: Column(children: <Widget>[ Text(_ports.length > 0 ? "Available Serial Ports" : "No serial devices available", style: Theme.of(context).textTheme.headline6), ..._ports, Text('Status: $_status\n'), Text('info: ${_port.toString()}\n'), ListTile( title: TextField( controller: _textController, decoration: InputDecoration( border: OutlineInputBorder(), labelText: 'Text To Send', ), ), trailing: ElevatedButton( child: Text("Send"), onPressed: _port == null ? null : () async { if (_port == null) { return; } String data = _textController.text + "\r\n"; await _port!.write(Uint8List.fromList(data.codeUnits)); _textController.text = ""; }, ), ), Text("Result Data", style: Theme.of(context).textTheme.headline6), ..._serialData, ])), )); } }
完整错误信息
exception in UsbManager.openDevice E/UsbManager(19118): java.lang.SecurityException: User has not given 10087/com.example.untitled4 permission to access device /dev/bus/usb/001/002 E/UsbManager(19118): at android.os.Parcel.createExceptionOrNull(Parcel.java:2426) E/UsbManager(19118): at android.os.Parcel.createException(Parcel.java:2410) E/UsbManager(19118): at android.os.Parcel.readException(Parcel.java:2393) E/UsbManager(19118): at android.os.Parcel.readException(Parcel.java:2335) E/UsbManager(19118): at android.hardware.usb.IUsbManager$Stub$Proxy.openDevice(IUsbManager.java:1022) E/UsbManager(19118): at android.hardware.usb.UsbManager.openDevice(UsbManager.java:719) E/UsbManager(19118): at dev.bessems.usbserial.UsbSerialPlugin.openDevice(UsbSerialPlugin.java:145) E/UsbManager(19118): at dev.bessems.usbserial.UsbSerialPlugin.createTyped(UsbSerialPlugin.java:186) E/UsbManager(19118): at dev.bessems.usbserial.UsbSerialPlugin.onMethodCall(UsbSerialPlugin.java:288) E/UsbManager(19118): at io.flutter.plugin.common.MethodChannel$IncomingMethodCallHandler.onMessage(MethodChannel.java:258) E/UsbManager(19118): at io.flutter.embedding.engine.dart.DartMessenger.invokeHandler(DartMessenger.java:295) E/UsbManager(19118): at io.flutter.embedding.engine.dart.DartMessenger.lambda$dispatchMessageToQueue$0$io-flutter-embedding-engine-dart-DartMessenger(DartMessenger.java:322) E/UsbManager(19118): at io.flutter.embedding.engine.dart.DartMessenger$$ExternalSyntheticLambda0.run(Unknown Source:12) E/UsbManager(19118): at android.os.Handler.handleCallback(Handler.java:938) E/UsbManager(19118): at android.os.Handler.dispatchMessage(Handler.java:99) E/UsbManager(19118): at android.os.Looper.loopOnce(Looper.java:201) E/UsbManager(19118): at android.os.Looper.loop(Looper.java:288) E/UsbManager(19118): at android.app.ActivityThread.main(ActivityThread.java:7870) E/UsbManager(19118): at java.lang.reflect.Method.invoke(Native Method) E/UsbManager(19118): at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548) E/UsbManager(19118): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1003) D/CompatibilityChangeReporter(19118): Compat change id reported: 160794467; UID 10087; state: ENABLED E/flutter (19118): [ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: PlatformException(UsbSerialPortAdapter, Failed to acquire USB device., null, null) E/flutter (19118): #0 StandardMethodCodec.decodeEnvelope (package:flutter/src/services/message_codecs.dart:653:7) E/flutter (19118): #1 MethodChannel._invokeMethod (package:flutter/src/services/platform_channel.dart:315:18) E/flutter (19118): <asynchronous suspension> E/flutter (19118): #2 UsbSerial.createFromDeviceId (package:usb_serial/usb_serial.dart:433:33) E/flutter (19118): <asynchronous suspension> E/flutter (19118): #3 UsbDevice.create (package:usb_serial/usb_serial.dart:333:13) E/flutter (19118): <asynchronous suspension> E/flutter (19118): #4 _MyAppState.build.<anonymous closure> (package:untitled4/main.dart:175:26) E/flutter (19118): <asynchronous suspension>
解决方案
1. 配置AndroidManifest.xml权限
在android/app/src/main/AndroidManifest.xml中添加以下权限和配置:
<!-- USB主机功能声明 --> <uses-feature android:name="android.hardware.usb.host" /> <!-- USB设备访问权限 --> <uses-permission android:name="android.permission.USB_PERMISSION" /> <application ...> <!-- 监听USB设备连接事件 --> <activity ...> <intent-filter> <action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" /> </intent-filter> <meta-data android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" android:resource="@xml/usb_device_filter" /> </activity> </application>
若需指定特定USB设备,创建android/app/src/main/res/xml/usb_device_filter.xml文件:
<?xml version="1.0" encoding="utf-8"?> <resources> <usb-device vendor-id="你的设备VID" product-id="你的设备PID" /> </resources>
(设备VID/PID可通过应用设备列表中的device.vid和device.pid获取)
2. 修改代码添加动态权限请求
在_connectTo方法中,打开设备前先请求USB权限,修改后的核心代码如下:
Future<bool> _connectTo(device) async { // 原有清理代码保持不变 if (device == null) { _device = null; setState(() { _status = "Disconnected"; }); return true; } // 新增:请求USB设备权限 bool hasPermission = await UsbSerial.requestPermission(device); if (!hasPermission) { setState(() { _status = "Permission denied"; }); return false; } // 后续打开设备及配置代码保持不变 _port = await device.create(); if (await (_port!.open()) != true) { setState(() { _status = "Failed to open port"; }); return false; } _device = device; // 后续串口参数配置、数据流监听代码保持不变 }
3. TvBox设备特殊适配
- 进入TvBox系统设置,找到应用权限管理,手动给目标应用开启USB访问权限;
- 若设备有开发者选项,开启USB调试(部分TvBox需此选项才能正常识别USB主机设备);
- 确保USB串口设备已正确连接,且设备本身支持Android主机模式。
内容的提问来源于stack exchange,提问作者Reem
相关产品推荐
相关产品推荐

