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

Android 14迁移应用访问非应用创建文件遇权限问题求助

Android 14访问非应用创建外部文件的可行方案

完全可以成功访问设备上非应用创建的外部文件,不用直接采用Content Provider封装的方案,先从权限配置和访问方式入手排查:

1. 核心问题原因

你的设备是Android 14,开启了分区存储(Scoped Storage),即使File.exists()返回true,应用也没有直接访问非私有目录文件的权限,这就是EACCES Permission denied的根源。

2. 权限配置调整

针对targetApi="31"(Android 12)+ Android 14设备,在AndroidManifest.xml中添加对应权限:

<!-- 适配Android 12及以下的外部存储读取权限 -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
<!-- Android 13+的媒体文件权限(如果涉及媒体文件) -->
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
<!-- 如需访问非媒体文件(如txt),可申请全存储权限(注意Google Play审核要求) -->
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
    tools:ignore="ScopedStorage" />

注意:所有危险权限都需要动态申请,不能仅依赖Manifest声明。

3. 推荐的文件访问方式

方式一:用MediaStore查询读取(适合已知文件名和路径的场景)

通过MediaStore获取文件的Content Uri,再用ContentResolver读取内容,示例代码:

val projection = arrayOf(MediaStore.Files.FileColumns._ID, MediaStore.Files.FileColumns.DISPLAY_NAME)
val selection = "${MediaStore.Files.FileColumns.DISPLAY_NAME} = ? AND ${MediaStore.Files.FileColumns.RELATIVE_PATH} = ?"
val selectionArgs = arrayOf("DataToAndroid.txt", "Download/")

contentResolver.query(
    MediaStore.Files.getContentUri("external"),
    projection,
    selection,
    selectionArgs,
    null
)?.use { cursor ->
    if (cursor.moveToFirst()) {
        val fileId = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns._ID))
        val fileUri = ContentUris.withAppendedId(MediaStore.Files.getContentUri("external"), fileId)
        
        contentResolver.openInputStream(fileUri)?.use { inputStream ->
            // 读取文件内容,比如转为字符串
            val content = inputStream.bufferedReader().readText()
            // 处理内容
        }
    }
}

方式二:使用存储访问框架(SAF)让用户选择文件(合规性最佳)

无需特殊权限,让用户手动选择目标文件,示例代码:

private const val REQUEST_CODE_OPEN_FILE = 1001

// 触发文件选择
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
    addCategory(Intent.CATEGORY_OPENABLE)
    type = "text/plain" // 指定文件类型为文本
}
startActivityForResult(intent, REQUEST_CODE_OPEN_FILE)

// 在回调中获取文件Uri并读取
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    super.onActivityResult(requestCode, resultCode, data)
    if (requestCode == REQUEST_CODE_OPEN_FILE && resultCode == RESULT_OK) {
        data?.data?.let { uri ->
            contentResolver.openInputStream(uri)?.use { inputStream ->
                val content = inputStream.bufferedReader().readText()
                // 处理内容
            }
        }
    }
}

4. 关于Content Provider的补充

如果你的应用是类似文件管理器的工具类应用,需要批量访问外部文件,才考虑用Content Provider封装或者申请MANAGE_EXTERNAL_STORAGE权限;普通场景下,上述两种方式足够解决问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 03:26:06