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

Android 13(API33)读取USB设备遇EACCES权限拒绝问题求助

问题分析与解决方案

核心错误原因

  • 你直接通过FileInputStream访问/dev/bus/usb/xxx路径是完全错误的操作方式。Android的USB Host API设计是通过UsbManager、UsbDeviceConnection和端点(Endpoint)完成设备通信,而非直接操作/dev下的系统设备节点文件——这些文件属于系统级资源,普通应用即便拿到USB权限也无法直接打开。
  • 你申请的READ_EXTERNAL_STORAGE、READ_MEDIA_*权限是针对外部存储(如SD卡)的,和USB设备通信完全无关,这些权限解决不了当前问题。

正确的USB设备数据读取流程

1. 确认USB权限已成功获取

你当前的requestUSBPermission方法只是发起权限请求,还需要通过广播接收器确认用户是否授予权限:

private val usbPermissionReceiver = object : BroadcastReceiver() {
    override fun onReceive(context: Context?, intent: Intent?) {
        if (ACTION_USB_PERMISSION == intent?.action) {
            synchronized(this) {
                val device = intent.getParcelableExtra<UsbDevice>(UsbManager.EXTRA_DEVICE)
                if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
                    device?.let {
                        // 权限已授予,在这里启动设备通信逻辑
                        readUsbDeviceData(it)
                    }
                } else {
                    Toast.makeText(context, "USB权限被拒绝", Toast.LENGTH_SHORT).show()
                }
            }
        }
    }
}

// 在Activity的onCreate中注册广播
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    // ...其他初始化代码
    val filter = IntentFilter(ACTION_USB_PERMISSION)
    registerReceiver(usbPermissionReceiver, filter)
}

// 在onDestroy中注销广播,避免内存泄漏
override fun onDestroy() {
    super.onDestroy()
    unregisterReceiver(usbPermissionReceiver)
}

2. 通过USB Host API读取设备数据

使用官方API通过端点传输数据,而非直接访问文件,示例代码如下:

fun readUsbDeviceData(device: UsbDevice) {
    val manager = getSystemService(USB_SERVICE) as UsbManager
    val connection = manager.openDevice(device) ?: run {
        Toast.makeText(this, "无法打开USB设备", Toast.LENGTH_SHORT).show()
        return
    }

    // 声明要使用的USB接口(通常为接口0,需根据你的设备实际情况调整)
    val usbInterface = device.getInterface(0)
    if (!connection.claimInterface(usbInterface, true)) {
        Toast.makeText(this, "无法声明USB接口", Toast.LENGTH_SHORT).show()
        connection.close()
        return
    }

    // 找到用于接收数据的批量传输端点(USB_DIR_IN方向)
    var inputEndpoint: UsbEndpoint? = null
    for (i in 0 until usbInterface.endpointCount) {
        val endpoint = usbInterface.getEndpoint(i)
        if (endpoint.type == UsbConstants.USB_ENDPOINT_XFER_BULK 
            && endpoint.direction == UsbConstants.USB_DIR_IN) {
            inputEndpoint = endpoint
            break
        }
    }

    inputEndpoint?.let { endpoint ->
        // 准备缓冲区,大小需匹配设备端点的最大包长
        val buffer = ByteArray(endpoint.maxPacketSize)
        try {
            // 从端点读取数据,超时时间设为5000ms
            val bytesRead = connection.bulkTransfer(endpoint, buffer, buffer.size, 5000)
            if (bytesRead > 0) {
                // 处理读取到的数据,示例:保存到应用私有目录
                val outputFile = File(filesDir, "received_data.mp4")
                FileOutputStream(outputFile).use { outputStream ->
                    outputStream.write(buffer, 0, bytesRead)
                }
                Toast.makeText(this, "数据读取成功", Toast.LENGTH_SHORT).show()
            } else {
                Toast.makeText(this, "未读取到数据", Toast.LENGTH_SHORT).show()
            }
        } catch (e: Exception) {
            e.printStackTrace()
            Toast.makeText(this, "读取失败: ${e.message}", Toast.LENGTH_SHORT).show()
        } finally {
            // 释放接口并关闭连接
            connection.releaseInterface(usbInterface)
            connection.close()
        }
    } ?: run {
        Toast.makeText(this, "未找到可用的输入端点", Toast.LENGTH_SHORT).show()
        connection.releaseInterface(usbInterface)
        connection.close()
    }
}

3. 额外注意事项

  • 如果是U盘这类存储设备,不需要用USB Host API直接读取端点,建议使用**Storage Access Framework(SAF)**让用户选择文件,操作更简单且无需处理复杂的USB协议:
    private const val REQUEST_CODE_OPEN_DOCUMENT = 1001
    
    // 启动文件选择器
    fun openUsbFilePicker() {
        val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
            addCategory(Intent.CATEGORY_OPENABLE)
            type = "video/mp4" // 匹配你要读取的文件类型
        }
        startActivityForResult(intent, REQUEST_CODE_OPEN_DOCUMENT)
    }
    
    // 处理选择结果
    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        if (requestCode == REQUEST_CODE_OPEN_DOCUMENT && resultCode == RESULT_OK) {
            data?.data?.let { uri ->
                contentResolver.openInputStream(uri)?.use { inputStream ->
                    // 读取文件内容逻辑
                }
            }
        }
    }
    
  • 若为自定义USB设备,需在Manifest中添加设备过滤,确保系统能识别你的设备:
    <uses-feature android:name="android.hardware.usb.host" android:required="true" />
    <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" />
    
    然后在res/xml/usb_device_filter.xml中添加设备的VID和PID:
    <?xml version="1.0" encoding="utf-8"?>
    <resources>
        <usb-device vendor-id="你的设备VID" product-id="你的设备PID" />
    </resources>
    

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 22:08:17