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

Jetpack Compose调用相机拍照后无法显示图片问题求助

排查Jetpack Compose相机拍照后图片不显示且回调未执行的问题

核心问题分析

你的代码逻辑框架没问题,但存在几个关键细节错误或缺失,导致拍照后回调未触发或图片无法显示,以下是逐一排查和修复方案:

1. 临时图片URI生成逻辑缺失或无效

createTempImageUri是核心函数,如果生成的URI不可写或无效,相机应用无法保存图片,会直接导致onResult回调返回false,甚至不触发回调。

正确实现示例:

private fun createTempImageUri(context: Context): Uri {
    val fileName = "temp_photo_${System.currentTimeMillis()}.jpg"
    val contentValues = ContentValues().apply {
        put(MediaStore.Images.Media.DISPLAY_NAME, fileName)
        put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg")
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            // Android 10+ 用分区存储指定保存路径
            put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_PICTURES + "/MyAppPhotos")
        }
    }

    return context.contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)
        ?: throw IllegalStateException("Failed to create temporary image URI.")
}

2. 配置变化导致临时URI丢失

tempPhotoUri用remember存储,屏幕旋转等配置变化时Composable重组,tempPhotoUri会被重置为Uri.EMPTY。即使拍照成功,回调里currentPhotoUri = tempPhotoUri也会赋值空URI,导致图片无法显示。

修复:改用rememberSaveable保留状态:

var tempPhotoUri by rememberSaveable { mutableStateOf(Uri.EMPTY) }

3. 低版本权限不全(Android 10及以下)

Android 10(API 29)以下,写入图片到媒体库需要WRITE_EXTERNAL_STORAGE权限,需同时请求相机和存储权限:

修改权限请求逻辑:

val requiredPermissions = buildList {
    add(Manifest.permission.CAMERA)
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
        add(Manifest.permission.WRITE_EXTERNAL_STORAGE)
    }
}

val permissionsState = rememberMultiplePermissionsState(permissions = requiredPermissions)

// 按钮点击触发权限组请求
Button(onClick = { permissionsState.launchMultiplePermissionRequest() }) {
    Text(text = "Take a photo with Camera")
}

// 权限授予后启动相机
LaunchedEffect(permissionsState.allPermissionsGranted) {
    if (permissionsState.allPermissionsGranted) {
        tempPhotoUri = createTempImageUri(context)
        cameraLauncher.launch(tempPhotoUri)
    } else {
        Timber.e("Camera or storage permission denied")
    }
}

4. 回调结果无日志,无法定位问题

当前代码没有在onResult中打印日志,无法确认回调是否执行及success值。添加日志快速定位:

val cameraLauncher = rememberLauncherForActivityResult(
    contract = ActivityResultContracts.TakePicture(),
    onResult = { success ->
        Timber.d("Camera result success: $success")
        if (success) {
            currentPhotoUri = tempPhotoUri
            Timber.d("Updated currentPhotoUri to: ${currentPhotoUri.toString()}")
        }
    }
)

5. AsyncImage加载失败无提示

如果URI有效但Coil加载失败,界面无任何反馈。添加错误占位符辅助排查:

AsyncImage(
    modifier = Modifier.size(240.dp),
    model = currentPhotoUri,
    contentDescription = "Captured photo",
    error = painterResource(R.drawable.ic_image_load_error), // 替换为你的错误图标
    placeholder = painterResource(R.drawable.ic_image_loading), // 替换为你的加载图标
    contentScale = ContentScale.Crop
)

完整修复后代码示例

@OptIn(ExperimentalPermissionsApi::class)
@Composable
fun ImagePicker() {
    val context = LocalContext.current
    var currentPhotoUri by rememberSaveable { mutableStateOf(Uri.EMPTY) }
    var tempPhotoUri by rememberSaveable { mutableStateOf(Uri.EMPTY) }

    val cameraLauncher = rememberLauncherForActivityResult(
        contract = ActivityResultContracts.TakePicture(),
        onResult = { success ->
            Timber.d("Camera result success: $success")
            if (success) {
                currentPhotoUri = tempPhotoUri
                Timber.d("Current photo URI updated to: ${currentPhotoUri.toString()}")
            }
        }
    )

    val requiredPermissions = buildList {
        add(Manifest.permission.CAMERA)
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
            add(Manifest.permission.WRITE_EXTERNAL_STORAGE)
        }
    }

    val permissionsState = rememberMultiplePermissionsState(permissions = requiredPermissions)

    Column(
        modifier = Modifier.fillMaxSize(),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Timber.d("Current URI: ${currentPhotoUri.toString()}")
        AnimatedVisibility(visible = currentPhotoUri != Uri.EMPTY) {
            AsyncImage(
                modifier = Modifier.size(240.dp),
                model = currentPhotoUri,
                contentDescription = "Captured photo",
                error = painterResource(R.drawable.ic_image_load_error),
                placeholder = painterResource(R.drawable.ic_image_loading),
                contentScale = ContentScale.Crop
            )
        }

        Button(
            onClick = { permissionsState.launchMultiplePermissionRequest() },
            enabled = !permissionsState.allPermissionsGranted
        ) {
            Text(text = "Take a photo with Camera")
        }
    }

    LaunchedEffect(permissionsState.allPermissionsGranted) {
        if (permissionsState.allPermissionsGranted && tempPhotoUri == Uri.EMPTY) {
            tempPhotoUri = createTempImageUri(context)
            cameraLauncher.launch(tempPhotoUri)
        }
    }
}

private fun createTempImageUri(context: Context): Uri {
    val fileName = "temp_photo_${System.currentTimeMillis()}.jpg"
    val contentValues = ContentValues().apply {
        put(MediaStore.Images.Media.DISPLAY_NAME, fileName)
        put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg")
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_PICTURES + "/MyAppPhotos")
        }
    }

    return context.contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)
        ?: throw IllegalStateException("Failed to create temporary image URI.")
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 23:54:54