Android Coroutine Recipes
本文的原始碼可以在YangQiRen/coroutine-recipes中找到
子協程通過調度程序的 async 功能啟動 IO 。
注意:父協程始終等待其所有子進程完成。
注意:如果發生未經檢查的異常,應用程序將崩潰。
父協程通過分派器的 launch 功能啟動 Main 。
背景作業通過執行 withContext 與功能的 IO 調度。
如果 stopPresenting 函數被調用,同時 dataProvider.loadData 仍在進行中,該功能 view.showData 將永遠不會被調用。
用於生命週期的協程範圍的示例 LifecycleObserver 。
子協程通過調度程序的 async 功能啟動 IO 。
注意:您可以使用 try-catch 塊來捕獲異常並進行處理。
基於Coroutine v1.0.0版本
引入依賴
implementation
'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.0.0'
implementation
'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.0.0'
如何啟動協程(Coroutine)
在 kotlinx.coroutines 庫中,您可以使用 launch 或 async 函數來啟動新的協程。
從概念上講, async 就像 launch 。它啟動一個單獨的協程,這是一個輕量級線程,可與所有其他協程同時工作。區別在於, launch 返回一個 Job 且不攜帶任何結果值,而 async 返回一個 Deferred 輕量無阻塞的未來,表示希望稍後提供結果。您可以使用 .await() 延期的值來獲得其最終結果,但是 Deferred 它也是一個 Job ,因此可以根據需要取消它。
***重點***
如果裡面的代碼 launch 以異常終止,然後將其視為未捕獲線程中的異常會使Android應用程序崩潰。內部未捕獲的異常 async 代碼存儲在結果中 Deferred 並且不會在其他任何地方交付,除非經過處理,否則它將被靜默丟棄。
協程調度員(Coroutine Dispatcher)
在Android中,我們通常使用兩個調度程序:- uiDispatcher將執行分派到Android主UI線程(用於父協程)。
- bgDispatcher在後台線程中分派執行(用於子協程)。
//將執行分派到Android主線程中
val uiDispatcher:CoroutineDispatcher = Dispatchers.Main
//將共享線程池表示為協程分派器
val bgDispatcher:CoroutineDispatcher = Dispatchers.I0
在下面的例子中,我們將使用 CommonPool 用於 bgContext 這限制並行運行為值64個線程或核的數量的線程的數目。
您可能要考慮使用 newFixedThreadPoolContext 緩存線程池或使用自己的緩存線程池實現。
協程範圍
要啟動協程,您需要提供 CoroutineScope 或使用 GlobalScope 。// GlobalScope example
class MainFragment : Fragment() {
fun loadData() = GlobalScope.launch { ... }
}
// CoroutineScope example
class MainFragment : Fragment() {
val uiScope = CoroutineScope(Dispatchers.Main)
fun loadData() = uiScope.launch { ... }
}
// Fragment implements CoroutineScope example
class MainFragment : Fragment(), CoroutineScope {
override val coroutineContext: CoroutineContext
get() = Dispatchers.Main
fun loadData() = launch { ... }
}
launch + async (execute task)
父協程通過分派器的 launch 功能啟動 Main 。子協程通過調度程序的 async 功能啟動 IO 。
注意:父協程始終等待其所有子進程完成。
注意:如果發生未經檢查的異常,應用程序將崩潰。
val uiScope = CoroutineScope(Dispatchers.Main)
fun loadData() = uiScope.launch {
view.showLoading() // ui thread
val task = async(bgDispatcher) { // background thread
// your blocking call
}
val result = task.await()
view.showData(result) // ui thread
}
launch + withContext (execute task)
儘管前面的示例運行良好,但我們通過啟動第二個協程來進行後台工作來浪費資源。如果僅啟動一個協程並用於 withContext 切換協程上下文,則可以優化代碼。父協程通過分派器的 launch 功能啟動 Main 。
背景作業通過執行 withContext 與功能的 IO 調度。
val uiScope = CoroutineScope(Dispatchers.Main)
fun loadData() = uiScope.launch {
view.showLoading() // ui thread
val result = withContext(bgDispatcher) { // background thread
// your blocking call
}
view.showData(result) // ui thread
}
launch + withContext (execute two tasks sequentially)
父協程通過分派器的 launch 功能啟動 Main 。
背景作業通過執行 withContext 與功能的 IO 調度。
注意: result1 和 result2 順序執行。
注意:如果發生未經檢查的異常,應用程序將崩潰。
val uiScope = CoroutineScope(Dispatchers.Main)
fun loadData() = uiScope.launch {
view.showLoading() // ui thread
val result1 = withContext(bgDispatcher) { // background thread
// your blocking call
}
val result2 = withContext(bgDispatcher) { // background thread
// your blocking call
}
val result = result1 + result2
view.showData(result) // ui thread
}
launch + async + async (execute two tasks parallel)
父協程通過分派器的 launch 功能啟動 Main 。
背景作業通過執行 async 與功能的 IO 調度。
注意: task1 和 task2 是並行執行。
背景作業通過執行 async 與功能的 IO 調度。
注意: task1 和 task2 是並行執行。
注意:如果發生未經檢查的異常,應用程序將崩潰。
val uiScope = CoroutineScope(Dispatchers.Main)
fun loadData() = uiScope.launch {
view.showLoading() // ui thread
val task1 = async(bgDispatcher) { // background thread
// your blocking call
}
val task2 = async(bgDispatcher) { // background thread
// your blocking call
}
val result = task1.await() + task2.await()
view.showData(result) // ui thread
}
How to launch a coroutine with a timeout
如果要為協程作業設置超時,請使用暫停的函數包裝該函數 withTimeoutOrNull ,如果超時,該函數將返回 null 。val uiScope = CoroutineScope(Dispatchers.Main)
fun loadData() = uiScope.launch {
view.showLoading() // ui thread
val task = async(bgDispatcher) { // background thread
// your blocking call
}
// suspend until task is finished or return null in 2 sec
val result = withTimeoutOrNull(2000) { task.await() }
view.showData(result) // ui thread
}
How to cancel a coroutine
job
該函數 loadData 返回一個 Job 可能被取消的對象。當父協程被取消時,其所有子進程也將被遞歸取消。val uiScope = CoroutineScope(Dispatchers.Main)
var job: Job? = null
fun startPresenting() {
job = loadData()
}
fun stopPresenting() {
job?.cancel()
}
fun loadData() = uiScope.launch {
view.showLoading() // ui thread
val result = withContext(bgDispatcher) { // background thread
// your blocking call
}
view.showData(result) // ui thread
}
parent job
注意:如果取消父作業,則需要創建新的Job對象才能啟動新的協程,這就是我們使用cancelChildren的原因。
var job = SupervisorJob()
val uiScope = CoroutineScope(Dispatchers.Main + job)
fun startPresenting() {
loadData()
}
fun stopPresenting() {
scope.coroutineContext.cancelChildren()
}
fun loadData() = uiScope.launch {
view.showLoading() // ui thread
val result = withContext(bgDispatcher) { // background thread
// your blocking call
}
view.showData(result) // ui thread
}
lifecycle aware coroutine scope
隨著android體系結構組件的發布,我們可以創建生命週期感知的協程範圍,該範圍將在 Activity#onDestroy 事件發生時自行取消。用於生命週期的協程範圍的示例 LifecycleObserver 。
class MainScope : CoroutineScope, LifecycleObserver {
private val job = SupervisorJob()
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
fun destroy() = coroutineContext.cancelChildren()
}
// usage
class MainFragment : Fragment() {
private val uiScope = MainScope()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
lifecycle.addObserver(mainScope)
}
private fun loadData() = uiScope.launch {
val result = withContext(bgDispatcher) {
// your blocking call
}
}
}
How to handle exceptions with coroutines
try-catch block
父協程通過分派器的 launch 功能啟動 Main 。子協程通過調度程序的 async 功能啟動 IO 。
注意:您可以使用 try-catch 塊來捕獲異常並進行處理。
留言
張貼留言