简介:本文针对Android 15系统升级后CameraX调用系统相机崩溃的典型问题——`Mutation of _data is not allowed`,进行系统性原因剖析,并提供基于FileProvider与Uri封装的最佳实践方案,帮助开发者彻底规避高版本系统对Intent数据可变性的强约束,同时给出完整的前置适配清单与代码示例。
随着Android 15(API 35)的逐步推送,不少基于CameraX构建相机能力的应用开始暴露出一些隐蔽的兼容性问题。其中一类高频崩溃发生在调用系统相机拍照并返回的瞬间——应用在onActivityResult回调触发前直接抛出RuntimeException,进程被终止。根据崩溃堆栈,异常根因指向ActivityThread.deliverResults方法,具体信息为:
java.lang.IllegalArgumentException: Mutation of _data is not allowed.
该错误并非CameraX自身逻辑缺陷,而是Android 15对传递给系统应用的Intent数据不可变约束进一步增强所致。为了理解并根治该问题,我们需要从系统组件间通信的数据载体机制说起。
_data 字段与Intent可变性在Android系统中,通过Intent启动系统相机时,通常使用MediaStore.EXTRA_OUTPUT指定输出Uri,例如:
val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE).apply {putExtra(MediaStore.EXTRA_OUTPUT, imageUri)}
当目标Activity(相机应用)完成拍摄后,系统会通过Intent携带结果返回到当前Activity。此时,返回的Intent内部包含一个ClipData或data属性,指向原始的输出Uri。在高版本系统中,系统对跨进程传递的Intent对象实施了严格的可变检查——一旦某个Intent被系统组件处理后,其内部的Bundle和_data字段即被标记为不可变(immutable)。若原始调用方后续尝试修改该Intent的字段(例如在onActivityResult中对data进行二次setData操作),便会在deliverResults阶段触发IllegalArgumentException。
在Android 15之前,该约束在某些国产ROM或特定系统版本上执行并不严格,大量开发者通过统一封装Intent返回结果的方式未触发该异常。但Android 15将其收紧为强制约束,导致存量代码集中爆发。
崩溃代码通常存在于以下模式中:
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {super.onActivityResult(requestCode, resultCode, data)if (requestCode == REQUEST_IMAGE_CAPTURE) {// 对data进行setData操作或修改data字段data?.setData(newUri) // 此处抛出异常}}
更隐蔽的情形是,某些封装库内部会对data进行二次包装,例如在ActivityResultContracts.TakePicture的适配层实现中,如果直接依赖系统返回的Intent作为Uri来源,并在回调链中继续传递并修改该Intent,便可能触发此问题。
遇到该崩溃时,不要急于修改CameraX初始化参数。应优先检查以下两点:
onActivityResult或ActivityResultCallback中,是否对传入的data(Intent)执行了setData、putExtra等修改操作。Contract在返回结果后重复调用Intent.setData补充额外参数。若存在上述行为,即使对CameraX做了全面升级,崩溃依旧会在某些分支路径上复现。
推荐做法是:完全放弃从返回的Intent中提取Uri,而是在启动相机前就自行生成并持有该Uri。该Uri通过FileProvider转换为content://类型后,仅用于EXTRA_OUTPUT。拍摄完成后,只校验文件是否生成,不再读取data内容。
val fileUri = createImageUri() // FileProvider.getUriForFile(...)val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE).apply {putExtra(MediaStore.EXTRA_OUTPUT, fileUri)addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)}cameraLauncher.launch(intent)// 回调中override fun onActivityResult(resultCode: Int, data: Intent?) {if (resultCode == Activity.RESULT_OK) {// 直接使用预先持有的fileUri,不读取dataprocessImage(fileUri)}}
官方推荐在CameraX与系统相机交互时,使用ActivityResultContracts.TakePicture()合约。该合约内部已正确封装了EXTRA_OUTPUT,且合约回调返回的Boolean仅表示成功与否,不会返回可变Intent。此时,确保传递的Uri为全局持有,不要在回调中对任何Intent进行修改。
private val takePicture = registerForActivityResult(ActivityResultContracts.TakePicture()) { success ->if (success) {// imageUri 为预先创建且只需读取}}// 触发takePicture.launch(imageUri)
部分开发者会在ImageCapture的OutputFileResults回调中重新构造Uri并写入其他数据。虽然该场景不涉及Intent,但要注意不要将拍摄结果Uri与data混用。
除了上述崩溃外,Android 15还进一步强化了READ_MEDIA_VISUAL_USER_SELECTED等分区存储权限,以及FOREGROUND_SERVICE_CAMERA的类型限制。若应用在拍摄前未正确申请CAMERA权限且未对输出目录进行正确隔离,仍可能引发安全异常。适配时建议将输出目录固定在应用专属外部存储或getExternalFilesDir下,避免直接依赖公共媒体库的写入路径。
FileProvider配置正确,且在manifest中声明了android:grantUriPermissions="true"。data是否为null。activity与fragment依赖库,确保系统组件契约一致。ActivityResultRegistry替换废弃的startActivityForResult体系,从源头降低对Intent可变性的依赖。Android 15对Intent可变性的强约束一方面提升了系统安全性,另一方面也迫使开发者必须从架构层审视既有的相机调用链。最稳妥的适配手段不是规避异常,而是彻底改变对系统返回数据的依赖——将Uri源头前置,以文件系统层面校验替代Intent内容校验。通过本文的方案,开发者不仅能修复崩溃,还能让相机模块的代码在高版本系统上更健壮、更可维护。建议在全面适配后,针对不同Android版本设备进行回归测试,尤其关注返回码及文件存在性的双重校验逻辑。