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

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)
        }
    }
}

解决方案

核心错误点

  1. 选目录用错Intent:ACTION_OPEN_DOCUMENT是用于选择单个文件的,选目录必须用ACTION_OPEN_DOCUMENT_TREE,否则获取的权限范围和Uri类型不匹配。
  2. 未持久化权限:通过系统文件选择器获取的Uri权限默认是临时的,App重启或后台Service使用时会失效,必须主动持久化权限。
  3. 错误用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 14:33:18