Kotlin 基礎(chǔ)精華篇
Kotlin 內(nèi)聯(lián)函數(shù)let、with、run乖订、apply、also
Kotlin 協(xié)程學(xué)習(xí)總結(jié)
一具练、協(xié)程的使用與說(shuō)明
val job = GlobalScope.launch(
context = Dispatchers.Default,
start = CoroutineStart.DEFAULT,
block = {
val result1 = doTask("1", 2000)
val result2 = async { doTask("1", 2000) }
withContext(Dispatchers.Main) {
logMessage("result is ${result1}, ${result2.await()}")
}
}
)
launch源碼:
public fun CoroutineScope.launch(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
): Job {
val newContext = newCoroutineContext(context)
val coroutine = if (start.isLazy)
LazyStandaloneCoroutine(newContext, block) else
StandaloneCoroutine(newContext, active = true)
coroutine.start(start, coroutine, block)
return coroutine
}
1乍构、CoroutineScope
協(xié)程范圍,即協(xié)程內(nèi)的代碼運(yùn)行的時(shí)間周期范圍扛点,如果超出了指定的協(xié)程范圍哥遮,協(xié)程會(huì)被取消執(zhí)行。
CoroutineScope
協(xié)程范圍接口
CoroutineContext:協(xié)程的上下文
public interface CoroutineScope {
/**
* The context of this scope.
* Context is encapsulated by the scope and used for implementation of coroutine builders that are extensions on the scope.
* Accessing this property in general code is not recommended for any purposes except accessing the [Job] instance for advanced usages.
*
* By convention, should contain an instance of a [job][Job] to enforce structured concurrency.
*/
public val coroutineContext: CoroutineContext
}
ContextScope
協(xié)程范圍實(shí)現(xiàn)類
internal class ContextScope(context: CoroutineContext) : CoroutineScope
GlobalScope
全局作用域陵究,實(shí)現(xiàn)了 CoroutineScope接口 同時(shí)執(zhí)行了一個(gè)空的上下文對(duì)象的協(xié)程作用域眠饮。
其創(chuàng)建的協(xié)程沒有父協(xié)程,通常用于啟動(dòng)頂級(jí)協(xié)程铜邮,這些協(xié)程在整個(gè)應(yīng)用程序生命周期內(nèi)運(yùn)行仪召。
直接使用 GlobalScope 的 async 或者 launch 方法是強(qiáng)烈不建議的。程序代碼通常應(yīng)該使用自定義的協(xié)程作用域牲距。
MainScope
實(shí)現(xiàn)了 CoroutineScope接口 同時(shí)是通過調(diào)度器調(diào)度到了主線程的協(xié)程作用域
public fun MainScope(): CoroutineScope = ContextScope(SupervisorJob() + Dispatchers.Main)
如:
class TestLifecycleActivity : AppCompatActivity(), CoroutineScope by MainScope() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.test_fargment)
testCoroutine()
}
private fun testCoroutine() {
logMessage("launchBefore")
launch {
//do task
logMessage("taskBefore")
val result1 = async { doTask("1", 20 * 1000) }
val result2 = async { doTask("2", 20 * 1000) }
val result3 = doTask("3", result1.await() + result2.await())
logMessage("taskAfter")
//切換主線程輸出結(jié)果
withContext(Dispatchers.Main) {
logMessage("result is ${result1.await()}, ${result2.await()}, $result3")
}
}
logMessage("launchAfter")
}
override fun onDestroy() {
super.onDestroy()
logMessage("onDestroy - cancel")
cancel()
}
//...
}
日志輸出:
E/Coroutine: 16:49:50 983 *** ThreadName : main *** launchBefore
E/Coroutine: 16:49:50 994 *** ThreadName : main *** launchAfter
E/Coroutine: 16:49:51 243 *** ThreadName : main *** taskBefore
E/Coroutine: 16:50:02 029 *** ThreadName : main *** onDestroy - cancel
自定義CoroutineScope
private val customScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
viewModelScope
添加如下依賴:
def lifecycle_version = "2.2.0"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
使用:
class TestViewModel : androidx.lifecycle.ViewModel() {
fun testCoroutine() {
viewModelScope.launch {
//do task
}
}
}
而viewModelScope是怎么定義的呢
/**
* [CoroutineScope] tied to this [ViewModel].
* This scope will be canceled when ViewModel will be cleared, i.e [ViewModel.onCleared] is called
*
* This scope is bound to
* [Dispatchers.Main.immediate][kotlinx.coroutines.MainCoroutineDispatcher.immediate]
*/
val ViewModel.viewModelScope: CoroutineScope
get() {
val scope: CoroutineScope? = this.getTag(JOB_KEY)
if (scope != null) {
return scope
}
return setTagIfAbsent(JOB_KEY,
CloseableCoroutineScope(SupervisorJob() + Dispatchers.Main.immediate))
}
lifecycleScope
添加如下依賴:
def lifecycle_version = "2.2.0"
implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version"
使用:
lifecycleScope.launch { }
lifecycleScope.launchWhenCreated { }
lifecycleScope.launchWhenResumed { }
lifecycleScope.launchWhenStarted { }
viewModelScope是怎么定義的呢
/**
* [CoroutineScope] tied to this [LifecycleOwner]'s [Lifecycle].
*
* This scope will be cancelled when the [Lifecycle] is destroyed.
*
* This scope is bound to
* [Dispatchers.Main.immediate][kotlinx.coroutines.MainCoroutineDispatcher.immediate].
*/
val LifecycleOwner.lifecycleScope: LifecycleCoroutineScope
get() = lifecycle.coroutineScope
/**
* [CoroutineScope] tied to this [Lifecycle].
*
* This scope will be cancelled when the [Lifecycle] is destroyed.
*
* This scope is bound to
* [Dispatchers.Main.immediate][kotlinx.coroutines.MainCoroutineDispatcher.immediate]
*/
val Lifecycle.coroutineScope: LifecycleCoroutineScope
get() {
while (true) {
val existing = mInternalScopeRef.get() as LifecycleCoroutineScopeImpl?
if (existing != null) {
return existing
}
val newScope = LifecycleCoroutineScopeImpl(
this,
SupervisorJob() + Dispatchers.Main.immediate
)
if (mInternalScopeRef.compareAndSet(null, newScope)) {
newScope.register()
return newScope
}
}
}
2返咱、launch
是CoroutineScope的一個(gè)擴(kuò)展函數(shù)。
默認(rèn)啟動(dòng)通過launch啟動(dòng)一個(gè)協(xié)程的時(shí)候包含一個(gè)繼承自作用域的CoroutineContext牍鞠,和一個(gè)默認(rèn)的啟動(dòng)模式,調(diào)度器和要執(zhí)行的協(xié)程體评姨,之后返回一個(gè)Job难述。同時(shí)內(nèi)部的job將成為外部job 的子job,當(dāng)一個(gè)父協(xié)程被取消的時(shí)候吐句,所有它的子協(xié)程也會(huì)被遞歸的取消胁后。
lauch 與 runBlocking 都能 開啟一個(gè)協(xié)程,但 lauch 是非阻塞的嗦枢,runBlocking 是阻塞的攀芯。
runBlocking中調(diào)用launch()會(huì)在當(dāng)前線程中執(zhí)行協(xié)程,也就是說(shuō)在runBlocking中不管開啟多少個(gè)子協(xié)程文虏,實(shí)際上它們都是使用runBlocking所在的線程執(zhí)行任務(wù)侣诺,所以會(huì)出現(xiàn)線程被霸占的情況殖演。
runBlocking {
GlobalScope.launch {
//do task
logMessage("taskBefore")
val result1 = async { doTask("1", 1000) }
val result2 = async { doTask("2", 1000) }
val result3 = doTask("3", result1.await() + result2.await())
logMessage("taskAfter")
}
}
日志輸出:(都在main線程順序執(zhí)行)
E/Coroutine: 19:42:37 140 *** ThreadName : main *** launchBefore
E/Coroutine: 19:42:37 157 *** ThreadName : main *** taskBefore
E/Coroutine: 19:42:38 164 *** ThreadName : main *** task1 - 1000
E/Coroutine: 19:42:38 166 *** ThreadName : main *** task2 - 1000
E/Coroutine: 19:42:40 168 *** ThreadName : main *** task3 - 2000
E/Coroutine: 19:42:40 168 *** ThreadName : main *** taskAfter
3、CoroutineContext
協(xié)程上下文年鸳,可以指定協(xié)程運(yùn)行的線程趴久。
默認(rèn)與指定的CoroutineScope中的coroutineContext保持一致,比如GlobalScope默認(rèn)運(yùn)行在一個(gè)后臺(tái)工作線程內(nèi)搔确。也可以通過顯示指定參數(shù)來(lái)更改協(xié)程運(yùn)行的線程彼棍,Dispatchers提供了幾個(gè)值可以指定:Dispatchers.Default、Dispatchers.Main膳算、Dispatchers.IO座硕、Dispatchers.Unconfined。
Dispatchers.Default
CPU密集型任務(wù)涕蜂,如列表排序华匾、JSON轉(zhuǎn)換等
Dispatchers.Main
主線程,和UI交互宇葱,執(zhí)行輕量任務(wù)
Dispatchers.IO
常用于網(wǎng)絡(luò)請(qǐng)求和文件訪問
Dispatchers.Unconfined
不限制任何制定線程瘦真,一般不用
4、CoroutineStart
協(xié)程的啟動(dòng)模式黍瞧。
默認(rèn)CoroutineStart.DEFAULT是指協(xié)程立即執(zhí)行诸尽,除此之外還有CoroutineStart.LAZY、CoroutineStart.ATOMIC印颤、CoroutineStart.UNDISPATCHED您机。
CoroutineStart.DEFAULT
立即執(zhí)行協(xié)程體
CoroutineStart.LAZY
懶漢式啟動(dòng)。launch后并不會(huì)有任何調(diào)度行為年局,協(xié)程體也不會(huì)進(jìn)入執(zhí)行狀態(tài)际看,直到需要它執(zhí)行的時(shí)候,即需要它的運(yùn)行結(jié)果的時(shí)候矢否,launch調(diào)用后會(huì)返回一個(gè) Job實(shí)例:
調(diào)用 Job.start仲闽,主動(dòng)觸發(fā)協(xié)程的調(diào)度執(zhí)行
調(diào)用 Job.join,隱式的觸發(fā)協(xié)程的調(diào)度執(zhí)行
CoroutineStart.ATOMIC
立即執(zhí)行協(xié)程體僵朗,但在開始運(yùn)行后無(wú)法取消赖欣,無(wú)視 job.cancel()
CoroutineStart.UNDISPATCHED
立即在當(dāng)前線程執(zhí)行協(xié)程體,直到第一個(gè) suspend 調(diào)用验庙。
協(xié)程在這種模式下會(huì)直接開始在當(dāng)前線程下執(zhí)行顶吮,直到第一個(gè)掛起點(diǎn),這聽起來(lái)有點(diǎn)兒像前面的 ATOMIC粪薛,不同之處在于 UNDISPATCHED 不經(jīng)過任何調(diào)度器即開始執(zhí)行協(xié)程體悴了。當(dāng)然遇到掛起點(diǎn)之后的執(zhí)行就取決于掛起點(diǎn)本身的邏輯以及上下文當(dāng)中的調(diào)度器了。
5、block
協(xié)程主體湃交,也就是要在協(xié)程內(nèi)部運(yùn)行的代碼熟空。
6、Job
返回值巡揍,對(duì)當(dāng)前創(chuàng)建的協(xié)程的引用痛阻。可以通過Job的start腮敌、cancel阱当、join等方法來(lái)控制協(xié)程的啟動(dòng)和取消。
isActive 協(xié)程是否存活(注意懶啟動(dòng))
isCancelled 協(xié)程是否取消
isCompleted 協(xié)程是否完成
cancel() 取消協(xié)程
start() 啟動(dòng)協(xié)程
join() 阻塞等候協(xié)程完成
cancelAndJoin() 取消并等候協(xié)程完成handler: CompletionHandler) 監(jiān)聽協(xié)程的狀態(tài)回調(diào)
attachChild(child: ChildJob) 附加一個(gè)子協(xié)程到當(dāng)前協(xié)程上
7糜工、async弊添、await
是CoroutineScope的一個(gè)擴(kuò)展函數(shù),用于開啟一個(gè)新的子協(xié)程捌木,與 launch 函數(shù)一樣可以設(shè)置啟動(dòng)模式镣丑,不同的是它的返回值為 Deferred章喉,Deferred是Job的子類唬滑,但是通過Deferred.await()可以得到一個(gè)返回值它掂。
await() 只有在 async 未執(zhí)行完成返回結(jié)果時(shí),才會(huì)掛起協(xié)程帆啃。若 async 已經(jīng)有結(jié)果了瞬女,await() 則直接獲取其結(jié)果并賦值給變量,此時(shí)不會(huì)掛起協(xié)程努潘。
8诽偷、suspend
suspend fun 掛起函數(shù),即該函數(shù)是一個(gè)耗時(shí)操作疯坤,須放在協(xié)程中執(zhí)行报慕。
掛起函數(shù)只能在協(xié)程中和其他掛起函數(shù)中調(diào)用,不能在其他地方使用压怠。
suspend函數(shù)會(huì)將整個(gè)協(xié)程掛起眠冈,而不僅僅是這個(gè)suspend函數(shù),也就是說(shuō)一個(gè)協(xié)程中有多個(gè)掛起函數(shù)時(shí)菌瘫,它們是順序執(zhí)行的洋闽。
9、withContext
withContext 與 async 都可以返回耗時(shí)任務(wù)的執(zhí)行結(jié)果突梦。 通常也會(huì)使用withContext實(shí)現(xiàn)線程切換。
一般來(lái)說(shuō)羽利,多個(gè) withContext 任務(wù)是串行的宫患, 且withContext 可直接返回耗時(shí)任務(wù)的結(jié)果。 多個(gè) async 任務(wù)是并行的这弧,async 返回的是一個(gè)Deferred<T>娃闲,需要調(diào)用其await()方法獲取結(jié)果虚汛。
二、協(xié)程的執(zhí)行測(cè)試
兩個(gè)輔助方法
private suspend fun doTask(msg: String, delayTime: Long = 0) =
(if (delayTime < 1000) (1000 + Math.random() * 1000).toLong() else delayTime).apply {
//延時(shí)
delay(this)
logMessage("task$msg - $this")
}
private fun logMessage(msg: String) {
currentTime.time = System.currentTimeMillis()
//日志輸出當(dāng)前時(shí)間皇帮、線程名卷哩、自定義信息
Log.e(
"Coroutine",
"${dateFormat.format(currentTime)} *** ThreadName : ${Thread.currentThread().name} *** $msg"
)
}
1、順序執(zhí)行:task3依賴task2的結(jié)果属拾,task2依賴task1的結(jié)果
private fun testCoroutine() {
logMessage("launchBefore")
GlobalScope.launch {
//do task
logMessage("taskBefore")
val result1 = doTask("1")
val result2 = doTask("2", result1 - 1000)
val result3 = doTask("3", result2 - 1000)
logMessage("taskAfter")
//輸出結(jié)果
withContext(Dispatchers.Main) {
logMessage("result is ${result1}, ${result2}, $result3")
}
}
logMessage("launchAfter")
}
日志輸出:
E/Coroutine: 11:26:57 530 *** ThreadName : main *** launchBefore
E/Coroutine: 11:26:57 590 *** ThreadName : main *** launchAfter
E/Coroutine: 11:26:57 591 *** ThreadName : DefaultDispatcher-worker-1 *** taskBefore
E/Coroutine: 11:26:59 475 *** ThreadName : DefaultDispatcher-worker-1 *** task1 - 1876
E/Coroutine: 11:27:01 413 *** ThreadName : DefaultDispatcher-worker-1 *** task2 - 1936
E/Coroutine: 11:27:03 386 *** ThreadName : DefaultDispatcher-worker-1 *** task3 - 1971
E/Coroutine: 11:27:03 386 *** ThreadName : DefaultDispatcher-worker-1 *** taskAfter
E/Coroutine: 11:27:03 394 *** ThreadName : main *** result is 1876, 1936, 1971
由以上日志輸出可知:
- 協(xié)程 launch 中執(zhí)行耗時(shí)任務(wù)沒有阻塞主線程(launchAfter先于協(xié)程輸出)
- task1将谊、task2、task3 順序執(zhí)行 (執(zhí)行時(shí)間時(shí)間線)
2渐白、異步執(zhí)行:task3依賴task1尊浓、task2的結(jié)果,task1纯衍、task2異步執(zhí)行
private fun testCoroutine() {
logMessage("launchBefore")
GlobalScope.launch {
//do task
logMessage("taskBefore")
val result1 = async { doTask("1", 2000) }
val result2 = async { doTask("2", 2000) }
val result3 = doTask("3", result1.await() + result2.await())
logMessage("taskAfter")
//輸出結(jié)果
withContext(Dispatchers.Main) {
logMessage("result is ${result1.await()}, ${result2.await()}, $result3")
}
}
logMessage("launchAfter")
}
日志輸出:
E/Coroutine: 12:22:50 202 *** ThreadName : main *** launchBefore
E/Coroutine: 12:22:50 264 *** ThreadName : main *** launchAfter
E/Coroutine: 12:22:50 266 *** ThreadName : DefaultDispatcher-worker-1 *** taskBefore
E/Coroutine: 12:22:52 279 *** ThreadName : DefaultDispatcher-worker-2 *** task1 - 2000
E/Coroutine: 12:22:52 279 *** ThreadName : DefaultDispatcher-worker-3 *** task2 - 2000
E/Coroutine: 12:22:56 282 *** ThreadName : DefaultDispatcher-worker-2 *** task3 - 4000
E/Coroutine: 12:22:56 283 *** ThreadName : DefaultDispatcher-worker-2 *** taskAfter
E/Coroutine: 12:22:56 290 *** ThreadName : main *** result is 2000, 2000, 4000
async { }
異步執(zhí)行
await()
獲取異步執(zhí)行結(jié)果
更多資料參考:
將 Kotlin 協(xié)程與架構(gòu)組件一起使用
超長(zhǎng)文栋齿,帶你全面了解Kotlin的協(xié)程
Kotlin協(xié)程核心庫(kù)分析
Kotlin實(shí)戰(zhàn)指南十四:協(xié)程啟動(dòng)模式