Android中通过保存的TreeUri在Service获取目录文件列表报错
问题:保存TreeUri后在Service中访问目录报错
requires that you obtain access using ACTION_OPEN_DOCUMENT 我在MainActivity.kt中通过以下代码,点击按钮获取目录的TreeUri:
fun openDirectory(pickerInitialUri: Char) { val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply { putExtra(DocumentsContract.EXTRA_INITIAL_URI, pickerInitialUri) } startActivityForResult(intent,1001) }
我认为应用已获得该目录的完整访问权限,计划将此Uri保存到设置中后续使用。但在Service中尝试通过保存的TreeUri获取目录文件列表时(代码如下),运行时报错:requires that you obtain access using ACTION_OPEN_DOCUMENT。
Service中的代码:
fun openDirectory() { val treeUri = Uri.parse("content://com.android.externalstorage.documents/tree/primary/blablabla") var mContext: Context = getApplicationContext() val pickedDir = DocumentFile.fromTreeUri(applicationContext, treeUri) val file = File(treeUri.getPath()) val files = file.listFiles() if (files != null) { Log.d("FilesFromService", "Size: " + files.size) for (i in files.indices) { Log.d("Files", "FileName:" + files[i].name) } } }
解决方案
核心错误点
- 选目录用错Intent:
ACTION_OPEN_DOCUMENT是用于选择单个文件的,选目录必须用ACTION_OPEN_DOCUMENT_TREE,否则获取的权限范围和Uri类型不匹配。 - 未持久化权限:通过系统文件选择器获取的Uri权限默认是临时的,App重启或后台Service使用时会失效,必须主动持久化权限。
- 错误用File处理Content Uri:TreeUri是基于ContentProvider的
content://协议Uri,不是本地文件路径,直接转成File对象完全无效。
第一步:修正MainActivity中获取TreeUri的代码
1. 替换为正确的目录选择Intent
fun openDirectory(pickerInitialUri: Uri?) { val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).apply { pickerInitialUri?.let { putExtra(DocumentsContract.EXTRA_INITIAL_URI, it) } } startActivityForResult(intent, 1001) }
2. 获取结果时持久化权限并保存Uri
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) if (requestCode == 1001 && resultCode == RESULT_OK) { data?.data?.let { treeUri -> // 保存Uri到SharedPreferences getSharedPreferences("AppSettings", Context.MODE_PRIVATE) .edit() .putString("saved_tree_uri", treeUri.toString()) .apply() // 持久化读写权限,确保后续重启或Service中能访问 val takeFlags = Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION contentResolver.takePersistableUriPermission(treeUri, takeFlags) } } }
第二步:修正Service中访问目录的代码
必须用DocumentFile或ContentResolver操作TreeUri,不能用File类:
fun openDirectory() { // 从SharedPreferences读取保存的Uri val savedUriStr = getSharedPreferences("AppSettings", Context.MODE_PRIVATE) .getString("saved_tree_uri", null) if (savedUriStr == null) { Log.d("FilesFromService", "未保存有效的目录Uri") return } val treeUri = Uri.parse(savedUriStr) // 方式1:用DocumentFile遍历目录 val pickedDir = DocumentFile.fromTreeUri(applicationContext, treeUri) pickedDir?.listFiles()?.forEach { docFile -> Log.d("Files", "FileName: ${docFile.name}") } // 方式2:用ContentResolver查询(批量操作效率更高) val childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(treeUri, DocumentsContract.getTreeDocumentId(treeUri)) contentResolver.query(childrenUri, null, null, null, null)?.use { cursor -> val nameColumn = cursor.getColumnIndex(DocumentsContract.Document.COLUMN_DISPLAY_NAME) while (cursor.moveToNext()) { val fileName = cursor.getString(nameColumn) Log.d("Files", "FileName: $fileName") } } }
额外注意
- 持久化权限后,用户可以在系统设置中随时撤销应用对该目录的访问权限,代码中需要处理权限失效的情况(比如
DocumentFile.fromTreeUri返回null,或者查询Cursor为空)。 - 不要硬编码TreeUri,必须通过SharedPreferences等方式保存用户选择后的Uri,硬编码的Uri会因为用户存储结构变化或权限撤销而失效。
内容的提问来源于stack exchange,提问作者herz88888888
相关产品推荐
相关产品推荐

