Kotlin Coroutines在Android中正确的方式

Sai*_*Sai 38 android coroutine async-await kotlin

我正在尝试使用异步更新适配器内的列表,我可以看到有太多的样板.

这是使用Kotlin Coroutines的正确方法吗?

这可以更优化吗?

fun loadListOfMediaInAsync() = async(CommonPool) {
        try {
            //Long running task 
            adapter.listOfMediaItems.addAll(resources.getAllTracks())
            runOnUiThread {
                adapter.notifyDataSetChanged()
                progress.dismiss()
            }
        } catch (e: Exception) {
            e.printStackTrace()
            runOnUiThread {progress.dismiss()}
        } catch (o: OutOfMemoryError) {
            o.printStackTrace()
            runOnUiThread {progress.dismiss()}
        }
    }
Run Code Online (Sandbox Code Playgroud)

KTC*_*TCO 38

经过几天的挣扎,我认为使用Kotlin的Android活动最简单明了的async-await模式是:

override fun onCreate(savedInstanceState: Bundle?) {
    //...
    loadDataAsync(); //"Fire-and-forget"
}

fun loadDataAsync() = async(UI) {
    try {
        //Turn on busy indicator.
        val job = async(CommonPool) {
           //We're on a background thread here.
           //Execute blocking calls, such as retrofit call.execute().body() + caching.
        }
        job.await();
        //We're back on the main thread here.
        //Update UI controls such as RecyclerView adapter data.
    } 
    catch (e: Exception) {
    }
    finally {
        //Turn off busy indicator.
    }
}
Run Code Online (Sandbox Code Playgroud)

协同程序的唯一Gradle依赖项是:kotlin-stdlib-jre7,kotlinx-coroutines-android.

注意:使用job.await()而不是job.join()因为await()重新抛出异常,但join()不是.如果您使用join(),则需要job.isCompletedExceptionally在作业完成后进行检查.

要启动并发改装调用,您可以执行以下操作:

val jobA = async(CommonPool) { /* Blocking call A */ };
val jobB = async(CommonPool) { /* Blocking call B */ };
jobA.await();
jobB.await();
Run Code Online (Sandbox Code Playgroud)

要么:

val jobs = arrayListOf<Deferred<Unit>>();
jobs += async(CommonPool) { /* Blocking call A */ };
jobs += async(CommonPool) { /* Blocking call B */ };
jobs.forEach { it.await(); };
Run Code Online (Sandbox Code Playgroud)

  • 请注意,这基本上与非静态AsyncTask完全相同,具有相同的潜在问题.您可以"触发"但不"忘记"它,因为它最终会与您的活动进行交互.我建议您在onStart()中启动协程并在onStop()中取消它,以避免在Activity不可见时执行工作并阻止在销毁Activity之后更新视图.另一种解决方案是将协程移动到Loader或ViewModel(来自Architecture组件). (13认同)

Dmy*_*lyk 33

如何启动协程

在kotlinx.coroutines库中,您可以使用launch或者async函数启动新的协同程序.

从概念上讲,async就像launch.它启动一个单独的协程,这是一个轻量级的线程,与所有其他协同程序同时工作.

不同之处在于,启动返回a Job并且不携带任何结果值,而async返回a Deferred- 轻量级非阻塞未来,表示稍后提供结果的承诺.您可以使用.await()延迟值来获取其最终结果,但Deferred也是a Job,因此您可以根据需要取消它.

协同上下文

在Android中我们通常使用两个上下文:

  • uiContext将执行分派到Android主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
Run Code Online (Sandbox Code Playgroud)

在下面的例子中,我们将使用CommonPool用于bgContext这限制并行运行,它的值的线程数Runtime.getRuntime.availableProcessors()-1.因此,如果安排了协程任务,但所有核心都被占用,它将排队.

您可能需要考虑使用newFixedThreadPoolContext或自己实现的缓存线程池.

启动+异步(执行任务)

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
}
Run Code Online (Sandbox Code Playgroud)

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

注意:task1和task2按顺序执行.

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
}
Run Code Online (Sandbox Code Playgroud)

launch + async + async(并行执行两个任务)

注意:task1和task2是并行执行的.

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
}
Run Code Online (Sandbox Code Playgroud)

如何取消协程

该函数loadData返回一个Job可能被取消的对象.当取消父协程时,它的所有子节点也会被递归取消.

如果stopPresenting函数被调用,同时dataProvider.loadData仍在进行中,该功能view.showData将永远不会被调用.

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
}
Run Code Online (Sandbox Code Playgroud)

完整的答案可以在我的文章Android Coroutine Recipes中找到


Ste*_*fen 8

我认为你可以runOnUiThread { ... }通过使用UIAndroid应用程序的上下文而不是CommonPool.

所述UI上下文由提供kotlinx协同程序-机器人模块.


小智 5

我们还有另一种选择.如果我们使用Anko库,那么它看起来像这样

doAsync { 

    // Call all operation  related to network or other ui blocking operations here.
    uiThread { 
        // perform all ui related operation here    
    }
}
Run Code Online (Sandbox Code Playgroud)

像这样在您的应用程序gradle中添加Anko的依赖项.

compile "org.jetbrains.anko:anko:0.10.3"
Run Code Online (Sandbox Code Playgroud)