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

