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

Android智能电视应用读取USB串口及获取文件技术问询

Android智能电视USB存储访问解决方案

问题核心

你当前的实现错误地使用了UsbManager的低层级USB设备通信API(如bulkTransfer、claimInterface)来访问USB存储设备。实际上,Android智能电视会自动将USB存储设备挂载为文件系统,无需通过UsbDeviceConnection这类针对自定义USB设备的接口,直接用标准文件API或存储访问框架(SAF)即可访问。

另外你的代码存在几个关键问题:

  • 权限请求是异步的,你在调用requestPermission后立即执行文件访问逻辑,此时权限还未真正授予
  • 硬编码USB路径/mnt/usb_storage/不具备通用性,不同电视厂商的挂载路径可能不同
  • 混淆了USB存储设备(系统自动挂载)和自定义USB设备(需要低层级通信)的访问方式

正确实现步骤

1. 申请必要权限

根据Android版本添加对应的权限:

  • Android 9及以下:在AndroidManifest.xml中添加
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    
  • Android 10-12:添加READ_EXTERNAL_STORAGE,或使用MANAGE_EXTERNAL_STORAGE(针对电视这类非移动设备更友好)
  • Android 13+:添加READ_MEDIA_IMAGES等媒体权限,或MANAGE_EXTERNAL_STORAGE

2. 检测USB存储挂载路径

不要硬编码路径,通过以下方式获取:

  • 方式一:扫描系统存储目录
    大部分电视的USB存储挂载在/storage/目录下,命名为usb0、usb1等,可通过遍历/storage/目录筛选出USB设备
  • 方式二:使用存储访问框架(SAF)让用户选择USB存储根目录,兼容性最好

3. 访问USB文件

用标准File API或ContentResolver读取文件即可,无需涉及UsbManager的低层级操作。

代码示例

示例1:自动检测并访问USB存储文件

import android.content.Context
import android.util.Log
import java.io.File
import java.io.FileInputStream
import android.graphics.BitmapFactory

class UsbStorageReader {

    private val TAG = "UsbStorageReader"

    // 获取所有挂载的USB存储目录
    fun getUsbMountPaths(context: Context): List<File> {
        val usbPaths = mutableListOf<File>()
        // 常见的USB挂载目录
        val possiblePaths = listOf(
            "/storage/usb0", "/storage/usb1", "/mnt/usb_storage",
            "/mnt/usbdisk", "/storage/external_storage"
        )
        possiblePaths.forEach { path ->
            val file = File(path)
            if (file.exists() && file.isDirectory && file.canRead()) {
                usbPaths.add(file)
            }
        }
        // 额外扫描/storage目录下的所有子目录,适配不同厂商
        val storageDir = File("/storage/")
        storageDir.listFiles()?.forEach { dir ->
            if (dir.isDirectory && dir.canRead() && dir.name.startsWith("usb")) {
                usbPaths.add(dir)
            }
        }
        return usbPaths
    }

    // 读取USB中的图片文件
    fun readImagesFromUsb(context: Context): List<Any> {
        val imageList = mutableListOf<Any>()
        val usbDirs = getUsbMountPaths(context)
        usbDirs.forEach { usbDir ->
            scanImages(usbDir, imageList)
        }
        return imageList
    }

    private fun scanImages(dir: File, imageList: MutableList<Any>) {
        dir.listFiles()?.forEach { file ->
            if (file.isDirectory) {
                scanImages(file, imageList)
            } else {
                val extension = file.name.lowercase()
                if (extension.endsWith(".jpg") || extension.endsWith(".png") || extension.endsWith(".jpeg")) {
                    try {
                        val inputStream = FileInputStream(file)
                        val bitmap = BitmapFactory.decodeStream(inputStream)
                        inputStream.close()
                        imageList.add(bitmap)
                        Log.d(TAG, "读取图片成功: ${file.absolutePath}")
                    } catch (e: Exception) {
                        Log.e(TAG, "读取图片失败: ${file.absolutePath}", e)
                    }
                }
            }
        }
    }
}

示例2:使用SAF让用户选择USB存储

import android.app.Activity
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.provider.DocumentsContract
import android.widget.Button
import androidx.appcompat.app.AppCompatActivity

class UsbSafActivity : AppCompatActivity() {
    private val REQUEST_CODE_PICK_USB = 1001

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_usb_saf)

        val selectUsbBtn = findViewById<Button>(R.id.select_usb_btn)
        selectUsbBtn.setOnClickListener {
            // 打开SAF选择器,指定选择目录
            val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
            intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, Uri.parse("/storage/"))
            startActivityForResult(intent, REQUEST_CODE_PICK_USB)
        }
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        if (requestCode == REQUEST_CODE_PICK_USB && resultCode == Activity.RESULT_OK) {
            data?.data?.let { uri ->
                // 保存uri权限,以便后续访问
                contentResolver.takePersistableUriPermission(
                    uri,
                    Intent.FLAG_GRANT_READ_URI_PERMISSION
                )
                // 现在可以通过ContentResolver访问该目录下的文件
                scanImagesFromUri(uri)
            }
        }
    }

    private fun scanImagesFromUri(uri: Uri) {
        // 使用ContentResolver遍历目录,可参考DocumentsContract API实现文件遍历
    }
}

关键注意事项

  • 权限异步处理:如果使用UsbManager.requestPermission,必须等待BroadcastReceiver回调确认权限授予后再执行操作,不能同步调用
  • 厂商兼容性:不同电视厂商的USB挂载路径可能不同,优先使用SAF方式,或多路径扫描
  • 避免低层级API:UsbDeviceConnection、bulkTransfer等API仅适用于串口、打印机这类非存储类USB设备,USB存储直接用文件系统访问即可
  • Android版本适配:针对不同Android版本的存储权限规则,做好动态权限申请

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 19:07:19