本文章已授權微信公眾號鴻洋(hongyangAndroid)轉載。
本文章講解的內容是在在Android中使用協(xié)程以及協(xié)程源碼分析。
在說協(xié)程之前,我先說下線程和線程池:
線程是操作系統(tǒng)的內核資源,是CPU調度的最小單位,所有的應用程序都運行在線程上,它是我們實現并發(fā)和異步的基礎。在Java的API中,Thread是實現線程的基礎類,每創(chuàng)建一個Thread對象,操作系統(tǒng)內核就會啟動一個線程,在Thread的源碼中,它的內部實現是大量的JNI調用,因為線程的實現必須由操作系統(tǒng)提供直接支持,在Linux操作系統(tǒng)中,每一個Java thread對應一個native thread,它們是一一對應的,在Android中,創(chuàng)建Thread的過程中都會調用Linux API中的pthread_create函數。
線程的調用會有存在以下問題:
- 線程不是輕量級資源,大量創(chuàng)建線程會消耗系統(tǒng)大量資源,傳統(tǒng)的阻塞調用會導致系統(tǒng)存在大量的因為阻塞而不能運行的線程,這很浪費系統(tǒng)資源。
- 線程阻塞狀態(tài)和運行狀態(tài)的切換會存在相當大的開銷,一直以來都是個優(yōu)化點,例如:JVM在運行時會對鎖進行優(yōu)化,就像自旋鎖、鎖粗化和鎖消除等等。
線程池(Thread Pool)是一種基于池化思想管理線程的工具,使用線程池有如下好處:
- 降低資源消耗:通過池化技術重復利用已創(chuàng)建的線程,降低線程的創(chuàng)建和銷毀的損耗。
- 提高響應速度:任務到達時,無需等待線程創(chuàng)建即可立即執(zhí)行。
- 提高線程的可管理性:線程是稀缺資源,如果無限制創(chuàng)建,不僅會消耗系統(tǒng)資源,還會因為線程的不合理分布導致資源調度失衡,降低系統(tǒng)的穩(wěn)定性,使用線程池可以進行統(tǒng)一的分配、調優(yōu)和監(jiān)控。
- 提供更多更強大的功能:線程池具備可拓展性,允許開發(fā)人員向其中增加更多的功能。
那協(xié)程與線程有什么關系呢?在Java中,協(xié)程是基于線程池的API,它并沒有脫離Java或者Kotlin已經有的東西。
協(xié)程的定義
協(xié)程源自Simula和Modula-2語言,它是一種編程思想,并不局限于特定的語言,在1958年的時候,Melvin Edward Conway提出這個術語并用于構建匯編程序。在Android中使用它可以簡化異步執(zhí)行的代碼,它是在版本1.3中添加到Kotlin。
協(xié)程的使用
下面來介紹如何使用協(xié)程:
依賴
要使用協(xié)程,需要在build.gradle文件中添加如下依賴:
項目根目錄的build.gradle文件:
// build.gradle(AndroidGenericFramework)
ext {
// 省略部分代碼
kotlinxCoroutinesVersion = '1.3.1'
// 省略部分代碼
}
module的build.gradle文件:
// build.gradle(:app)
dependencies {
// 省略部分代碼
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinxCoroutinesVersion"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlinxCoroutinesVersion"
// 省略部分代碼
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$kotlinxCoroutinesVersion"
// 省略部分代碼
}
- org.jetbrains.kotlinx:kotlinx-coroutines-core:協(xié)程的核心庫,它是協(xié)程的公共API,有了這一層公共代碼才能使協(xié)程在各個平臺的接口得到統(tǒng)一。
- org.jetbrains.kotlinx:kotlinx-coroutines-android:協(xié)程的當前平臺對應的平臺庫,當前平臺是Android,它是協(xié)程在具體平臺的具體實現,因為類似多線程在各個平臺的實現方式是有差異的。
- org.jetbrains.kotlinx:kotlinx-coroutines-test:協(xié)程的測試庫,它方便我們在測試中使用協(xié)程。
這里要注意的是,這三個庫的版本要保持一致。
基礎
下面是協(xié)程的基礎部分:
啟動協(xié)程
可以通過以下兩種方式來啟動協(xié)程:
- launch:可以啟動新協(xié)程,但是不將結果返回給調用方。
- async:可以啟動新協(xié)程,并且允許使用await暫停函數返回結果。
通常我們使用launch函數從常規(guī)函數啟動新協(xié)程,如果要執(zhí)行并行分解的話才使用async函數。
async函數可以返回結果,代碼如下所示:
// Builders.common.kt
public fun <T> CoroutineScope.async(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> T
): Deferred<T> {
val newContext = newCoroutineContext(context)
val coroutine = if (start.isLazy)
LazyDeferredCoroutine(newContext, block) else
DeferredCoroutine<T>(newContext, active = true)
coroutine.start(start, coroutine, block)
return coroutine
}
async函數返回的是Deferred接口,繼承Job接口,它是一個非阻塞、可取消的future。
要注意的是launch函數和async函數以不同的方式處理異常,在使用async函數時候可以調用await函數得到結果,如果出現異常將會以靜默方式拋出,也就是說不會出現在崩潰指標中,也不會在logcat中注明。
await函數是針對單個協(xié)程的,awaitAll函數是針對多個協(xié)程的,它們都能保證這些協(xié)程在返回結果之前完成。
通常協(xié)程有三種方式創(chuàng)建,如下所示:
runBlocking
使用runBlocking頂層函數來創(chuàng)建協(xié)程,這種方式是線程阻塞的,適用于單元測試,一般業(yè)務開發(fā)不會使用這種,示例代碼如下所示:
runBlocking {
login()
}
runBlocking函數源碼如下所示:
// Builders.kt
@Throws(InterruptedException::class)
// 第一個參數context是協(xié)程上下文,默認值為EmptyCoroutineContext,第二個參數是帶有CoroutineScope接受者對象,不接受任何參數返回T的Lambda表達式
public fun <T> runBlocking(context: CoroutineContext = EmptyCoroutineContext, block: suspend CoroutineScope.() -> T): T {
val currentThread = Thread.currentThread()
val contextInterceptor = context[ContinuationInterceptor]
val eventLoop: EventLoop?
val newContext: CoroutineContext
if (contextInterceptor == null) {
// 如果沒有指定調度程序(dispatcher),就創(chuàng)建或者使用私有事件循環(huán)
eventLoop = ThreadLocalEventLoop.eventLoop
newContext = GlobalScope.newCoroutineContext(context + eventLoop)
} else {
// 看看上下文(context)的攔截器(interceptor)是否是一個我們將要使用的事件循環(huán)(用來支持TestContext)或者如果存在thread-local事件循環(huán),就使用它來避免阻塞,不過它不會去新建一個
eventLoop = (contextInterceptor as? EventLoop)?.takeIf { it.shouldBeProcessedFromContext() }
?: ThreadLocalEventLoop.currentOrNull()
newContext = GlobalScope.newCoroutineContext(context)
}
val coroutine = BlockingCoroutine<T>(newContext, currentThread, eventLoop)
coroutine.start(CoroutineStart.DEFAULT, coroutine, block)
return coroutine.joinBlocking()
}
GlobalScope
使用GlobalScope單例對象,并且調用launch函數來創(chuàng)建協(xié)程,這種方式不會阻塞線程,但是不推薦在Android中使用這種方式,因為它的生命周期是整個應用程序的生命周期,如果處理不好,容易導致內存泄漏,而且不能取消,示例代碼如下所示:
GlobalScope.launch {
login()
}
GlobalScope源碼如下所示:
// CoroutineScope.kt
public object GlobalScope : CoroutineScope {
/**
* Returns [EmptyCoroutineContext].
*/
override val coroutineContext: CoroutineContext
get() = EmptyCoroutineContext
}
EmptyCoroutineContext是一個空的協(xié)程上下文。
CoroutineScope
使用CoroutineScope對象,并且調用launch函數來創(chuàng)建協(xié)程,這種方式可以通過傳入的CoroutineContext來控制協(xié)程的生命周期,推薦使用這種方式,示例代碼如下所示:
CoroutineScope(Dispatchers.IO).launch {
login()
}
Dispatchers.IO是CoroutineContext其中一種類型,下面會講到這個。
CoroutineScope可以管理一個或者多個相關的協(xié)程,可以使用它在指定范圍內啟動新協(xié)程。
與調度程序不同,CoroutineScope不運行協(xié)程。
CoroutineScope的一項重要功能就是在用戶離開你應用中的內容區(qū)域時停止執(zhí)行協(xié)程,它可以確保所有正在運行的操作都能正確停止。
CoroutineScope源碼如下所示:
// CoroutineScope.kt
// 參數block是帶有CoroutineScope接受者對象,不接受任何參數返回R的Lambda表達式
public suspend fun <R> coroutineScope(block: suspend CoroutineScope.() -> R): R =
suspendCoroutineUninterceptedOrReturn { uCont ->
val coroutine = ScopeCoroutine(uCont.context, uCont)
coroutine.startUndispatchedOrReturn(coroutine, block)
}
在Android中使用協(xié)程
在Android平臺上,協(xié)程有助于解決兩個主要問題:
- 管理長時間運行的任務,如果管理不當,這些任務可能會阻塞主線程并導致你的應用界面凍結。
- 提供主線程安全性,或者從主線程安全地調用網絡或者磁盤操作。
管理長時間運行的任務
在Android平臺上,每個應用都有一個用于處理界面并且管理用戶交互的主線程。如果你的應用為主線程分配的工作太多,會導致界面呈現速度緩慢或者界面凍結,對觸摸事件的響應速度很慢,例如:網絡請求、JSON解析、寫入或者讀取數據庫、遍歷大型列表,這些都應該在工作線程完成。
協(xié)程在常規(guī)函數的基礎上添加了兩項操作,用于處理長時間運行的任務。在invoke或者call和return之外,協(xié)程添加了suspend和resume:
- suspend用于暫停執(zhí)行當前協(xié)程,并保存所有的局部變量。
- resume用于讓已暫停的協(xié)程從其暫停處繼續(xù)執(zhí)行。
要調用suspend函數,只能從其他suspend函數進行調用,或者通過使用協(xié)程構建器(例如:launch)來啟動新的協(xié)程。
Kotlin使用堆棧幀來管理要運行哪個函數以及所有的局部變量。暫停協(xié)程時會復制并保存當前的堆棧幀以供稍后使用;恢復協(xié)程時會將堆棧幀從其保存位置復制回來,然后函數再次開始運行。
編譯器會在編譯期間對被suspend修飾符修飾的函數進行續(xù)體傳遞風格(CPS)變換,它會改變suspend函數的函數簽名,我舉個例子:
await函數是個suspend函數,函數簽名如下所示:
suspend fun <T> CompletableFuture<T>.await(): T
在編譯期間進行續(xù)體傳遞風格(CPS)變換后:
fun <T> CompletableFuture<T>.await(continuation: Continuation<T>): Any?
我們可以看到進行續(xù)體傳遞風格(CPS)變換后的函數多了一個類型為Continuation<T>的參數,Continuation代碼如下所示:
interface Continuation<in T> {
val context: CoroutineContext
fun resumeWith(result: Result<T>)
}
續(xù)體包裝了協(xié)程在掛起之后繼續(xù)執(zhí)行的代碼,在編譯過程中,一個完整的協(xié)程被分割成一個又一個續(xù)體,在await函數的掛起結束之后,它會調用參數continuation的resumeWith函數來恢復執(zhí)行await之后的代碼。
進行續(xù)體傳遞風格(CPS)變換后的函數返回值是Any?,這是因為這個函數發(fā)生變換后,它會返回一個類型為T(返回它本身)和COROUTINE_SUSPENDED標記的聯合類型,因為Kotlin沒有聯合類型語法,所以就使用最泛化的類型Any?來表示,COROUTINE_SUSPENDED標記表示的是這個suspend函數會發(fā)生事實上的掛起操作。
在下面介紹的三個調度程序,它們都會繼承CoroutineDispatcher類,源碼如下所示:
// CorountineDispatcher.kt
public abstract class CoroutineDispatcher :
AbstractCoroutineContextElement(ContinuationInterceptor), ContinuationInterceptor {
// 省略部分代碼
}
這個類實現了ContinuationInterceptor接口,源碼如下所示:
// ContinuationInterceptor.kt
@SinceKotlin("1.3")
public interface ContinuationInterceptor : CoroutineContext.Element {
// 定義上下文攔截器的鍵
companion object Key : CoroutineContext.Key<ContinuationInterceptor>
// 返回原始封裝的Continuation,從而攔截所有的恢復
public fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T>
// 初始化時,為interceptContinuation返回的Continuation實例調用
public fun releaseInterceptedContinuation(continuation: Continuation<*>) {
/* do nothing by default */
}
public override operator fun <E : CoroutineContext.Element> get(key: CoroutineContext.Key<E>): E? {
// getPolymorphicKey專門用于ContinuationInterceptor的鍵
@OptIn(ExperimentalStdlibApi::class)
if (key is AbstractCoroutineContextKey<*, *>) {
@Suppress("UNCHECKED_CAST")
return if (key.isSubKey(this.key)) key.tryCast(this) as? E else null
}
@Suppress("UNCHECKED_CAST")
return if (ContinuationInterceptor === key) this as E else null
}
public override fun minusKey(key: CoroutineContext.Key<*>): CoroutineContext {
// minusPolymorphicKey專門用于ContinuationInterceptor的鍵
@OptIn(ExperimentalStdlibApi::class)
if (key is AbstractCoroutineContextKey<*, *>) {
return if (key.isSubKey(this.key) && key.tryCast(this) != null) EmptyCoroutineContext else this
}
return if (ContinuationInterceptor === key) EmptyCoroutineContext else this
}
}
這個接口叫做續(xù)體攔截器,它負責攔截協(xié)程在恢復后執(zhí)行的代碼(也就是續(xù)體),并且將其在指定線程或者線程池恢復。
在編譯期間,每個suspend函數會被編譯成實現了Continuation接口的匿名類,其實調用suspend函數時,并不一定會掛起協(xié)程,舉個例子:有個網絡請求的邏輯調用了await函數,如果網絡請求還沒得到結果,那么協(xié)程就會被掛起,直到得到結果為止,續(xù)體攔截器只會攔截發(fā)生掛起后的掛起點的續(xù)體,而對于沒發(fā)生掛起的掛起點,協(xié)程會調用resumeWith函數,而不再需要續(xù)體攔截器處理。
續(xù)體攔截器會緩存攔截過的續(xù)體,并且在不需要它的時候調用releaseInterceptedContinuation函數釋放。
使用協(xié)程確保主線程安全
Kotlin協(xié)程使用調度程序來確定哪些線程用于執(zhí)行協(xié)程,所有協(xié)程都必須在調度程序中運行,協(xié)程可以可以暫停,而調度程序負責將其恢復。
Kotlin提供了三個調度程序,可以使用它們來指定應在何處運行協(xié)程:
- Dispatchers.Main:使用此調度程序可在Android主線程上運行協(xié)程,只能用于界面交互和執(zhí)行快速工作,例如:調用suspend函數、運行Android界面框架操作和更新LiveData對象。
- Dispatchers.Default:此調度程序適合在主線程之外執(zhí)行占用大量CPU資源的工作,例如:對列表排序和解析JSON。
- Dispatchers.IO:此調度程序適合在主線程之外執(zhí)行磁盤或者網絡I/O,例如:操作數據庫(使用Room)、向文件中寫入數據或者從文件中讀取數據和運行任何網絡操作。
我們可以調用withContext函數,并且傳入相應的協(xié)程上下文(CoroutineContext)就可以調度線程。
withContext函數是個suspend函數,它可以在不引用回調的情況下控制任何代碼行的線程池,因此可以將其應用于非常小的函數,示例代碼如下所示:
suspend fun getUserInfo() { // Dispatchers.Main
val data = fetchUserInfo() // Dispatchers.Main
show(data) // Dispatchers.Main
}
suspend fun fetchUserInfo() { // Dispatchers.Main
withContext(Dispatchers.IO) { // Dispatchers.IO
// 執(zhí)行網絡請求 // Dispatchers.IO
} // Dispatchers.Main
}
在示例代碼中,getUserInfo函數在主線程上執(zhí)行,它可以安全地調用fetchUserInfo函數,在工作線程中執(zhí)行網絡請求,并且掛起,在withContext代碼塊執(zhí)行完成后,主線程上的協(xié)程就會根據fetchUserInfo函數的結果恢復后面的邏輯。
相比于回調實現,使用withContext函數不會增加額外的開銷,在某些情況下,甚至優(yōu)于回調實現,例如:某個函數執(zhí)行了很多次的網絡請求,使用外部withContext函數可以讓Kotlin停留在同一個調度程序,并且只切換一次線程,此外,Kotlin還優(yōu)化了Dispatchers.Default和Dispatchers.IO之間的切換,以盡可能避免線程切換。
要注意的是,Dispatchers.Default和Dispatchers.IO都是使用線程池的調度程序,它們不能保證代碼塊在同一線程從上到下執(zhí)行,因為在某些情況下,Kotlin會在掛起和恢復后,將執(zhí)行工作移交給另外一個線程,這意味著,對于整個withContext代碼塊,線程局部變量并不指向同一個值。
Dispatchers.Main
源碼如下所示:
// Dispatchers.kt
public actual object Dispatchers {
// 省略部分代碼
@JvmStatic
public actual val Main: MainCoroutineDispatcher get() = MainDispatcherLoader.dispatcher
// 省略部分代碼
}
然后看下MainDispatcherLoader.dispatcher,源碼如下所示:
// MainDispatchers.kt
internal object MainDispatcherLoader {
// 省略部分代碼
@JvmField
val dispatcher: MainCoroutineDispatcher = loadMainDispatcher()
// 省略部分代碼
}
變量dispatcher為MainCoroutineDispatcher類型,MainCoroutineDispatcher是個抽象類,然后它的其中一個實現類是包裝類(sealed class)HandlerDispatcher,也就是它的子類肯定是在這個類的所在的文件中,然后我找到HandlerContext這個類,這個類是HandlerDispatcher的唯一子類,源碼如下所示:
// MainCoroutineDispatcher.kt
internal class HandlerContext private constructor(
private val handler: Handler,
private val name: String?,
private val invokeImmediately: Boolean
) : HandlerDispatcher(), Delay {
// HandlerContext的構造函數,參數handler為要傳進來的Handler,參數name為用于調試的可選名稱
public constructor(
handler: Handler,
name: String? = null
) : this(handler, name, false)
@Volatile
private var _immediate: HandlerContext? = if (invokeImmediately) this else null
override val immediate: HandlerContext = _immediate ?:
HandlerContext(handler, name, true).also { _immediate = it }
// 判斷是否需要調度,參數context為CoroutineContext
override fun isDispatchNeeded(context: CoroutineContext): Boolean {
// 判斷invokeImmediately的值或者是否是同一個線程
return !invokeImmediately || Looper.myLooper() != handler.looper
}
// 調度線程,參數context為CoroutineContext,參數block為Runnable
override fun dispatch(context: CoroutineContext, block: Runnable) {
// 調用Handler的post方法,將Runnable添加到消息隊列中,這個Runnable會在這個Handler附加在線程上的時候運行
handler.post(block)
}
override fun scheduleResumeAfterDelay(timeMillis: Long, continuation: CancellableContinuation<Unit>) {
val block = Runnable {
with(continuation) { resumeUndispatched(Unit) }
}
// 調用Handler的postDelayed方法,將Runnable添加到消息隊列中,并且在指定的時間結束后運行
handler.postDelayed(block, timeMillis.coerceAtMost(MAX_DELAY))
continuation.invokeOnCancellation { handler.removeCallbacks(block) }
}
override fun invokeOnTimeout(timeMillis: Long, block: Runnable): DisposableHandle {
// 調用Handler的postDelayed方法,將Runnable添加到消息隊列中,并且在指定的時間結束后運行
handler.postDelayed(block, timeMillis.coerceAtMost(MAX_DELAY))
return object : DisposableHandle {
override fun dispose() {
// 調用Handler的removeCallbacks方法,刪除消息隊列中的Runnable
handler.removeCallbacks(block)
}
}
}
override fun toString(): String =
if (name != null) {
if (invokeImmediately) "$name [immediate]" else name
} else {
handler.toString()
}
override fun equals(other: Any?): Boolean = other is HandlerContext && other.handler === handler
override fun hashCode(): Int = System.identityHashCode(handler)
}
然后我們找下調用HandlerContext的構造函數的地方,源碼如下所示:
// HandlerDispatcher.kt
@JvmField
@Deprecated("Use Dispatchers.Main instead", level = DeprecationLevel.HIDDEN)
internal val Main: HandlerDispatcher? = runCatching { HandlerContext(Looper.getMainLooper().asHandler(async = true), "Main") }.getOrNull()
可以看到傳入了Looper.getMainLooper方法,也就是應用程序的主循環(huán)程序(Main Looper),它位于應用程序的主線程中。
可以看到使用了很多Handler相關的方法,也就是它還是依賴于Android的消息機制。
Dispatchers.Default
源碼如下所示:
// Dispatchers.kt
public actual object Dispatchers {
// 省略部分代碼
@JvmStatic
public actual val Default: CoroutineDispatcher = createDefaultDispatcher()
// 省略部分代碼
}
然后看下createDefaultDispatcher函數,源碼如下所示:
// CoroutineContext.kt
internal actual fun createDefaultDispatcher(): CoroutineDispatcher =
if (useCoroutinesScheduler) DefaultScheduler else CommonPool
這里會根據內部變量(internal val)useCoroutinesScheduler判斷返回是DefaultScheduler還是CommonPool,useCoroutinesScheduler源碼如下所示:
// CoroutineContext.kt
internal const val COROUTINES_SCHEDULER_PROPERTY_NAME = "kotlinx.coroutines.scheduler"
internal val useCoroutinesScheduler = systemProp(COROUTINES_SCHEDULER_PROPERTY_NAME).let { value ->
when (value) {
null, "", "on" -> true
"off" -> false
else -> error("System property '$COROUTINES_SCHEDULER_PROPERTY_NAME' has unrecognized value '$value'")
}
}
這個內部變量(internal val)useCoroutinesScheduler是根據JVM的System.getProperty方法獲取的,通過傳入"kotlinx.coroutines.scheduler"作為鍵(key),返回的值為on,useCoroutinesScheduler為true;返回的值是off,useCoroutinesScheduler為false。
先看下DefaultScheduler這種情況,源碼如下所示:
// Dispatcher.kt
internal object DefaultScheduler : ExperimentalCoroutineDispatcher() {
val IO = blocking(systemProp(IO_PARALLELISM_PROPERTY_NAME, 64.coerceAtLeast(AVAILABLE_PROCESSORS)))
override fun close() {
throw UnsupportedOperationException("$DEFAULT_SCHEDULER_NAME cannot be closed")
}
override fun toString(): String = DEFAULT_SCHEDULER_NAME
@InternalCoroutinesApi
@Suppress("UNUSED")
public fun toDebugString(): String = super.toString()
}
它繼承ExperimentalCoroutineDispatcher類,它是個不穩(wěn)定的類,以后可能會改變,可以看下這個類的dispatch函數,這個函數負責調度線程,源碼如下所示:
// Dispatcher.kt
@InternalCoroutinesApi
open class ExperimentalCoroutineDispatcher(
// 核心線程數
private val corePoolSize: Int,
// 最大線程數
private val maxPoolSize: Int,
// 調度器保持存活的時間(單位:納秒)
private val idleWorkerKeepAliveNs: Long,
// 調度器名字
private val schedulerName: String = "CoroutineScheduler"
) : ExecutorCoroutineDispatcher() {
constructor(
corePoolSize: Int = CORE_POOL_SIZE,
maxPoolSize: Int = MAX_POOL_SIZE,
schedulerName: String = DEFAULT_SCHEDULER_NAME
) : this(corePoolSize, maxPoolSize, IDLE_WORKER_KEEP_ALIVE_NS, schedulerName)
@Deprecated(message = "Binary compatibility for Ktor 1.0-beta", level = DeprecationLevel.HIDDEN)
constructor(
corePoolSize: Int = CORE_POOL_SIZE,
maxPoolSize: Int = MAX_POOL_SIZE
) : this(corePoolSize, maxPoolSize, IDLE_WORKER_KEEP_ALIVE_NS)
// 省略部分代碼
override fun dispatch(context: CoroutineContext, block: Runnable): Unit =
try {
// 調用了coroutineScheduler的dispatch函數
coroutineScheduler.dispatch(block)
} catch (e: RejectedExecutionException) {
DefaultExecutor.dispatch(context, block)
}
// 省略部分代碼
}
看下CoroutineScheduler這個類,然后再看下它的dispatch函數,源碼如下所示:
// CoroutineScheduler.kt
@Suppress("NOTHING_TO_INLINE")
internal class CoroutineScheduler(
// 核心線程數
@JvmField val corePoolSize: Int,
// 最大線程數
@JvmField val maxPoolSize: Int,
// 調度器保持存活的時間(單位:納秒)
@JvmField val idleWorkerKeepAliveNs: Long = IDLE_WORKER_KEEP_ALIVE_NS,
// 調度器名字
@JvmField val schedulerName: String = DEFAULT_SCHEDULER_NAME
) : Executor, Closeable {
init {
require(corePoolSize >= MIN_SUPPORTED_POOL_SIZE) {
"Core pool size $corePoolSize should be at least $MIN_SUPPORTED_POOL_SIZE"
}
require(maxPoolSize >= corePoolSize) {
"Max pool size $maxPoolSize should be greater than or equals to core pool size $corePoolSize"
}
require(maxPoolSize <= MAX_SUPPORTED_POOL_SIZE) {
"Max pool size $maxPoolSize should not exceed maximal supported number of threads $MAX_SUPPORTED_POOL_SIZE"
}
require(idleWorkerKeepAliveNs > 0) {
"Idle worker keep alive time $idleWorkerKeepAliveNs must be positive"
}
}
// 省略部分代碼
fun dispatch(block: Runnable, taskContext: TaskContext = NonBlockingContext, tailDispatch: Boolean = false) {
// 用于支持虛擬時間
trackTask()
val task = createTask(block, taskContext)
// 嘗試將任務提交到本地隊列,并且根據結果采取執(zhí)行相關的邏輯
val currentWorker = currentWorker()
val notAdded = currentWorker.submitToLocalQueue(task, tailDispatch)
if (notAdded != null) {
if (!addToGlobalQueue(notAdded)) {
// 全局隊列在最后一步關閉時不應該接受更多的任務
throw RejectedExecutionException("$schedulerName was terminated")
}
}
val skipUnpark = tailDispatch && currentWorker != null
if (task.mode == TASK_NON_BLOCKING) {
if (skipUnpark) return
// 執(zhí)行任務
signalCpuWork()
} else {
// 增加阻塞任務
signalBlockingWork(skipUnpark = skipUnpark)
}
}
// 省略部分代碼
}
可以看到CoroutineScheduler實現了Executor接口,在Java中線程池的核心實現類是ThreadPoolExecutor類,它也是實現了Executor接口,所以這個CoroutineScheduler是協(xié)程中線程池的一種實現。
corePoolSize是核心線程數量,它是通過調用JVM的Runtime.getRuntime().availableProcessors()方法得到當前處理器可運行的線程數,它的缺省值強制設置為至少兩個線程。
maxPoolSize是最大線程數量,最小值為corePoolSize,最大值為(1 shl BLOCKING_SHIFT) - 2,BLOCKING_SHIFT為21,也就是1向左位移21位再減去2,確保Runtime.getRuntime().availableProcessors()得到的值再乘以2在最小值和最大值之間。
這個函數做的事情就是將傳入的任務壓入任務棧,然后調用signalCpuWork執(zhí)行任務或者調用signalBlockingWork來增加阻塞任務。
然后再看下另外一種情況:CommonPool,源碼如下所示:
// CommonPool.kt
internal object CommonPool : ExecutorCoroutineDispatcher() {
// 省略部分代碼
private fun createPool(): ExecutorService {
if (System.getSecurityManager() != null) return createPlainPool()
// ForkJoinPool類的反射,方便它在JDK6上可以運行(這里沒有),如果沒有就使用普通線程池
val fjpClass = Try { Class.forName("java.util.concurrent.ForkJoinPool") }
?: return createPlainPool()
// 嘗試使用commonPool,除非顯式指定了并行性或者在調試privatePool模式
if (!usePrivatePool && requestedParallelism < 0) {
Try { fjpClass.getMethod("commonPool")?.invoke(null) as? ExecutorService }
?.takeIf { isGoodCommonPool(fjpClass, it) }
?.let { return it }
}
// 嘗試創(chuàng)建私有ForkJoinPool實例
Try { fjpClass.getConstructor(Int::class.java).newInstance(parallelism) as? ExecutorService }
?. let { return it }
// 使用普通線城市
return createPlainPool()
}
// 省略部分代碼
// 創(chuàng)建普通線程池
private fun createPlainPool(): ExecutorService {
val threadId = AtomicInteger()
// 使用Java的newFixedThreadPool線程池
return Executors.newFixedThreadPool(parallelism) {
Thread(it, "CommonPool-worker-${threadId.incrementAndGet()}").apply { isDaemon = true }
}
}
// 省略部分代碼
// 調度線程
override fun dispatch(context: CoroutineContext, block: Runnable) {
try {
(pool ?: getOrCreatePoolSync()).execute(wrapTask(block))
} catch (e: RejectedExecutionException) {
unTrackTask()
DefaultExecutor.enqueue(block)
}
}
// 省略部分代碼
}
可以看到使用CommonPool,其實就是使用Java的newFixedThreadPool線程池。
Dispatchers.Default調度器的核心線程池和處理器的線程數是相等的,因此它可以用于處理密集型計算,適合在主線程之外執(zhí)行占用大量CPU資源的工作,例如:對列表排序和解析JSON,和RxJava的計算線程池的思想有點類似。
Dispatchers.IO
源碼如下所示:
// Dispatchers.kt
public actual object Dispatchers {
// 省略部分代碼
@JvmStatic
public val IO: CoroutineDispatcher = DefaultScheduler.IO
}
可以看到IO其實是DefaultScheduler的一個成員變量,源碼如下所示:
internal object DefaultScheduler : ExperimentalCoroutineDispatcher() {
// 調用了父類ExperimentalCoroutineDispatcher的blocking函數
val IO = blocking(systemProp(IO_PARALLELISM_PROPERTY_NAME, 64.coerceAtLeast(AVAILABLE_PROCESSORS)))
override fun close() {
throw UnsupportedOperationException("$DEFAULT_SCHEDULER_NAME cannot be closed")
}
override fun toString(): String = DEFAULT_SCHEDULER_NAME
@InternalCoroutinesApi
@Suppress("UNUSED")
public fun toDebugString(): String = super.toString()
}
可以看下它的父類ExperimentalCoroutineDispatcher的blocking函數,源碼如下所示:
// Dispatcher.kt
@InternalCoroutinesApi
open class ExperimentalCoroutineDispatcher(
// 核心線程數
private val corePoolSize: Int,
// 最大線程數
private val maxPoolSize: Int,
// 調度器保持存活的時間(單位:納秒)
private val idleWorkerKeepAliveNs: Long,
// 調度器名字
private val schedulerName: String = "CoroutineScheduler"
) : ExecutorCoroutineDispatcher() {
constructor(
corePoolSize: Int = CORE_POOL_SIZE,
maxPoolSize: Int = MAX_POOL_SIZE,
schedulerName: String = DEFAULT_SCHEDULER_NAME
) : this(corePoolSize, maxPoolSize, IDLE_WORKER_KEEP_ALIVE_NS, schedulerName)
@Deprecated(message = "Binary compatibility for Ktor 1.0-beta", level = DeprecationLevel.HIDDEN)
constructor(
corePoolSize: Int = CORE_POOL_SIZE,
maxPoolSize: Int = MAX_POOL_SIZE
) : this(corePoolSize, maxPoolSize, IDLE_WORKER_KEEP_ALIVE_NS)
// 省略部分代碼
public fun blocking(parallelism: Int = BLOCKING_DEFAULT_PARALLELISM): CoroutineDispatcher {
require(parallelism > 0) { "Expected positive parallelism level, but have $parallelism" }
// 創(chuàng)建LimitingDispatcher對象
return LimitingDispatcher(this, parallelism, TASK_PROBABLY_BLOCKING)
}
// 省略部分代碼
}
看下LimitingDispatcher類,源碼如下所示:
// Dispatcher.kt
private class LimitingDispatcher(
// final變量dispatcher為ExperimentalCoroutineDispatcher類型
val dispatcher: ExperimentalCoroutineDispatcher,
val parallelism: Int,
override val taskMode: Int
) : ExecutorCoroutineDispatcher(), TaskContext, Executor {
// 省略部分代碼
// 調度線程,調用dispatch(block: Runnable, tailDispatch: Boolean)函數
override fun dispatch(context: CoroutineContext, block: Runnable) = dispatch(block, false)
private fun dispatch(block: Runnable, tailDispatch: Boolean) {
var taskToSchedule = block
while (true) {
// 提交正在執(zhí)行的任務槽
val inFlight = inFlightTasks.incrementAndGet()
// 快速路徑,如果沒有達到并行性限制,就會分派任務并且返回
if (inFlight <= parallelism) {
// 調用ExperimentalCoroutineDispatcher的dispatchWithContext函數
dispatcher.dispatchWithContext(taskToSchedule, this, tailDispatch)
return
}
// 達到并行性限制后就將任務添加到隊列中
queue.add(taskToSchedule)
if (inFlightTasks.decrementAndGet() >= parallelism) {
return
}
taskToSchedule = queue.poll() ?: return
}
}
// 省略部分代碼
}
可以看到其實Dispatchers.Default調度器和Dispatchers.IO調度器是共用同一個線程池的。
指定CoroutineScope
在定義協(xié)程時,必須指定其CoroutineScope,CoroutineScope可以管理一個或者多個相關的協(xié)程,可以使用它在指定范圍內啟動新協(xié)程。
與調度程序不同,CoroutineScope不運行協(xié)程。
CoroutineScope的一項重要功能就是在用戶離開應用中內容區(qū)域時停止執(zhí)行協(xié)程,可以確保所有正在運行的操作都能正確停止。
在Android平臺上,可以將CoroutineScope實現與組件中生命周期相關聯,例如:Lifecycle和ViewModel,這樣可以避免內存泄漏和不再對與用戶相關的Activity或者Fragment執(zhí)行額外的工作,例如:ViewModelScope、LifecycleScope和liveData。
添加KTX依賴項
- 對于ViewModelScope,請使用androidx.lifecycle:lifecycle-viewmodel-ktx:2.1.0-beta01或更高版本。
- 對于LifecycleScope,請使用androidx.lifecycle:lifecycle-runtime-ktx:2.2.0-alpha01或更高版本。
- 對于liveData,請使用androidx.lifecycle:lifecycle-livedata-ktx:2.2.0-alpha01或更高版本。
生命周期感知型CoroutineScope
ViewModelScope
為ViewModel定義ViewModelScope,如果ViewModel已清除,那么在這個范圍內的啟動的協(xié)程就會自動取消,如果你的工作需要在ViewModel處于活動狀態(tài)下才能完成的話,可以使用它,示例代碼如下所示:
class MyViewModel : ViewModel() {
init {
viewModelScope.launch {
// 當ViewModel被清除,這個范圍內啟動的協(xié)程就會自動取消
}
}
}
LifecycleScope
為每個Lifecycle對象定義LifecycleScope,在這個范圍內啟動的協(xié)程會在Lifecycle被銷毀的時候自動取消,可以通過lifecycle.coroutineScope或者lifecycleOwner.lifecycleScope屬性訪問Lifecycle的CoroutineScope,示例代碼如下所示:
class MyFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewLifecycleOwner.lifecycleScope.launch {
// 在這個范圍內啟動的協(xié)程會在Lifecycle被銷毀的時候自動取消
}
}
}
即使CoroutineScope提供了適當的方法來自動取消長時間運行的操作,在某些情況下,可能需要暫停執(zhí)行代碼塊,例如:要使用FragmentTransaction,Fragment的生命周期至少處于STARTED狀態(tài),對于這種情況,Lifecycle提供了這些方法:lifecycle.whenCreated、lifecycle.whenStarted和lifecycle.whenResumed,如果Lifecycle未至少處于所需的最低狀態(tài),那么就會暫停在這些代碼塊內運行的任何協(xié)程,示例代碼如下所示:
class MyFragment : Fragment() {
init {
// 在Fragment的構造函數可以安全地啟動
lifecycleScope.launch {
whenCreateed {
// 只有當Fragment的生命周期至少處于CREATED狀態(tài)下,這個代碼塊才會執(zhí)行,并且可以調用其他suspend函數
}
whenStarted {
// 只有當Fragment的生命周期至少處于STARTED狀態(tài)下,這個代碼塊才會執(zhí)行,并且可以調用其他suspend函數
}
whenResumed {
// 只有當Fragment的生命周期至少處于RESUMED狀態(tài)下,這個代碼塊才會執(zhí)行,并且可以調用其他suspend函數
}
}
}
}
liveData
使用LiveData時,我們可能需要異步計算值,例如:獲取了用戶信息后顯示到界面,在這種情況下,我們可以使用liveData構建器函數調用suspend函數,并且將結果作為LiveData對象返回,示例代碼如下所示:
val userInfoData: LiveData<UserInfoData> = liveData {
// getUserInfo函數是一個suspend函數
val data = remoteSource.getUserInfo()
emit(data)
}
liveData構建塊用作協(xié)程和LiveData之間的結構化并發(fā)基元。
當LiveData變?yōu)?strong>活動狀態(tài)的時候,代碼塊開始執(zhí)行;當LiveData變?yōu)?strong>非活動狀態(tài)的時候,代碼塊會在可配置的超時過后自動取消。如果代碼塊在完成前取消,則會在LiveData再次變成活動狀態(tài)后重啟;如果在上次運行中成功完成,則不會重啟。要注意的是,代碼塊只有在自動取消的情況下才會重啟,如果代碼塊由于任何其他原因(例如:拋出CancelationException)而取消,則不會重啟。
我們可以從代碼塊中發(fā)出多個值,每次調用emit函數都會暫停執(zhí)行代碼塊,直到在主線程上設置LiveData值。
我的GitHub:TanJiaJunBeyond
Android通用框架:Android通用框架
我的掘金:譚嘉俊
我的簡書:譚嘉俊
我的CSDN:譚嘉俊