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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 20:24:50