小米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
相关产品推荐
相关产品推荐

