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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.08 18:01:06