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

Android 10下Jetpack Compose读取外部存储图片失败求助:权限已获但文件不可读

Android 10下Jetpack Compose读取外部存储图片失败排查

问题背景

正在将一款大型天文应用迁移至Jetpack Compose,已完成Room数据库、OpenGL视图与渲染器、多个复杂页面等迁移工作,但卡在读取外部存储图片的基础功能上。

使用环境

  • Android 10(API 29)
  • AndroidManifest已添加<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>权限
  • 依赖库:
    implementation "com.google.accompanist:accompanist-permissions:0.29.1-alpha"
    implementation "io.coil-kt:coil-compose:2.2.2"
    

问题现象

测试Composable函数中:

  • 外部存储目录可访问,目标文件存在且大小正确
  • 权限检查结果为已授予
  • 但file.canRead()返回false,图片无法加载显示

测试代码

@OptIn(ExperimentalMaterialApi::class, ExperimentalPermissionsApi::class)
@Composable
fun SheetContent() {

    val context = LocalContext.current

    val readExternalMemoryState = rememberPermissionState(
        Manifest.permission.READ_EXTERNAL_STORAGE
    )
    if (readExternalMemoryState.status.isGranted) {
        Text("Read External memory permission Granted")
    } else {
        AskPermission(permissionState = readExternalMemoryState)
    }

    val path = Environment.getExternalStorageDirectory().toString() + "/Download/"
    val file = File(path + "solar-system.png")
    val filesize = file.length()
    
    if (file.exists()) {
        Text("File exist with size: $filesize")
        when (ContextCompat.checkSelfPermission(context, Manifest.permission.READ_EXTERNAL_STORAGE
        )) {
            PackageManager.PERMISSION_GRANTED -> {
                //File read logic here
                Text("checkSelfPermission gives status: Granted")
                if (file.canRead()) {
                    Text("File can be read")
                    Image(
                        rememberAsyncImagePainter(file),
                        contentDescription = ""
                    )
                } else {
                    Text("File not readable")
                }
            }
        }
    } else {
        Text("Not available")
    }
}

输出结果

Read External memory permission Granted
File exist with size: 20534
checkSelfPermission gives status: Granted
File not readable

解决方案

方案1:启用旧版存储访问兼容(快速解决)

Android 10引入了分区存储机制,默认限制了直接通过File路径访问外部存储文件的权限。在AndroidManifest.xml的<application>标签中添加以下属性,即可暂时禁用分区存储,恢复旧的文件访问逻辑:

<application
    ...
    android:requestLegacyExternalStorage="true">
    ...
</application>

添加后重新编译运行,file.canRead()应该会返回true,Coil也能正常加载图片。

方案2:适配分区存储(推荐长期方案)

如果希望遵循Android 10及以上的分区存储规范,建议通过MediaStore查询文件Uri,再用Coil加载Uri而非直接操作File:

  1. 编写工具函数获取Download目录下指定文件名的Uri:
fun getImageUriFromDownloads(context: Context, fileName: String): Uri? {
    val projection = arrayOf(MediaStore.Images.Media._ID)
    val selection = "${MediaStore.Images.Media.DISPLAY_NAME} = ?"
    val selectionArgs = arrayOf(fileName)
    val sortOrder = "${MediaStore.Images.Media.DATE_ADDED} DESC"

    context.contentResolver.query(
        MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
        projection,
        selection,
        selectionArgs,
        sortOrder
    )?.use { cursor ->
        if (cursor.moveToFirst()) {
            val idColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media._ID)
            val id = cursor.getLong(idColumn)
            return ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, id)
        }
    }
    return null
}
  1. 在Compose中修改加载逻辑:
@OptIn(ExperimentalMaterialApi::class, ExperimentalPermissionsApi::class)
@Composable
fun SheetContent() {
    val context = LocalContext.current
    val readExternalMemoryState = rememberPermissionState(
        Manifest.permission.READ_EXTERNAL_STORAGE
    )

    if (readExternalMemoryState.status.isGranted) {
        Text("Read External memory permission Granted")
        val imageUri = remember { getImageUriFromDownloads(context, "solar-system.png") }
        imageUri?.let { uri ->
            Text("File found via MediaStore")
            Image(
                painter = rememberAsyncImagePainter(uri),
                contentDescription = "Solar system image"
            )
        } ?: run {
            Text("File not found in MediaStore")
        }
    } else {
        AskPermission(permissionState = readExternalMemoryState)
    }
}

补充说明

  • 分区存储是Android 10及以上的默认行为,requestLegacyExternalStorage在Android 11(API 30)及以上会被忽略,所以长期来看推荐采用方案2适配。
  • Coil支持直接加载Uri对象,无需额外转换,适配分区存储时使用Uri加载是更合规的方式。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 20:05:04