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

Jetpack Compose中FileProvider相机Uri仅首次可显示图片

问题根因

故障由两个问题共同触发,核心是Compose状态赋值时机错误导致的空文件提前加载,辅以FileProvider路径配置不规范:

  1. 核心触发逻辑:首次拍照完成后hasImage状态始终为true,后续点击拍照按钮时,你在启动相机前就把刚创建的0字节空文件Uri赋值给了imageUri状态,此时图片渲染的判断条件hasImage && imageUri != null立刻成立,AsyncImage(Coil)会尝试解码这个空文件,抛出skia解码失败错误,并将该Uri对应的失败结果存入内存缓存;等相机拍照完成、文件写入磁盘后,Coil直接命中之前的失败缓存,不会重新读取磁盘上的完整图片,因此无法渲染。
    这个逻辑完全匹配你的故障现象:
    • 首次启动时hasImage初始为false,提前赋值Uri不会触发图片加载,等拍照返回后文件已写完,加载正常
    • 后续拍照时hasImage已经为true,提前赋值空Uri立刻触发空文件加载失败,缓存失败结果后即使文件写完也不会重新加载
    • 重启应用后状态重置,回到首次可加载、后续失败的循环
  2. 配置问题:file_paths.xml存在大量错误冗余配置:external-files-path标签本身自动映射应用外部私有目录的files根路径,你配置的全路径、绝对路径、根路径全暴露规则,会导致FileProvider路径匹配逻辑不稳定。
修复步骤

1. 修正FileProvider路径配置

替换file_paths.xml内容,删掉错误冗余配置,只保留合法的Pictures目录映射:

<?xml version="1.0" encoding="utf-8"?>
<paths>
    <external-files-path
        name="my_images"
        path="Pictures" />
</paths>

注意:external-files-path的path属性仅支持相对路径,根目录自动对应/storage/emulated/0/Android/data/<你的应用包名>/files/,不需要写绝对路径或重复拼接全路径。

2. 调整状态赋值时机,避免提前加载空文件

不要把拍照用的目标Uri直接赋值给渲染用的imageUri状态,新增临时变量存储待拍照的Uri,仅在拍照成功、文件写入完成后,再更新渲染状态:

@Composable
fun MainScreen() {
    var hasImage by remember { mutableStateOf(false) }
    var imageUri by remember { mutableStateOf<Uri?>(null) }
    // 新增临时变量,存储本次拍照待写入的Uri,不直接触发UI渲染
    var pendingImageUri by remember { mutableStateOf<Uri?>(null) }

    val context = LocalContext.current

    var grantCameraState by remember {
        mutableStateOf(
            ContextCompat.checkSelfPermission(
                context,
                Manifest.permission.CAMERA
            ) == PackageManager.PERMISSION_GRANTED
        )
    }

    val cameraPermissionlauncher: ManagedActivityResultLauncher<String, Boolean> =
        rememberLauncherForActivityResult(contract = ActivityResultContracts.RequestPermission()) {
            grantCameraState = it
        }

    val cameraLauncher = rememberLauncherForActivityResult(ActivityResultContracts.TakePicture()) { success ->
        Log.i("UriContent@Snapshot", pendingImageUri.toString())
        if (success && pendingImageUri != null) {
            // 仅拍照成功后,才更新渲染用的Uri状态,此时文件已写入完成
            imageUri = pendingImageUri
            hasImage = true
        }
        // 清空临时Uri
        pendingImageUri = null
    }

    Column {
        Button(
            modifier = Modifier.align(alignment = Alignment.CenterHorizontally),
            onClick = {
            if (grantCameraState) {
                val uri = getCamImageUri(context)
                pendingImageUri = uri // 仅赋值给临时变量,不触发图片组件重组
                cameraLauncher.launch(uri)
            } else {
                cameraPermissionlauncher.launch(Manifest.permission.CAMERA)
            }
        }) {
            Text(text = "Take photo")
        }

        Spacer(Modifier.width(10.dp))

        if(hasImage && imageUri != null){
            Log.i("UriContent@Render", imageUri.toString())

            AsyncImage(
                model = imageUri,
                contentDescription = null,
                modifier = Modifier.fillMaxWidth()
            )
        }
    }
}

3. (可选兜底)禁用Coil对该图片的缓存

如果需要彻底避免缓存导致的异常,可以给AsyncImage配置禁用内存、磁盘缓存,确保每次状态更新都重新读取文件:

AsyncImage(
    model = ImageRequest.Builder(LocalContext.current)
        .data(imageUri)
        .memoryCachePolicy(CachePolicy.DISABLED)
        .diskCachePolicy(CachePolicy.DISABLED)
        .build(),
    contentDescription = null,
    modifier = Modifier.fillMaxWidth()
)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 03:57:20