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
相关产品推荐
相关产品推荐

