Android协程回调调用Google Ads触发WebView线程错误如何修复?
安卓协程调用广告线程异常修复方案
问题背景
部分用户使用时出现如下崩溃:
kotlinx.coroutines.CompletionHandlerException: Exception in completion handler InvokeOnCompletion@c1de63[job@7e00a60] for StandaloneCoroutine{Completed}@7e00a60 at kotlinx.coroutines.JobSupport.completeStateFinalization(JobSupport.kt:320) at kotlinx.coroutines.JobSupport.tryFinalizeSimpleState(JobSupport.kt:295) at kotlinx.coroutines.JobSupport.tryMakeCompleting(JobSupport.kt:856) at kotlinx.coroutines.JobSupport.makeCompletingOnce$kotlinx_coroutines_core(JobSupport.kt:828) at kotlinx.coroutines.AbstractCoroutine.resumeWith(AbstractCoroutine.kt:100) at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:46) at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106) at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571) at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:750) at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678) at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665) Caused by: java.lang.RuntimeException: java.lang.Throwable: A WebView method was called on thread 'DefaultDispatcher-worker-5'. All WebView methods must be called on the same thread. (Expected Looper Looper (main, tid 2) {8812c5f} called on null, FYI main Looper is Looper (main, tid 2) {8812c5f}) at android.webkit.WebView.checkThread(WebView.java:2603) at android.webkit.WebView.evaluateJavascript(WebView.java:888) at com.google.android.gms.ads.internal.webview.ac.evaluateJavascript(:com.google.android.gms.policy_ads_fdr_dynamite@213806100@213806100057.397311113.397311113:3) // 省略中间堆栈 at com.app_name.utils.ads.Ads.showInter(Ads.kt:73) at com.app_name.ui.FragmentDeleting$jProgressBarAnimate$1$1$1$onAnimationEnd$1.invoke(FragmentDeleting.kt:105) at com.app_name.ui.FragmentDeleting$jProgressBarAnimate$1$1$1$onAnimationEnd$1.invoke(FragmentDeleting.kt:102) at kotlinx.coroutines.InvokeOnCompletion.invoke(JobSupport.kt:1391) at kotlinx.coroutines.JobSupport.completeStateFinalization(JobSupport.kt:318) ... 10 more Caused by: java.lang.Throwable: A WebView method was called on thread 'DefaultDispatcher-worker-5'. All WebView methods must be called on the same thread. (Expected Looper Looper (main, tid 2) {8812c5f} called on null, FYI main Looper is Looper (main, tid 2) {8812c5f}) at android.webkit.WebView.checkThread(WebView.java:2598) ... 33 more
现有业务代码如下:
- 删除功能协程代码:
val deletingJob = lifecycleScope.launch(Dispatchers.IO) { viewModel.selectFilesDeletion { _, _, _ -> } }
- 动画结束调用广告逻辑:
val animProgress = ObjectAnimator.ofInt(progressBar, "progress", progressBar.progress, PROGRESS_MAX) animProgress.apply { duration = DURATION_ANIMATION setAutoCancel(true) interpolator = DecelerateInterpolator() start() doOnEnd { binding.lottieAnimation.addAnimatorListener(object : Animator.AnimatorListener { override fun onAnimationStart(p0: Animator?) {} override fun onAnimationCancel(p0: Animator?) {} override fun onAnimationRepeat(p0: Animator?) {} override fun onAnimationEnd(animator: Animator) { if (deletingJob.isCompleted) { Ads.showInter(requireActivity(), this@FragmentDeleting) } else { deletingJob.invokeOnCompletion { Ads.showInter(requireActivity(), this@FragmentDeleting) } } } }) binding.lottieAnimation.playAnimation() } }
问题解答
1. 包runOnUiThread是否能解决问题?
可以。所有WebView相关的API必须在主线程调用,requireActivity().runOnUiThread{ }会将包裹的逻辑强制投递到主线程执行,完全符合系统要求,能直接修复该崩溃。
2. 是否需要改写现有协程逻辑?问题根源是什么?
不需要完全改写现有逻辑,问题确实是线程调用不符合要求导致的:
- 当动画结束时删除任务已经执行完成,走
deletingJob.isCompleted为true的分支,这部分代码运行在动画回调的主线程,调用广告没有问题 - 当动画结束时删除任务还在执行,走
invokeOnCompletion回调,这个回调的执行线程是协程完成时所在的线程,也就是Dispatchers.IO对应的子线程,这时候调用广告内部的WebView API就触发了线程检查异常
可选修复方案
两种方案任选即可:
方案1:最小修改,给广告调用加主线程包裹
两个调用点都加上主线程调度:
override fun onAnimationEnd(animator: Animator) { if (deletingJob.isCompleted) { requireActivity().runOnUiThread { Ads.showInter(requireActivity(), this@FragmentDeleting) } } else { deletingJob.invokeOnCompletion { requireActivity().runOnUiThread { Ads.showInter(requireActivity(), this@FragmentDeleting) } } } }
方案2:协程写法优化,更简洁规范
不用手动判断任务状态和切线程,用协程的join方法更符合协程使用习惯:
override fun onAnimationEnd(animator: Animator) { lifecycleScope.launch { // 挂起等待删除任务完成,不会阻塞主线程 deletingJob.join() // lifecycleScope默认运行在主线程,直接调用广告即可 Ads.showInter(requireActivity(), this@FragmentDeleting) } }
内容的提问来源于stack exchange,提问作者Binary
相关产品推荐
相关产品推荐

