Android WebView文件选择器回调丢失问题求助(Activity重建场景)
解决Android WebView相机拍照后Activity重建导致文件选择回调丢失的问题
问题根源
Activity启动相机时,部分机型会因系统配置变化(如屏幕旋转、内存回收)触发Activity销毁重建,此时原Activity中的ValueCallback(文件选择回调)和照片路径会被重置为null,WebView虽能通过URL恢复页面,但无法将拍照结果回传给Web端。
解决方案
1. 使用ViewModel保存跨配置变化的状态
ViewModel的生命周期独立于Activity的配置变化,可安全保存文件选择的回调、照片路径及状态标记:
import androidx.lifecycle.ViewModel class WebFileChooserViewModel : ViewModel() { // 文件选择回调 var uploadMessage: ValueCallback<Array<Uri>>? = null // 拍照生成的文件路径 var photoPath: String? = null // 标记是否正在进行文件选择操作 var isFileChoosing: Boolean = false }
在Activity中获取ViewModel实例:
private val fileChooserViewModel: WebFileChooserViewModel by viewModels()
2. 替换原有成员变量为ViewModel中的值
将代码中所有mUploadMessage替换为fileChooserViewModel.uploadMessage,mCM替换为fileChooserViewModel.photoPath,并在onShowFileChooser中标记文件选择状态:
private inner class sampleChromeClient : WebChromeClient() { override fun onShowFileChooser( mWebView: WebView, filePathCallback: ValueCallback<Array<Uri>>, fileChooserParams: FileChooserParams ): Boolean { // 先结束之前未完成的文件选择,避免回调泄漏 fileChooserViewModel.uploadMessage?.onReceiveValue(null) // 保存新的回调和状态 fileChooserViewModel.uploadMessage = filePathCallback fileChooserViewModel.isFileChoosing = true var takePictureIntent: Intent? = Intent(MediaStore.ACTION_IMAGE_CAPTURE) if (takePictureIntent!!.resolveActivity(this@sampleWebActivity.packageManager) != null) { var photoFile: File? = null try { photoFile = createImageFile() } catch (ex: IOException) { Log.e(TAG, "Image file creation failed", ex) } if (photoFile != null) { // 将照片路径存入ViewModel fileChooserViewModel.photoPath = "file:" + photoFile.absolutePath takePictureIntent.putExtra("PhotoPath", fileChooserViewModel.photoPath) val uri = FileProvider.getUriForFile( this@sampleWebActivity, this@sampleWebActivity.applicationContext.packageName + ".provider", photoFile ) takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, uri) } else { takePictureIntent = null } } // 后续的选择逻辑保持不变,仅将相关Intent存入ViewModel(如果需要权限判断) // ... 原有代码 ... return true } }
3. 在Activity重建后恢复未完成的文件选择
在onCreate方法中,WebView初始化完成后,检查是否有未完成的文件选择操作,若有则直接处理结果并回调:
@SuppressLint("SetJavaScriptEnabled") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivitySampleWebActivity.inflate(layoutInflater) setContentView(binding.root) binding.sampleWebView.settings.javaScriptEnabled = true binding.sampleWebView.settings.domStorageEnabled = true binding.sampleWebView.webChromeClient = sampleChromeClient() binding.sampleWebView.webViewClient = sampleWebViewClient() // 检查是否存在未完成的文件选择 if (fileChooserViewModel.isFileChoosing && fileChooserViewModel.photoPath != null) { val results = arrayOf(Uri.parse(fileChooserViewModel.photoPath)) fileChooserViewModel.uploadMessage?.onReceiveValue(results) // 重置状态 fileChooserViewModel.uploadMessage = null fileChooserViewModel.isFileChoosing = false fileChooserViewModel.photoPath = null } // 仅在首次创建时加载URL,避免重建时重复加载 if (savedInstanceState == null) { loadUrl() } }
4. 修正onActivityResult逻辑
将onActivityResult中的变量替换为ViewModel中的值,确保回调和路径在重建后仍可访问:
override fun onActivityResult( requestCode: Int, resultCode: Int, intent: Intent? ) { super.onActivityResult(requestCode, resultCode, intent) var results: Array<Uri>? = null if (resultCode == Activity.RESULT_OK) { if (requestCode == FILE_CHOOSER_REQUEST_CODE) { fileChooserViewModel.uploadMessage ?: return if (intent == null) { fileChooserViewModel.photoPath?.let { results = arrayOf(Uri.parse(it)) } } else { val dataString = intent.dataString results = if (dataString != null) { arrayOf(Uri.parse(dataString)) } else { fileChooserViewModel.photoPath?.let { arrayOf(Uri.parse(it)) } } } } } fileChooserViewModel.uploadMessage?.onReceiveValue(results) // 重置所有状态 fileChooserViewModel.uploadMessage = null fileChooserViewModel.isFileChoosing = false fileChooserViewModel.photoPath = null }
为什么之前的方案无效
- 单例存储回调:回调持有Activity引用易引发内存泄漏,且Activity重建后WebView为新实例,原回调无法关联到新WebView。
- 复用ChromeClient:Activity重建后WebView重新初始化,虽ChromeClient实例复用,但
onShowFileChooser不会自动触发,无法重新绑定回调。
内容的提问来源于stack exchange,提问作者dhiraj uchil
相关产品推荐
相关产品推荐

