Jetpack Compose中FileProvider相机Uri仅首次可显示图片
问题根因
故障由两个问题共同触发,核心是Compose状态赋值时机错误导致的空文件提前加载,辅以FileProvider路径配置不规范:
- 核心触发逻辑:首次拍照完成后
hasImage状态始终为true,后续点击拍照按钮时,你在启动相机前就把刚创建的0字节空文件Uri赋值给了imageUri状态,此时图片渲染的判断条件hasImage && imageUri != null立刻成立,AsyncImage(Coil)会尝试解码这个空文件,抛出skia解码失败错误,并将该Uri对应的失败结果存入内存缓存;等相机拍照完成、文件写入磁盘后,Coil直接命中之前的失败缓存,不会重新读取磁盘上的完整图片,因此无法渲染。
这个逻辑完全匹配你的故障现象:- 首次启动时
hasImage初始为false,提前赋值Uri不会触发图片加载,等拍照返回后文件已写完,加载正常 - 后续拍照时
hasImage已经为true,提前赋值空Uri立刻触发空文件加载失败,缓存失败结果后即使文件写完也不会重新加载 - 重启应用后状态重置,回到首次可加载、后续失败的循环
- 首次启动时
- 配置问题:
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
相关产品推荐
相关产品推荐

