android - 使用 Kotlin 协程按顺序运行 evaluateJavascript() 函数
问题描述
我正在做一个宠物项目,我正在尝试使用WebView
. 我在其中运行的 Web 平台通过对象WebView
向 WebView/App 发送事件。@JavascriptInterface
我还可以通过使用该函数在 Web 平台上运行一组 javascript 函数来命令Web 导航。WebView
evaluateJavascript(String, (String) -> Unit)
我现在想要实现的是,我通过函数执行的这些命令evaluateJavascript(String, (String) -> Unit)
是按顺序运行的。我可能会同时从许多不同的地方执行这些命令,所以我希望它们运行,等待evaluateJavascript()
函数的回调被调用,然后执行队列中的下一个命令。
这就是我在我的自定义WebView
类中所拥有的:
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()
回调。理想情况下,我想找到一种方法来平面映射每个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()
}
}
推荐阅读
- bucket - RGW 多站点 - 如何设置默认存储桶同步禁用?
- java - 如何类型安全地将元素添加到泛型扩展类并实现接口的列表中?
- pentaho - Pentaho - 使用什么步骤来翻转桌子的一部分?
- javascript - 使用“useLocation”时如何将位置传递给道具
- python - 指定时间后 cronjob 不运行?
- amazon-s3 - Amazon S3 parquet 文件 - 传输到 GCP / BQ
- python - 使用嵌套循环修改python中的数组
- javascript - 如何将参数数据发送到抽屉堆栈中的屏幕。在本机反应
- mysql - 从数据库查询中获取总数
- google-sheets - GSheet 中是否有任何函数可以计算当前日期花费的总时间,单列中有两个不同的时间单位?