我正在尝试使用异步更新适配器内的列表,我可以看到有太多的样板。
使用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()}
}
}
答案 0 :(得分:34)
经过几天的挣扎,我认为使用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.
}
}
协同程序的唯一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();
或者:
val jobs = arrayListOf<Deferred<Unit>>();
jobs += async(CommonPool) { /* Blocking call A */ };
jobs += async(CommonPool) { /* Blocking call B */ };
jobs.forEach { it.await(); };
答案 1 :(得分:24)
如何启动协程
在kotlinx.coroutines
库中,您可以使用launch
或async
函数启动新的协程。
从概念上讲,async
就像launch
一样。它启动一个单独的协程,这是一个轻量级的线程,与所有其他协同程序同时工作。
不同之处在于,发布会返回Job
并且不会带来任何结果值,而async
会返回Deferred
- 一个轻量级的非阻塞未来,代表一个承诺稍后提供结果。您可以对延迟值使用.await()
来获取最终结果,但Deferred
也是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
在下面的示例中,我们将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
}
启动+ 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
}
启动+ 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
}
如何取消协程
函数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
}
中提供了完整的答案
答案 2 :(得分:8)
我认为您可以使用runOnUiThread { ... }
上下文替代UI
来删除CommonPool
。
UI
模块提供- local_action: command rsync -av /root/owned/dir/ {{ item }}:/root/owned/dir/
with_items: "{{ groups['group'] }}"
when: inventory_hostname != "{{item}}"
become: no
上下文。
答案 3 :(得分:5)
我们还有另一种选择。如果我们使用Anko库,那么它看起来像这样
doAsync {
// Call all operation related to network or other ui blocking operations here.
uiThread {
// perform all ui related operation here
}
}
像你一样在你的应用程序gradle中添加Anko的依赖项。
compile "org.jetbrains.anko:anko:0.10.3"
答案 4 :(得分:3)
就像sdeff所说,如果你使用UI上下文,那个协程里面的代码默认会在UI线程上运行。而且,如果您需要在另一个线程上运行指令,可以使用run(CommonPool) {}
此外,如果您不需要从方法中返回任何内容,则可以使用函数launch(UI)
而不是async(UI)
(前者将返回Job
而后者将返回Deferred<Unit>
)。
一个例子可能是:
fun loadListOfMediaInAsync() = launch(UI) {
try {
withContext(CommonPool) { //The coroutine is suspended until run() ends
adapter.listOfMediaItems.addAll(resources.getAllTracks())
}
adapter.notifyDataSetChanged()
} catch(e: Exception) {
e.printStackTrace()
} catch(o: OutOfMemoryError) {
o.printStackTrace()
} finally {
progress.dismiss()
}
}
如果您需要更多帮助,我建议您阅读main guide of kotlinx.coroutines,另外还要阅读guide of coroutines + UI
答案 5 :(得分:1)
以上所有答案都是正确的,但我很难从UI
找到kotlinx.coroutines
的正确导入,但UI
与Anko
有冲突。
其
import kotlinx.coroutines.experimental.android.UI
答案 6 :(得分:0)
如果你想从后台线程返回一些东西,请使用async
launch(UI) {
val result = async(CommonPool) {
//do long running operation
}.await()
//do stuff on UI thread
view.setText(result)
}
如果后台线程没有返回任何内容
launch(UI) {
launch(CommonPool) {
//do long running operation
}.await()
//do stuff on UI thread
}
答案 7 :(得分:0)
这是使用Kotlin协程的正确方法。协程范围仅挂起当前的协程,直到所有子协程完成执行为止。此示例明确地向我们展示了 child coroutine
在 parent coroutine
中的工作方式。
带有说明的示例:
fun main() = blockingMethod { // coroutine scope
launch {
delay(2000L) // suspends the current coroutine for 2 seconds
println("Tasks from some blockingMethod")
}
coroutineScope { // creates a new coroutine scope
launch {
delay(3000L) // suspends this coroutine for 3 seconds
println("Task from nested launch")
}
delay(1000L)
println("Task from coroutine scope") // this line will be printed before nested launch
}
println("Coroutine scope is over") // but this line isn't printed until nested launch completes
}
希望这会有所帮助。
答案 8 :(得分:0)
请附带Kotlin Coroutines和Retrofit库的远程API调用的实现。
import android.view.View
import android.util.Log
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.test.nyt_most_viewed.NYTApp
import com.test.nyt_most_viewed.data.local.PreferenceHelper
import com.test.nyt_most_viewed.data.model.NytAPI
import com.test.nyt_most_viewed.data.model.response.reviews.ResultsItem
import kotlinx.coroutines.*
import javax.inject.Inject
class MoviesReviewViewModel @Inject constructor(
private val nytAPI: NytAPI,
private val nytApp: NYTApp,
appPreference: PreferenceHelper
) : ViewModel() {
val moviesReviewsResponse: MutableLiveData<List<ResultsItem>> = MutableLiveData()
val message: MutableLiveData<String> = MutableLiveData()
val loaderProgressVisibility: MutableLiveData<Int> = MutableLiveData()
val coroutineJobs = mutableListOf<Job>()
override fun onCleared() {
super.onCleared()
coroutineJobs.forEach {
it.cancel()
}
}
// You will call this method from your activity/Fragment
fun getMoviesReviewWithCoroutine() {
viewModelScope.launch(Dispatchers.Main + handler) {
// Update your UI
showLoadingUI()
val deferredResult = async(Dispatchers.IO) {
return@async nytAPI.getMoviesReviewWithCoroutine("full-time")
}
val moviesReviewsResponse = deferredResult.await()
this@MoviesReviewViewModel.moviesReviewsResponse.value = moviesReviewsResponse.results
// Update your UI
resetLoadingUI()
}
}
val handler = CoroutineExceptionHandler { _, exception ->
onMoviesReviewFailure(exception)
}
/*Handle failure case*/
private fun onMoviesReviewFailure(throwable: Throwable) {
resetLoadingUI()
Log.d("MOVIES-REVIEWS-ERROR", throwable.toString())
}
private fun showLoadingUI() {
setLoaderVisibility(View.VISIBLE)
setMessage(STATES.INITIALIZED)
}
private fun resetLoadingUI() {
setMessage(STATES.DONE)
setLoaderVisibility(View.GONE)
}
private fun setMessage(states: STATES) {
message.value = states.name
}
private fun setLoaderVisibility(visibility: Int) {
loaderProgressVisibility.value = visibility
}
enum class STATES {
INITIALIZED,
DONE
}
}