Android Jetpack Compose中WebView拍照选图后无响应问题求助
问题排查与修复方案
核心问题分析
onShowFileChooser提前终止流程:你在方法开头调用了filePathCallback?.onReceiveValue(null),这会直接告诉WebView"用户取消了选择",后续再调用onReceiveValue已无效。- 状态丢失问题:
takePictureUri和takePictureFile是Composable局部变量,Compose重组时会被重置,导致拍照成功后无法正确传递Uri。grantState仅在首次组合时计算,权限申请成功后不会自动更新,导致后续点击拍照按钮仍会触发权限申请。
fileChooserCallback未持久化:未用rememberSaveable保存,配置变化(如旋转屏幕)会丢失回调引用。
修复后的代码
1. WebView 相关代码修复
// 用rememberSaveable持久化回调,防止配置变化丢失 var fileChooserCallback by rememberSaveable { mutableStateOf<ValueCallback<Array<Uri>>?>(null) } var selectImage by remember { mutableStateOf(false) } ... override fun onShowFileChooser( webView: WebView?, filePathCallback: ValueCallback<Array<Uri>>?, fileChooserParams: FileChooserParams? ): Boolean { // 移除提前调用onReceiveValue(null)的代码 filePathCallback?.let { callback -> this.fileChooserCallback = callback selectImage = true } return true } ... if (selectImage) { ImageSelectionBottomSheet { uri -> fileChooserCallback?.onReceiveValue(uri?.let { arrayOf(it) } ?: emptyArray()) // 无论选择成功还是取消,都重置状态 selectImage = false // 清空回调,避免重复调用 fileChooserCallback = null } }
2. 图片选择底部弹窗修复
@OptIn(ExperimentalMaterial3Api::class) @Composable fun ImageSelectionBottomSheet( onDismiss: (Uri?) -> Unit ) { val permissions = arrayOf(Manifest.permission.CAMERA) val context = LocalContext.current // 用remember保存拍照相关的文件和Uri,避免重组丢失 var takePictureFile by remember { mutableStateOf<File?>(null) } var takePictureUri by remember { mutableStateOf<Uri?>(null) } // 实时检查权限状态 val grantState by remember { mutableStateOf(permissions.all { ContextCompat.checkSelfPermission(context, it) == PackageManager.PERMISSION_GRANTED }) } // 权限申请后更新状态 val permissionLauncher = rememberLauncherForActivityResult( contract = ActivityResultContracts.RequestMultiplePermissions(), onResult = { results -> val allGranted = results.values.all { it } if (allGranted) { takePictureFile = createImageCaptureFile(context) takePictureUri = FileProvider.getUriForFile( context, "${context.packageName}.fileprovider", takePictureFile!! ) cameraLauncher.launch(takePictureUri) } else { // 权限拒绝通知回调 onDismiss.invoke(null) } } ) val singlePhotoPickerLauncher = rememberLauncherForActivityResult( contract = ActivityResultContracts.PickVisualMedia(), onResult = { uri -> Timber.tag("ok").d("ImageSelectionBottomSheet: %s", uri) onDismiss.invoke(uri) } ) val cameraLauncher = rememberLauncherForActivityResult( contract = ActivityResultContracts.TakePicture() ) { success -> Timber.tag("ok").d("ImageSelectionBottomSheet: %s $success", takePictureUri) if (success) { onDismiss.invoke(takePictureUri) } else { // 拍照失败也通知回调 onDismiss.invoke(null) } // 重置拍照状态 takePictureFile = null takePictureUri = null } ModalBottomSheet( content = { Column(modifier = Modifier.padding(16.dp)) { ActionButton( iconDrawableRes = com.liquidity.core.ui.R.drawable.ic_camera, text = stringResource(id = R.string.open_gallery), onClick = { singlePhotoPickerLauncher.launch( PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly) ) }, ) Spacer(modifier = Modifier.height(8.dp)) ActionButton( iconDrawableRes = com.liquidity.core.ui.R.drawable.ic_camera, text = stringResource(id = R.string.take_a_new_photo), onClick = { val currentGrantState = permissions.all { ContextCompat.checkSelfPermission(context, it) == PackageManager.PERMISSION_GRANTED } if (currentGrantState) { takePictureFile = createImageCaptureFile(context) takePictureUri = FileProvider.getUriForFile( context, "${context.packageName}.fileprovider", takePictureFile!! ) cameraLauncher.launch(takePictureUri) } else { permissionLauncher.launch(permissions) } }, ) Spacer(modifier = Modifier.height(30.dp)) } }, shape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp), onDismissRequest = { onDismiss.invoke(null) } ) } private fun createImageCaptureFile(context: Context): File { val timeStamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date()) val fileName = "JPEG_${timeStamp}.jpg" val cacheDir = context.cacheDir return File(cacheDir, fileName) }
关键修复点说明
- 移除提前终止代码:删除
onShowFileChooser中的filePathCallback?.onReceiveValue(null),确保WebView等待选择结果。 - 持久化状态:
- 用
rememberSaveable保存fileChooserCallback,避免配置变化丢失。 - 用
remember保存takePictureFile和takePictureUri,防止Compose重组时重置。
- 用
- 实时检查权限:点击拍照按钮时实时检查权限状态,避免依赖静态的
grantState。 - 完善回调逻辑:无论选择成功、失败还是取消,都调用
onDismiss并重置状态,避免内存泄漏和重复调用。
内容的提问来源于stack exchange,提问作者Stevie
相关产品推荐
相关产品推荐

