运行 使用 Kotlin 协程按顺序评估 Javascript() 函数

Run evaluateJavascript() function sequentially using Kotlin Corutines

我正在做一个宠物项目,我正在尝试使用 WebView 创建一个混合应用程序。我在WebView中运行的网络平台通过一个@JavascriptInterface对象向WebView/App发送事件。我还可以 命令 网络导航,方法是 运行 使用 evaluateJavascript(String, (String) -> Unit) 通过 WebView 针对网络平台设置一组 javascript 函数] 函数。

我现在想要实现的是,我通过evaluateJavascript(String, (String) -> Unit)函数运行顺序执行的这些命令。我可能同时从许多不同的地方执行这些命令,所以我希望它们 运行,等待 evaluateJavascript() 函数的回调被调用,然后执行队列中的下一个命令.

这是我的习惯 WebView class:

    val scriptQueue = mutableListOf<String>()
    
    fun queueEvaluateJavascript(script: String) {
        if (webViewIsLoading) {
            scriptQueue.add(script)
        } else {
            scriptQueue.add(script)
            runScriptQueue()
        }
    }
    
    fun runScriptQueue() {
        for (script in scriptQueue) {
            evaluateJavascript(script, { })
        }
        scriptQueue.clear()
    }

如您所见,这是一种超级基本的方法,我并没有真正考虑 evaluateJavascript() 回调。理想情况下,我想找到一种方法 flat map 每个 evaluateJavascript() 调用,这样我们一个接一个地执行,但等待回调完成。

使用 RxJava 我想我会创建一个 Observable,然后让 evaluateJavascript() 回调触发订阅者的 onNext()。因为,我正在使用 Kotlin Coroutines 我想用 Coroutines 做一些事情,所以我可以对这些 evaulateJavascript() 调用进行排队。但我不是 100% 确定这里的等价物是什么。

用协程解决这个问题会很好。

将基于回调的 API 转换为挂起函数的常用方法如下:

suspend fun evaluateJs(script: String) = suspendCoroutine<String> { cont ->
    evaluateJavascript(script) { result ->
        cont.resume(result)
    }
}

然后您可以将它与 Channel(用作队列)和处理此通道的协程结合使用:

class MyWebView(context: Context) : WebView(context) {
    
    private val jsQueue = Channel<String>(BUFFERED)

    fun startJsProcessingLoopIn(scope: CoroutineScope) {
        scope.launch { 
            for (script in jsQueue) {
                evaluateJs(script)
            }
        }
    }

    // you could also make this function non-suspend if necessary by calling
    // sendBlocking (or trySend depending on coroutines version)
    suspend fun queueEvaluateJavascript(script: String) {
        jsQueue.send(script)
    }

    private suspend fun evaluateJs(script: String) = suspendCoroutine<String> { cont ->
        evaluateJavascript(script) { result ->
            cont.resume(result)
        }
    }
}

或者你可以创建你自己的协程作用域,并确保将它与你的 webview 的某种生命周期联系起来(我不熟悉 WebView 所以我会让你判断哪种方法正确):

class MyWebView2(context: Context) : WebView(context) {

    // you can even further customize the exact thread pool used here 
    // by providing a particular dispatcher
    private val jsProcessingScope = CoroutineScope(CoroutineName("js-processing"))

    private val jsQueue = Channel<String>(BUFFERED)

    // this starts the loop right away but you can also put this in a method 
    // to start it at a more appropriate moment
    init {
        jsProcessingScope.launch {
            for (script in jsQueue) {
                evaluateJs(script)
            }
        }
    }

    // you could also make this function non-suspend if necessary by calling
    // sendBlocking (or trySend depending on coroutines version)
    suspend fun queueEvaluateJavascript(script: String) {
        jsQueue.send(script)
    }

    private suspend fun evaluateJs(script: String) = suspendCoroutine<String> { cont ->
        evaluateJavascript(script) { result ->
            cont.resume(result)
        }
    }

    fun someCloseOrDisposeCallback() {
        jsProcessingScope.cancel()
    }
}