WebView返回后再次打开无法调用相机问题求助
我开发了一个加载WebView并调用相机扫描二维码的项目,但有时从页面返回后再次进入,页面会显示灰色播放按钮。模拟器中该问题出现频率较低。正常工作时Logcat输出日志如下:
W/m.migrosmagazam: Long monitor contention with owner VideoCaptureCamera2_CameraThread (6680) at void android.hardware.camera2.impl.CameraDeviceImpl.close()(CameraDeviceImpl.java:1433) waiters=0 in void android.hardware.camera2.impl.CameraDeviceImpl$CameraDeviceCallbacks.onCaptureStarted(android.hardware.camera2.impl.CaptureResultExtras, long) for 237ms
D/FA: Connected to remote service
V/FA: Processing queued up service tasks: 1
D/EGL_emulation: app_time_stats: avg=24.30ms min=3.44ms max=390.79ms count=29
D/EGL_emulation: eglCreateContext: 0x723d53444e10: maj 2 min 0 rcv 2
D/EGL_emulation: app_time_stats: avg=467.91ms min=2.15ms max=1398.04ms count=3
D/EGL_emulation: app_time_stats: avg=62.40ms min=12.81ms max=794.05ms count=17
V/FA: Inactivity, disconnecting from the service
D/EGL_emulation: app_time_stats: avg=912.88ms min=5.76ms max=6756.47ms count=8
E/WebView - Logger: Uncaught Cannnot transition to a new state, already under transition : 2 : Uncaught Cannnot transition to a new state, already under transition
E/WebView - Logger: Uncaught Cannnot transition to a new state, already under transition : 2 : Uncaught Cannnot transition to a new state, already under transition
D/EGL_emulation: eglCreateContext: 0x723d530d1550: maj 2 min 0 rcv 2
E/cr_VideoCapture: CameraDevice.StateCallback onOpened

相关代码:
override fun onCreateFinished() { binding.webView.settings.javaScriptEnabled = true binding.webView.settings.domStorageEnabled = true binding.webView.settings.mediaPlaybackRequiresUserGesture = false binding.webView.settings.allowFileAccessFromFileURLs = true binding.webView.settings.allowUniversalAccessFromFileURLs = true binding.webView.settings.allowContentAccess = false binding.webView.settings.useWideViewPort = true binding.webView.settings.loadWithOverviewMode = true binding.webView.settings.cacheMode = WebSettings.LOAD_NO_CACHE binding.webView.loadUrl("https://izlenebilirlik.migros.com.tr/urun_izle_ms.aspx") if (ContextCompat.checkSelfPermission(requireContext(), android.Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { ErrorDialog( ErrorType.CUSTOM_MESSAGE, ::onClickWarningMessage, getString(R.string.you_must_allow_camera_permission_to_read_qr) ).show( childFragmentManager, ErrorDialog.TAG ) } else { binding.webView.loadUrl("https://izlenebilirlik.migros.com.tr/urun_izle_ms.aspx") } binding.webView.webChromeClient = object : WebChromeClient() { override fun onPermissionRequest(request: android.webkit.PermissionRequest?) { if (request?.resources?.contains("android.permission.CAMERA") == true || request?.resources?.contains("android.webkit.resource.VIDEO_CAPTURE") == true) { if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { // Permission is not granted, so request it ActivityCompat.requestPermissions(requireActivity(), arrayOf(Manifest.permission.CAMERA), 100) } else { request.grant(request.resources) } } else { super.onPermissionRequest(request) } } override fun onConsoleMessage(consoleMessage: ConsoleMessage?): Boolean { Log.e ("WebView - Logger", consoleMessage?.message () + " : " + consoleMessage?.lineNumber () + " : " + consoleMessage?.message ()); return true } } } private fun onClickWarningMessage() { ActivityCompat.requestPermissions(requireActivity(), arrayOf(android.Manifest.permission.CAMERA), 100) }
从日志里的Uncaught Cannnot transition to a new state, already under transition错误,以及相机线程的锁竞争日志来看,问题出在相机资源没有正确释放,导致WebView再次进入时无法重新初始化相机,加上页面状态冲突引发的状态切换异常。可以按以下步骤修复:
1. 页面销毁时彻底释放WebView和相机资源
在Fragment的onDestroyView方法中,强制销毁WebView并清理相关资源,避免相机被占用:
override fun onDestroyView() { super.onDestroyView() // 停止WebView加载,释放资源 binding.webView.stopLoading() binding.webView.webChromeClient = null binding.webView.webViewClient = null // 清除WebView缓存和历史 binding.webView.clearCache(true) binding.webView.clearHistory() // 从父布局移除WebView,避免内存泄漏 (binding.webView.parent as? ViewGroup)?.removeView(binding.webView) binding.webView.destroy() }
2. 避免重复加载URL
当前代码中onCreateFinished里先调用了一次loadUrl,权限判断后又调用了一次,重复加载会导致页面状态混乱。去掉第一次的loadUrl,只在权限处理完成后加载:
override fun onCreateFinished() { binding.webView.settings.javaScriptEnabled = true binding.webView.settings.domStorageEnabled = true binding.webView.settings.mediaPlaybackRequiresUserGesture = false binding.webView.settings.allowFileAccessFromFileURLs = true binding.webView.settings.allowUniversalAccessFromFileURLs = true binding.webView.settings.allowContentAccess = false binding.webView.settings.useWideViewPort = true binding.webView.settings.loadWithOverviewMode = true binding.webView.settings.cacheMode = WebSettings.LOAD_NO_CACHE // 移除重复的loadUrl调用 // binding.webView.loadUrl("https://izlenebilirlik.migros.com.tr/urun_izle_ms.aspx") if (ContextCompat.checkSelfPermission(requireContext(), android.Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { ErrorDialog( ErrorType.CUSTOM_MESSAGE, ::onClickWarningMessage, getString(R.string.you_must_allow_camera_permission_to_read_qr) ).show( childFragmentManager, ErrorDialog.TAG ) } else { binding.webView.loadUrl("https://izlenebilirlik.migros.com.tr/urun_izle_ms.aspx") } // 剩余WebChromeClient代码不变 }
3. 处理权限申请后的页面刷新
当用户在权限申请弹窗中允许相机权限后,需要通知WebView重新加载页面,确保相机权限生效。重写onRequestPermissionsResult方法:
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) { super.onRequestPermissionsResult(requestCode, permissions, grantResults) if (requestCode == 100) { if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { // 权限允许后重新加载页面,确保相机能正常初始化 binding.webView.reload() } else { // 权限拒绝,再次提示用户 ErrorDialog( ErrorType.CUSTOM_MESSAGE, ::onClickWarningMessage, getString(R.string.you_must_allow_camera_permission_to_read_qr) ).show( childFragmentManager, ErrorDialog.TAG ) } } }
4. 优化相机状态冲突
日志中的Long monitor contention说明相机线程有锁竞争,可在页面生命周期中暂停/恢复WebView的媒体资源使用:
override fun onPause() { super.onPause() // 暂停WebView的所有媒体资源,释放相机 binding.webView.onPause() binding.webView.pauseTimers() } override fun onResume() { super.onResume() // 恢复WebView,重新初始化相机 binding.webView.onResume() binding.webView.resumeTimers() }
这些步骤可以解决相机资源未释放、页面重复加载、权限申请后状态不同步导致的灰色播放按钮问题,同时缓解相机线程的锁竞争情况。
内容的提问来源于stack exchange,提问作者Ahmet Yılmaz

