Android Kotlin:非挂起函数实现WebView DOM抓取适配DukTape
问题:WebView DOM抓取同步返回适配DukTape需求
在Kotlin Android项目中,我通过WebView抓取HTML页面DOM,计划用DukTape库结合远程JS文件处理DOM(无需更新应用即可适配未来DOM结构变更)。当前遇到的问题是:webViewRequest函数无法等待DOM抓取完成就返回空值,且不能改为挂起函数适配DukTape,尝试过runBlocking和协程启动都无法将数据传递给DukTape。
解决方案:用CountDownLatch实现同步等待
核心思路是利用CountDownLatch阻塞当前线程,直到WebView完成DOM抓取并回调结果,同时注意线程切换避免ANR:
1. 修改WebViewClientRequest,支持结果回调
给WebViewClientRequest添加回调参数,用于传递抓取到的HTML:
class WebViewClientRequest( private val webView: WebView, private val url: String, private val onHtmlFetched: (String) -> Unit ) { init { val webSettings = webView.settings webSettings.javaScriptEnabled = true webSettings.userAgentString = "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Mobile Safari/537.36" val cookieManager = CookieManager.getInstance() cookieManager.setAcceptCookie(true) cookieManager.setAcceptThirdPartyCookies(webView, true) webView.addJavascriptInterface(JsInterface(), "HTMLOUT") webView.webViewClient = WebClientFetchData() webView.loadUrl(url) } inner class WebClientFetchData : WebViewClient() { override fun onPageFinished(view: WebView?, url: String?) { super.onPageFinished(view, url) webView.loadUrl( "javascript:window.HTMLOUT.showHTML('<html>'+document.getElementsByTagName('html')[0].innerHTML+'</html>');" ) } override fun onReceivedError(view: WebView?, request: WebResourceRequest?, error: WebResourceError?) { super.onReceivedError(view, request, error) onHtmlFetched("Error loading page: ${error?.description}") } } inner class JsInterface { @JavascriptInterface fun showHTML(html: String) { onHtmlFetched(html) } } }
2. 修改webViewRequest实现,用CountDownLatch同步等待
在webViewRequest中创建CountDownLatch,等待回调完成后再返回结果:
override fun webViewRequest(url: String): String { val latch = CountDownLatch(1) var resultHtml = "" // WebView操作必须在主线程执行,切换到主线程发起请求 Handler(Looper.getMainLooper()).post { WebViewClientRequest(webView, url) { html -> resultHtml = html latch.countDown() // 抓取完成,释放锁 } } try { // 设置30秒超时,防止线程无限阻塞 latch.await(30, TimeUnit.SECONDS) } catch (e: InterruptedException) { e.printStackTrace() resultHtml = "Request interrupted" } return resultHtml }
3. 调整Duktape执行线程,避免阻塞主线程
必须把Duktape的执行放到后台线程,因为latch.await()会阻塞当前线程,在主线程执行会导致ANR:
// 在Fragment的合适生命周期(比如onViewCreated)中启动后台线程 lifecycleScope.launch(Dispatchers.IO) { val duktape = Duktape.create() val webView = binding.unvisibleWebview val retrofitBridge = RetrofitBridge(webView) duktape.set("retrofitBridge", AndroidNetworking::class.java, retrofitBridge) try { val jsCode = """var result = retrofitBridge.webViewRequest("https://jsonplaceholder.typicode.com/posts"); result;""" val result = duktape.evaluate(jsCode) // 回到主线程处理结果(比如更新UI) withContext(Dispatchers.Main) { Log.d("ResponseHtml", result.toString()) println(result) } } catch (e: Exception) { e.printStackTrace() } finally { duktape.close() } }
关键注意事项
- 线程切换:WebView的所有操作必须在主线程执行,而Duktape的执行要放在后台线程,避免主线程阻塞
- 超时设置:给
latch.await()设置合理的超时时间,防止线程无限等待 - WebView初始化:确保WebView已经完成初始化(比如在Fragment的
onViewCreated之后使用)
内容的提问来源于stack exchange,提问作者Lalmi Mohammed Issam
相关产品推荐
相关产品推荐

