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

Android 14下USB串口通信权限申请失败问题求助

Android 14下USB权限申请异常问题

我开发了一款连接带重量传感器Arduino的Android应用,在Android 11上运行正常,但在Android 14环境测试时出现异常:要么不弹出USB权限申请弹窗直接自动拒绝,要么弹出权限申请后点击同意仍被拒绝。

已确认设备支持OTG,且使用“Serial USB Terminal”应用可正常与Arduino通信,说明硬件通信无问题。我并非编程新手,但刚接触Kotlin且久未编码。

以下是相关代码与日志:

USB通信处理类代码

class IOPesa(private val context: Context) {
    private var serialPort: UsbSerialDevice? = null
    private var responseData: String? = null
    private val usbManager: UsbManager = context.getSystemService(Context.USB_SERVICE) as UsbManager
    private val responseLatch = CountDownLatch(1)

    companion object {
        private const val ACTION_USB_PERMISSION = "com.example.testapp.USB_PERMISSION"
        private const val TAG = "IOPesa"
    }

    private val usbPermissionReceiver = object : BroadcastReceiver() {
        override fun onReceive(context: Context, intent: Intent) {
            synchronized(this) {
                if (ACTION_USB_PERMISSION == intent.action) {
                    val device: UsbDevice? = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE)
                    Log.d(TAG, "Broadcast received. Device: $device")
                    if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false) && device != null) {
                        Log.d(TAG, "Permission granted for device: ${device.deviceName}")
                        connectToDevice(device)
                    } else {
                        Log.e(TAG, "Permission denied for the device ${device?.deviceName}")
                    }
                }
            }
        }
    }

    init {
        val filter = IntentFilter(ACTION_USB_PERMISSION)
        context.registerReceiver(usbPermissionReceiver, filter, Context.RECEIVER_EXPORTED)
        discoverDeviceAndRequestPermission()
    }

    private fun discoverDeviceAndRequestPermission() {
        val deviceList = usbManager.deviceList
        val device = deviceList.values.firstOrNull()
        if (device != null) {
            Log.d(TAG, "Device found: ${device.deviceName}, requesting permission...")
            requestPermission(device)
        } else {
            Log.e(TAG, "No USB devices connected.")
        }
    }

    private fun requestPermission(device: UsbDevice) {
        Log.d(TAG, "Requesting permission for device: ${device.deviceName}")
        val permissionIntent = PendingIntent.getBroadcast(context, 0, Intent(ACTION_USB_PERMISSION), PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
        usbManager.requestPermission(device, permissionIntent)
        Log.d(TAG, "Permission intent sent. Device: $device, PermissionIntent: $permissionIntent")
    }

    private fun connectToDevice(device: UsbDevice) {
        val connection = usbManager.openDevice(device)
        serialPort = UsbSerialDevice.createUsbSerialDevice(device, connection)
        if (serialPort != null && serialPort!!.open()) {
            Log.d(TAG, "Connection opened, configuring serial port...")
            configureSerialConnection()
        } else {
            Log.e(TAG, "Could not open the device connection or create serial device.")
        }
    }

    private fun configureSerialConnection() {
        serialPort?.apply {
            setBaudRate(9600)
            setDataBits(UsbSerialInterface.DATA_BITS_8)
            setStopBits(UsbSerialInterface.STOP_BITS_1)
            setParity(UsbSerialInterface.PARITY_NONE)
            setFlowControl(UsbSerialInterface.FLOW_CONTROL_OFF)
            read { data ->
                responseData = String(data)
                Log.d(TAG, "Received data: $responseData")
                responseLatch.countDown()
            }
        }
        Log.d(TAG, "Serial port configured.")
    }

    fun enviar(message: String): String {
        Log.d(TAG, "Sending message: $message")

        responseData = null  // Resetear los datos antes de enviar
        responseLatch.await(0, TimeUnit.SECONDS)  // Restablecer el latch

        serialPort?.write(message.toByteArray())

        responseLatch.await(2, TimeUnit.SECONDS)  // Esperar hasta 2 segundos para la respuesta

        return responseData ?: "No response received"
    }

    fun disconnect() {
        serialPort?.close()
        context.unregisterReceiver(usbPermissionReceiver)
        Log.d(TAG, "Disconnected and receiver unregistered.")
    }
}

Manifest配置

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="com.example.testapp">

    <uses-feature android:name="android.hardware.usb.host" />
    <uses-feature android:name="android.hardware.camera" android:required="false" />
    <uses-permission android:name="android.permission.USB_PERMISSION" />
    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.permission.USB_HOST" />
    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
    <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.TestApp"
        tools:targetApi="31">
        <activity
            android:name=".MainActivity"
            android:exported="true"
            android:label="@string/app_name"
            android:theme="@style/Theme.TestApp"
            android:screenOrientation="landscape">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

异常Logcat日志

设备发现

2024-06-22 23:49:05.360 30020-30020 IOPesa                  com.example.testapp                  D  Device found: /dev/bus/usb/001/002, requesting permission...

请求权限

2024-06-22 23:49:05.360 30020-30020 IOPesa                  com.example.testapp                  D  Requesting permission for device: /dev/bus/usb/001/002

请求已发送

2024-06-22 23:49:05.377 30020-30020 IOPesa                  com.example.testapp                  D  Permission intent sent. Device: UsbDevice[mName=/dev/bus/usb/001/002,mVendorId=6790,mProductId=29987,mClass=255,mSubclass=0,mProtocol=0,mManufacturerName=null,mProductName=USB Serial,mVersion=2.64,mSerialNumberReader=android.hardware.usb.IUsbSerialReader$Stub$Proxy@7e3e8bc, mHasAudioPlayback=false, mHasAudioCapture=false, mHasMidi=false, mHasVideoCapture=false, mHasVideoPlayback=false, mConfigurations=[
                                                                                                    UsbConfiguration[mId=1,mName=null,mAttributes=128,mMaxPower=49,mInterfaces=[
                                                                                                    UsbInterface[mId=0,mAlternateSetting=0,mName=null,mClass=255,mSubclass=1,mProtocol=2,mEndpoints=[
                                                                                                    UsbEndpoint[mAddress=130,mAttributes=2,mMaxPacketSize=32,mInterval=0]
                                                                                                    UsbEndpoint[mAddress=2,mAttributes=2,mMaxPacketSize=32,mInterval=0]
                                                                                                    UsbEndpoint[mAddress=129,mAttributes=3,mMaxPacketSize=8,mInterval=1]]]], PermissionIntent: PendingIntent{a5b9045: android.os.BinderProxy@94fa39a}

权限被拒绝

无论是否弹出权限申请弹窗,点击同意后均收到该日志:

2024-06-22 23:49:07.736 30020-30020 IOPesa                  com.example.testapp                  E  Permission denied for the device null

问题修复方案

1. 适配Android 14的Parcelable获取方式

日志中出现device null是因为API 33+推荐使用类型安全的Parcelable获取方法,修改广播接收器中的设备获取代码:

val device: UsbDevice? = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE, UsbDevice::class.java)

2. 优化广播接收器与权限声明

在Manifest中添加自定义权限,防止其他应用恶意触发权限广播:

<permission
    android:name="com.example.testapp.USB_PERMISSION"
    android:protectionLevel="signature" />

同时修改广播接收器的注册逻辑,添加权限校验:

context.registerReceiver(
    usbPermissionReceiver,
    filter,
    "com.example.testapp.USB_PERMISSION",
    null,
    Context.RECEIVER_EXPORTED
)

3. 调整权限申请时机

Android 14限制后台发起权限请求,将discoverDeviceAndRequestPermission()从init块移出,改为在用户交互事件(如按钮点击)中调用:

// 移除init块中的discoverDeviceAndRequestPermission()调用
// 在MainActivity中添加触发逻辑
buttonConnect.setOnClickListener {
    ioPesa.discoverDeviceAndRequestPermission()
}

4. 添加USB设备过滤规则

在Manifest的MainActivity中添加设备连接的intent-filter与元数据:

<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>

在res/xml目录创建usb_device_filter.xml,填入你的Arduino设备ID:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <usb-device vendor-id="6790" product-id="29987" />
</resources>

5. 检查PendingIntent的兼容性

虽然你已使用FLAG_IMMUTABLE,但确保你的UsbSerial库适配Android 14,若仍存在问题,可尝试临时改用FLAG_MUTABLE(仅测试使用,正式版推荐保持FLAG_IMMUTABLE):

val permissionIntent = PendingIntent.getBroadcast(
    context,
    0,
    Intent(ACTION_USB_PERMISSION),
    PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 21:38:13