cef在android中使用_在 Android 中使用协程(Coroutine)

image

简评:可能对于很多的 Android 程序员来说协程(Coroutine)并不是一个熟悉的概念,更多是和线程、回调打交道。但协程这一概念其实很早就提出来了,C#, Lua, Go 等语言也支持协程,Kotlin 也提供了 kotlinx.coroutines 库来帮助使用协程。所以,今天这里就介绍下怎么通过 Kotlin 在 Android 中使用协程。

Coroutine 中文大多翻译为「协程」,相关概念网上已有很多相关的资料(《计算机程序设计艺术 卷一》中就有讲到 Coroutine),这里就不再赘述。

在这篇文章中,主要关注如何通过 kotlinx.coroutines 库来在 Android 中实现 Coroutine。

如何启动一个协程(Coroutine)

在 kotlinx.coroutines 库中,我们可以使用 launch 或 async 来启动一个新的 coroutine。

从概念上讲,async 和 launch 是类似的,区别在于 launch 会返回一个 Job 对象,不会携带任何结果值。而 async 则是返回一个 Deferred - 一个轻量级、非阻塞的 future,代表了之后将会提供结果值的承诺(promise),因此可以使用 .await() 来获得其最终的结果,当然 Deferred 也是一个 Job,如果需要也是可以取消的。

如果你对于 future, promise, deferred 等概念感到困惑,可以先阅读并发 Promise 模型或其他资料了解相关概念。

Coroutine context

在 Android 中我们经常使用两类 context:

uiContext: 用于执行 UI 相关操作。

bgContext: 用于执行需要在后台运行的耗时操作。

// dispatches execution onto the Android main UI thread

private val uiContext: CoroutineContext = UI

// represents a common pool of shared threads as the coroutine dispatcher

private val bgContext: CoroutineContext = CommonPool

这里 bgContext 使用 CommonPool,可以限制同时运行的线程数小于 Runtime.getRuntime.availableProcessors() - 1。

launch + async (execute task)

父协程(The parent coroutine)使用 uiContext 通过 *launch *启动。

子协程(The child coroutine)使用 CommonPool context 通过 *async *启动。

注意:

父协程总是会等待所有的子协程执行完毕。

如果发生未检查的异常,应用将会崩溃。

下面实现一个简单的读取数据并由视图进行展示的例子:

private fun loadData() = launch(uiContext) {

view.showLoading() // ui thread

val task = async(bgContext) { dataProvider.loadData("Task") }

val result = task.await() // non ui thread, suspend until finished

view.showData(result) // ui thread

}

launch + async + async (顺序执行两个任务)

下面的两个任务是顺序执行的:

private fun loadData() = launch(uiContext) {

view.showLoading() // ui thread

// non ui thread, suspend until task is finished

val result1 = async(bgContext) { dataProvider.loadData("Task 1") }.await()

// non ui thread, suspend until task is finished

val result2 = async(bgContext) { dataProvider.loadData("Task 2") }.await()

val result = "$result1 $result2" // ui thread

view.showData(result) // ui thread

}

launch + async + async (同时执行两个任务)

private fun loadData() = launch(uiContext) {

view.showLoading() // ui thread

val task1 = async(bgContext) { dataProvider.loadData("Task 1") }

val task2 = async(bgContext) { dataProvider.loadData("Task 2") }

val result = "${task1.await()} ${task2.await()}" // non ui thread, suspend until finished

view.showData(result) // ui thread

}

启动 coroutine 并设置超时时间

可以通过 withTimeoutOrNull 来给 coroutine job 设置时限,如果超时将会返回 null。

private fun loadData() = launch(uiContext) {

view.showLoading() // ui thread

val task = async(bgContext) { dataProvider.loadData("Task") }

// non ui thread, suspend until the task is finished or return null in 2 sec

val result = withTimeoutOrNull(2, TimeUnit.SECONDS) { task.await() }

view.showData(result) // ui thread

}

如何取消一个协程(coroutine)

var job: Job? = null

fun startPresenting() {

job = loadData()

}

fun stopPresenting() {

job?.cancel()

}

private fun loadData() = launch(uiContext) {

view.showLoading() // ui thread

val task = async(bgContext) { dataProvider.loadData("Task") }

val result = task.await() // non ui thread, suspend until finished

view.showData(result) // ui thread

}

当父协程被取消时,所有的子协程将递归的被取消。

在上面的例子中,如果 stopPresenting 在被调用时 dataProvider.loadData 正在运行,那么 view.showData 方法将不会被调用。

如何处理异常

try-catch block

我们还是可以像平时一样用 try-catch 来捕获和处理异常。不过这里推荐将 try-catch 移到 dataProvider.loadData 方法里面,而不是直接包裹在外面,并提供一个统一的 Result 类方便处理。

data class Result(val success: T? = null, val error: Throwable? = null)

private fun loadData() = launch(uiContext) {

view.showLoading() // ui thread

val task = async(bgContext) { dataProvider.loadData("Task") }

val result: Result = task.await() // non ui thread, suspend until the task is finished

if (result.success != null) {

view.showData(result.success) // ui thread

} else if (result.error != null) {

result.error.printStackTrace()

}

}

async + async

当通过 async 来启动父协程时,将会忽略掉任何异常:

private fun loadData() = async(uiContext) {

view.showLoading() // ui thread

val task = async(bgContext) { dataProvider.loadData("Task") }

val result = task.await() // non ui thread, suspend until the task is finished

view.showData(result) // ui thread

}

在这里 loadData() 方法会返回 Job 对象,而 exception 会被存放在这个 Job 对象中,我们可以用 invokeOnCompletion 函数来进行检索:

var job: Job? = null

fun startPresenting() {

job = loadData()

job?.invokeOnCompletion { it: Throwable? ->

it?.printStackTrace() // (1)

// or

job?.getCompletionException()?.printStackTrace() // (2)

// difference between (1) and (2) is that (1) will NOT contain CancellationException

// in case if job was cancelled

}

}

launch + coroutine exception handler

我们还可以为父协程的 context 中添加 CoroutineExceptionHandler 来捕获和处理异常:

val exceptionHandler: CoroutineContext = CoroutineExceptionHandler { _, throwable -> throwable.printStackTrace() }

private fun loadData() = launch(uiContext + exceptionHandler) {

view.showLoading() // ui thread

val task = async(bgContext) { dataProvider.loadData("Task") }

val result = task.await() // non ui thread, suspend until the task is finished

view.showData(result) // ui thread

}

怎么测试协程(coroutine)?

要启动协程(coroutine)必须要指定一个 CoroutineContext。

class MainPresenter(private val view: MainView,

private val dataProvider: DataProviderAPI) {

private fun loadData() = launch(UI) { // UI - dispatches execution onto Android main UI thread

view.showLoading()

// CommonPool - represents common pool of shared threads as coroutine dispatcher

val task = async(CommonPool) { dataProvider.loadData("Task") }

val result = task.await()

view.showData(result)

}

}

因此,如果你想要为你的 MainPresenter 写单元测试,你就必须要能为 UI 和 后台任务指定 coroutine context。

最简单的方式就是为 MainPresenter 的构造方法增加两个参数,并设置默认值:

class MainPresenter(private val view: MainView,

private val dataProvider: DataProviderAPI

private val uiContext: CoroutineContext = UI,

private val ioContext: CoroutineContext = CommonPool) {

private fun loadData() = launch(uiContext) { // use the provided uiContext (UI)

view.showLoading()

// use the provided ioContext (CommonPool)

val task = async(bgContext) { dataProvider.loadData("Task") }

val result = task.await()

view.showData(result)

}

}

现在,就可以在测试中传入 kotlin.coroutines 提供的 EmptyCoroutineContext 来让代码运行在当前线程里。

@Test

fun test() {

val dataProvider = Mockito.mock(DataProviderAPI::class.java)

val mockView = Mockito.mock(MainView::class.java)

val presenter = MainPresenter(mockView, dataProvider, EmptyCoroutineContext, EmptyCoroutineContext)

presenter.startPresenting()

...

}

上面就是 kotlin 中协程(coroutine)的基本用法,完整代码可以查看 Github 项目。

如果想了解更多内容还可以查看 Kotlin 的官方示例:Kotlin/kotlinx.coroutines

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
duilib 是一个基于C++的GUI库,用于构建Windows平台上的桌面应用程序。CEF(Chromium Embedded Framework)是一个开源项目,用于将Chromium引擎集成到其他应用程序,实现浏览器功能。在duilib使用CEF控件可以实现在应用程序嵌入一个内置的浏览器。 要在duilib使用CEF控件,首先需要下载CEF的二进制文件,并将相应的头文件和库文件链接到duilib项目。然后在duilib的UI布局文件添加一个容器控件用来承载CEF控件,比如一个<Control>标签定义一个名为"browser"的控件。接着在C++代码通过CreateControl()方法创建CEF控件并将其添加到布局的容器控件。 在CEF控件加载网页可以通过调用LoadURL()方法来实现,比如在初始化时可以加载一个初始的网页。同时也可以通过CEF提供的接口来处理网页加载完成、前进后退等事件。另外,也可以在CEF的控件执行JavaScript代码,比如修改网页内容或者与网页交互等。 当应用程序退出时,需要通过调用CEF控件的Close()方法来释放资源并关闭CEF的相关进程。另外,需要确保在适当的时候调用CEF的资源释放方法,比如在窗口销毁前调用Shutdown()方法来释放资源。 在duilib使用CEF控件可以帮助开发者实现嵌入式浏览器功能,比如在应用程序展示在线内容或者实现基于web的功能。同时也能够与duilib的其他控件进行交互,实现更加丰富的用户体验。通过合理的使用和管理,可以让应用程序在集成CEF控件的同时保持稳定性和性能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值