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

安卓Root设备音视频文件无法检测,RecyclerView无法填充的解决办法

问题:Root设备无法检测音视频文件并填充RecyclerView

我开发了一款简单应用,用于将音视频文件所在文件夹路径填充至RecyclerView。该应用在普通安卓设备上运行正常,但在Root设备上,既无法检测到音视频文件,RecyclerView也无法填充。启动应用时还会弹出提示:shell granted superuser access。

核心代码

private var videosPaths: ArrayList<File> = ArrayList()
private lateinit var pathsViewModel: PathsViewModel
private val videoFormat = arrayOf(".mp4", ".MP4", ".mkv", ".MKV")

// 此处将音视频文件路径存入创建的数据库
// Room数据库相关文件未在此展示

val pathsDao = PathsDatabase.getPathsInstance(applicationContext).pathsDao()
val pathsRepository = PathsRepository(pathsDao)
val pathsMainDatabase = PathsMainDatabase()
pathsViewModel = ViewModelProvider(this, PathsViewModelFactory(pathsRepository))[PathsViewModel::class.java]

// 我创建了名为"video_paths"的数据库
if(!applicationContext.getDatabasePath("videos_paths").exists()) {
    videosPaths = getVideoFolders()

// 在Root设备上,videoPaths返回0个视频文件夹,且应用启动时会弹出提示:"shell granted superuser access"

    videosPaths.forEach{ file ->
        pathsViewModel.insertPath(Paths(0, file.absolutePath))
    }
}

// 这是我编写的无需使用Cursor库即可提取设备中音视频文件的自定义代码

// 获取视频文件夹
fun getVideoFolders() : ArrayList<File> {
    searchVideoFiles(Environment.getExternalStorageDirectory())
    return videoFolders
}

// 用于扫描设备所有文件夹中音视频文件的递归函数
private fun searchVideoFiles(file: File) {
    val dir = file.listFiles() ?: return
    for (f in dir) {
        if (f.isDirectory) {
            searchVideoFiles(f)
            if (isVideoFolder(f)) videoFolders.add(f)
        }
    }
}

// 若检测到至少一个视频文件则返回true
private fun isVideoFolder(file: File?): Boolean {
    assert(file != null)
    assert(file!!.isDirectory)
    val dir = file.listFiles() ?: return false
    for (f in dir) {
        val path = f.absolutePath
        for(v in videoFormat) {
            if(path.endsWith(v)) return true
        }
    }
    return false
} 

解决方案

  • 修复Root设备存储路径访问
    Root设备的外部存储路径可能和普通设备有差异,Environment.getExternalStorageDirectory()可能无法指向正确的多媒体存储位置。可以尝试直接扫描多个常见路径:

    fun getVideoFolders() : ArrayList<File> {
        // 添加多个可能的存储路径
        val storagePaths = arrayOf(
            Environment.getExternalStorageDirectory(),
            File("/sdcard/"),
            File("/storage/emulated/0/"),
            File("/storage/sdcard0/")
        )
        storagePaths.forEach { searchVideoFiles(it) }
        return videoFolders
    }
    
  • 调整Superuser权限影响
    应用以shell权限运行时,可能被限制访问用户私有目录。建议确保应用以普通用户身份访问存储,避免依赖自动获取的Root权限。同时检查Root设备的权限管理工具(如Magisk)是否限制了应用的存储访问权限。

  • 完善文件扫描逻辑
    当前扫描逻辑只检查目录下是否有视频文件,遗漏了直接存放在根目录的视频文件,且未处理文件直接是视频的情况。修改扫描函数:

    private fun searchVideoFiles(file: File) {
        val dir = file.listFiles() ?: return
        for (f in dir) {
            if (f.isDirectory) {
                searchVideoFiles(f)
                if (isVideoFolder(f) && !videoFolders.contains(f)) {
                    videoFolders.add(f)
                }
            } else {
                // 直接检查文件是否为视频,若是则将其父目录加入列表
                val path = f.absolutePath
                for(v in videoFormat) {
                    if(path.endsWith(v)) {
                        val parentFolder = f.parentFile ?: continue
                        if(!videoFolders.contains(parentFolder)) {
                            videoFolders.add(parentFolder)
                        }
                        break
                    }
                }
            }
        }
    }
    
  • 优化数据库初始化逻辑
    原逻辑仅在数据库不存在时扫描,若首次扫描失败则后续无法触发重新扫描。添加数据库空数据检查:

    val dbFile = applicationContext.getDatabasePath("videos_paths")
    if(!dbFile.exists()) {
        refreshVideoFolders()
    } else {
        // 观察数据库数据,为空则重新扫描
        pathsViewModel.allPaths.observe(this) { paths ->
            if(paths.isEmpty()) {
                refreshVideoFolders()
            }
        }
    }
    
    

// 封装扫描插入逻辑
private fun refreshVideoFolders() {
videosPaths = getVideoFolders()
videosPaths.forEach{ file ->
pathsViewModel.insertPath(Paths(0, file.absolutePath))
}
}

- **改用MediaStore API(推荐)**
自定义文件扫描在Root设备上容易遇到权限和路径兼容问题,官方推荐使用`MediaStore`获取多媒体文件,兼容性更强:
```kotlin
private fun getVideoFoldersFromMediaStore(): ArrayList<File> {
    val videoFolders = ArrayList<File>()
    val projection = arrayOf(MediaStore.Video.Media.DATA)
    contentResolver.query(
        MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
        projection,
        null,
        null,
        null
    )?.use { cursor ->
        val dataColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DATA)
        while(cursor.moveToNext()) {
            val filePath = cursor.getString(dataColumn)
            val parentFolder = File(filePath).parentFile ?: continue
            if(!videoFolders.contains(parentFolder)) {
                videoFolders.add(parentFolder)
            }
        }
    }
    return videoFolders
}

注意:Android 10及以上需处理分区存储,可在Manifest中添加android:requestLegacyExternalStorage="true"(仅临时兼容),或适配Scoped Storage;同时需申请READ_EXTERNAL_STORAGE权限(Android 13及以上需申请READ_MEDIA_VIDEO权限)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.10 14:35:17