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

小米Android API29设备调用相机/文件Intent时应用被杀死问题

解决方案:API29小米设备后台进程被回收导致相机/文件选择结果丢失问题

核心原因

小米API29设备的内存回收机制较为激进,应用进入后台(相机/文件选择界面)时极易被杀死。当前代码中,临时文件Uri、cameraLauncher关联的状态仅存在于Composable内存中,进程重建后这些状态完全丢失,导致无法接收拍照/选文件的结果,相机场景下甚至连临时文件路径都找不到。

具体修复方案

1. 持久化临时文件关键信息

使用rememberSaveable保存临时文件路径,确保进程重建后能恢复对应的Uri,避免状态丢失。

2. 延迟初始化临时文件

将临时文件的创建逻辑延迟到权限通过后执行,避免进程重建时重复创建新文件覆盖旧文件。

3. 优化结果回调逻辑

确保ActivityResultLauncher在进程重建后能正确绑定回调,配合持久化状态处理返回结果。

修改后的代码示例

@Composable
fun CameraPickerButton(
    modifier: Modifier = Modifier,
    buttonLabel: String,
    buttonColors: ButtonColors = ButtonDefaults.buttonColors(),
    photoFileName: String,
    onImagePicked: (String) -> Unit,
    onCameraPermissionDenied: () -> Unit
) {
    val context = LocalContext.current
    // 用rememberSaveable持久化临时文件路径,进程重建后可恢复
    var tmpFilePath by rememberSaveable { mutableStateOf<String?>(null) }
    var currentPhotoUri by rememberSaveable { mutableStateOf(Uri.EMPTY) }

    // 根据保存的路径恢复Uri
    val uri: Uri? = tmpFilePath?.let { path ->
        FileProvider.getUriForFile(
            context,
            BuildConfig.APPLICATION_ID + ".provider",
            File(path)
        )
    }

    val cameraLauncher = rememberLauncherForActivityResult(
        contract = ActivityResultContracts.TakePicture(),
        onResult = { success ->
            if (success) {
                uri?.let { currentPhotoUri = it }
            }
        }
    )

    val permissionLauncher = rememberLauncherForActivityResult(
        ActivityResultContracts.RequestPermission()
    ) { isGranted ->
        if (isGranted) {
            // 权限通过后创建临时文件并保存路径
            val tmpFile = createTmpExtCachePhoto(context, photoFileName)
            tmpFilePath = tmpFile.absolutePath
            val launchUri = FileProvider.getUriForFile(
                context,
                BuildConfig.APPLICATION_ID + ".provider",
                tmpFile
            )
            cameraLauncher.launch(launchUri)
        } else {
            onCameraPermissionDenied.invoke()
        }
    }

    // 用LaunchedEffect处理结果回调,避免重组重复触发
    LaunchedEffect(currentPhotoUri) {
        if (currentPhotoUri.toString().isNotEmpty()) {
            onImagePicked.invoke(currentPhotoUri.toString())
        }
    }

    Button(
        modifier = modifier,
        colors = buttonColors,
        onClick = {
            when (PackageManager.PERMISSION_GRANTED) {
                ContextCompat.checkSelfPermission(context, Manifest.permission.CAMERA) -> {
                    val launchUri = uri ?: run {
                        val tmpFile = createTmpExtCachePhoto(context, photoFileName)
                        tmpFilePath = tmpFile.absolutePath
                        FileProvider.getUriForFile(
                            context,
                            BuildConfig.APPLICATION_ID + ".provider",
                            tmpFile
                        )
                    }
                    cameraLauncher.launch(launchUri)
                }
                else -> permissionLauncher.launch(Manifest.permission.CAMERA)
            }
        }
    ) {
        Text(text = buttonLabel)
    }
}

// 临时文件创建工具方法
fun createTmpExtCachePhoto(context: Context, photoFileName: String): File {
    val cacheDir = context.externalCacheDir ?: context.cacheDir
    return File(cacheDir, "${photoFileName}_${System.currentTimeMillis()}.jpg").apply {
        createNewFile()
    }
}

额外优化建议

  • 临时前台服务保活:启动相机/文件选择前,启动一个带空通知的临时前台服务(API29允许),提高进程优先级避免被快速回收,操作完成后立即停止服务。
  • 文件选择场景适配:对于GetContent(),在结果回调中立即将选中的Uri复制到应用私有目录,保存复制后的文件路径到rememberSaveable,避免原始Uri失效。
  • 强制回收测试:开启开发者选项的「不保留活动」,强制触发进程回收,验证修复后的逻辑是否正常工作。

替代方案

如果以上方案仍无法适配小米设备的极端情况,可以考虑:

  • 使用Jetpack MediaStore直接处理拍照逻辑,避免依赖临时文件Uri的传递;
  • 封装独立的CameraActivity或FilePickerActivity,在独立组件中处理权限和结果,通过Intent传递数据,降低Composable状态丢失风险。

内容的提问来源于stack exchange,提问作者x-devel

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 11:36:00