Kotlin協(xié)程場(chǎng)景化學(xué)習(xí)
何為Kotlin協(xié)程?
協(xié)程是一種并發(fā)設(shè)計(jì)模式,Kotlin協(xié)程是一個(gè)線程框架。
為什么需要Kotlin協(xié)程?
提供方便的線程操作API,編寫邏輯清晰且簡(jiǎn)潔的線程代碼。
協(xié)程是Google在 Android 上進(jìn)行異步編程的推薦解決方案。具有如下特點(diǎn):
輕量:您可以在單個(gè)線程上運(yùn)行多個(gè)協(xié)程,因?yàn)閰f(xié)程支持掛起,不會(huì)使正在運(yùn)行協(xié)程的線程阻塞。掛起比阻塞節(jié)省內(nèi)存,且支持多個(gè)并行操作。 內(nèi)存泄漏更少:使用結(jié)構(gòu)化并發(fā)機(jī)制在一個(gè)作用域內(nèi)執(zhí)行多項(xiàng)操作。 內(nèi)置取消支持:取消操作會(huì)自動(dòng)在運(yùn)行中的整個(gè)協(xié)程層次結(jié)構(gòu)內(nèi)傳播。 Jetpack 集成:許多 Jetpack 庫(kù)都包含提供全面協(xié)程支持的擴(kuò)展。某些庫(kù)還提供自己的協(xié)程作用域,可供您用于結(jié)構(gòu)化并發(fā)。
如何使用Kotlin協(xié)程
添加依賴
dependencies?{
????implementation?'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9'
}
使用場(chǎng)景
啟動(dòng)協(xié)程
若我們需要執(zhí)行一個(gè)簡(jiǎn)單的后臺(tái)(或者前臺(tái))任務(wù),通過(guò)GlobalScope.launch可以快速的啟動(dòng)一個(gè)協(xié)程來(lái)處理業(yè)務(wù)邏輯,同時(shí),也可以通過(guò)Dispatchs來(lái)指定線程類型:Dispatchers.Default、Dispatchers.IO、Dispatchers.Main、Dispatchers.Unconfined。
GlobalScope.launch(Dispatchers.IO)?{
????delay(1000)
????Log.d(TAG,?"processIO?in?${Thread.currentThread().name}")
}
切換線程
主線程 => IO線程 => 主線程。這種場(chǎng)景開(kāi)發(fā)過(guò)程中使用最多,比如后臺(tái)獲取一張照片,然后前臺(tái)顯示。
//?主線程內(nèi)啟動(dòng)一個(gè)協(xié)程
GlobalScope.launch(Dispatchers.Main)?{
????//?切換到IO線程
????withContext(Dispatchers.IO)?{
????????delay(1000)
????????Log.d(TAG,?"processIO?in?${Thread.currentThread().name}")
????}
????//?自動(dòng)切回主線程
????Log.d(TAG,?"processUI?in?${Thread.currentThread().name}")
}
運(yùn)行結(jié)果:
2021-01-02?18:38:23.812?15506-15535/tech.kicky.coroutine?D/Coroutine?Sample:?processIO?in?DefaultDispatcher-worker-1
2021-01-02?18:38:23.813?15506-15506/tech.kicky.coroutine?D/Coroutine?Sample:?processUI?in?main
取消協(xié)程
private?fun?cancelCoroutine()?{
????val?job?=?GlobalScope.launch(Dispatchers.IO)?{
????????for?(i?in?0..10000)?{
????????????delay(1)
????????????Log.d(TAG,?"count?=?$i")
????????}
????}
????Thread.sleep(30)
????job.cancel()
????Log.d(TAG,?"Coroutine?Cancel")
}
執(zhí)行結(jié)果如下:
2021-01-02?18:53:37.680?23240-23279/tech.kicky.coroutine?D/Coroutine?Sample:?count?=?0
2021-01-02?18:53:37.682?23240-23278/tech.kicky.coroutine?D/Coroutine?Sample:?count?=?1
2021-01-02?18:53:37.685?23240-23280/tech.kicky.coroutine?D/Coroutine?Sample:?count?=?2
2021-01-02?18:53:37.687?23240-23280/tech.kicky.coroutine?D/Coroutine?Sample:?count?=?3
2021-01-02?18:53:37.689?23240-23280/tech.kicky.coroutine?D/Coroutine?Sample:?count?=?4
2021-01-02?18:53:37.690?23240-23280/tech.kicky.coroutine?D/Coroutine?Sample:?count?=?5
2021-01-02?18:53:37.693?23240-23280/tech.kicky.coroutine?D/Coroutine?Sample:?count?=?6
2021-01-02?18:53:37.696?23240-23240/tech.kicky.coroutine?D/Coroutine?Sample:?Coroutine?Cancel
LifecycleOwner配合使用
由于Kotlin協(xié)程主要作用是處理線程操作,若處理不當(dāng)會(huì)出現(xiàn)內(nèi)存泄露等問(wèn)題,如Activity或者Fragment已銷毀,但是界面內(nèi)的協(xié)程卻依舊在執(zhí)行,就會(huì)產(chǎn)生內(nèi)存泄露的問(wèn)題。所以,我們?cè)诮缑驿N毀時(shí),必須取消界面內(nèi)的協(xié)程操作。我們可以自己在界面銷毀時(shí)調(diào)用cancel()方法,但是無(wú)良好的編程習(xí)慣就很容易被忽略。建議采用Google給我們提供的擴(kuò)展方法。
implementation?"androidx.activity:activity-ktx:1.1.0"
implementation?"androidx.fragment:fragment-ktx:1.2.5"
private?fun?lifecycleCoroutine()?{
????//?主線程內(nèi)啟動(dòng)一個(gè)協(xié)程
????lifecycleScope.launch?{
????????//?切換到IO線程
????????withContext(Dispatchers.IO)?{
????????????delay(1000)
????????????Log.d(TAG,?"processIO?in?${Thread.currentThread().name}")
????????}
????????//?自動(dòng)切回主線程
????????Log.d(TAG,?"processUI?in?${Thread.currentThread().name}")
????}
}
注意:
1. lifecycleScope.launch()默認(rèn)就是在主線程啟動(dòng)協(xié)程;
2. lifecycleScope內(nèi)的協(xié)程在Lifecycle為destroyed狀態(tài)時(shí)會(huì)自動(dòng)取消。
3.lifecycleScope還有一些其他的擴(kuò)展方法,如launchWhenCreated、launchWhenStarted、launchWhenResumed等,用法從方法名上看很明顯
ViewModel配合使用
協(xié)程與LifecycleOwner配合使用解決的是界面生命周期變化過(guò)程中協(xié)程的處理問(wèn)題。但是針對(duì)屏幕旋轉(zhuǎn)這種界面重建的場(chǎng)景,ViewModel對(duì)象的存在時(shí)間比LifecycleOwner要持久。雖然界面需要重建,但是協(xié)程不一定要被取消,這個(gè)需要結(jié)合具體需求考慮。
fun?viewModelCoroutine()?{
????viewModelScope.launch?{
????????Log.d("Coroutine?Sample",?Thread.currentThread().name)
????}
}
注意:
1. viewModelScope.launch()默認(rèn)也是在主線程啟動(dòng)協(xié)程;
2. viewModelScope內(nèi)的協(xié)程在ViewModel將被onCleared時(shí)會(huì)自動(dòng)取消。
Retrofit真香組合
說(shuō)重點(diǎn),Retrofit 2.6之后的版本支持使用Kotlin的協(xié)程。那么,具體如何支持?
添加依賴
implementation?"com.squareup.retrofit2:retrofit:2.9.0"
implementation?"com.squareup.retrofit2:converter-gson:2.9.0"
添加網(wǎng)絡(luò)權(quán)限
<uses-permission?android:name="android.permission.INTERNET"?/>
Retrofit
object?Retrofitance?{
????private?val?client:?OkHttpClient?by?lazy?{
????????OkHttpClient.Builder()
????????????.build()
????}
????private?val?retrofitance:?Retrofit?by?lazy?{
????????Retrofit.Builder()
????????????.baseUrl("https://www.wanandroid.com")
????????????.addConverterFactory(GsonConverterFactory.create())
????????????.client(client)
????????????.build()
????}
????val?wanAndroidApi:?WanAndroidApi?by?lazy?{
????????retrofitance.create(WanAndroidApi::class.java)
????}
}
API
interface?WanAndroidApi?{
????@GET("/banner/json")
????suspend?fun?banners():?WanAndroidRoot
}
重點(diǎn)關(guān)注API里的suspend關(guān)鍵字。suspend是掛起的意思,提醒開(kāi)發(fā)者此方法為耗時(shí)方法。
執(zhí)行網(wǎng)絡(luò)請(qǐng)求
class?MainViewModel?:?ViewModel()?{
????val?banners?=?MutableLiveData>()
????fun?viewModelCoroutine()?{
????????viewModelScope.launch(Dispatchers.IO)?{
????????????val?result?=?Retrofitance.wanAndroidApi.banners()
????????????banners.postValue(result.data)
????????}
????}
}
private?val?viewModel?by?viewModels()
override?fun?onCreate(savedInstanceState:?Bundle?)?{
????super.onCreate(savedInstanceState)
????setContentView(binding.root)
????viewModel.banners.observe(this,?{
????????val?content:?List?=?it.map?{?banner?->
????????????banner.title
????????}
????????binding.text.text?=?content.toTypedArray().contentToString()
????})
????viewModel.viewModelCoroutine()
}
Retrofit請(qǐng)求依賴
針對(duì)存在依賴關(guān)系的網(wǎng)絡(luò)請(qǐng)求,未使用協(xié)程之前,我們需要在回調(diào)中處理,一層兩層尚可,層次多了就容易凌亂。使用協(xié)程,按照順序編寫代碼,簡(jiǎn)潔清晰。
fun?viewModelSequenceRequest()?{
????viewModelScope.launch(Dispatchers.IO)?{
????????val?start?=?System.currentTimeMillis()
????????//?先請(qǐng)求首頁(yè)Banners
????????val?result?=?Retrofitance.wanAndroidApi.banners()
????????banners.postValue(result.data)
????????//?再請(qǐng)求熱鍵,只要是順序執(zhí)行即可且上一次的請(qǐng)求結(jié)果已拿到即可滿足我們的使用場(chǎng)景。
????????val?keys?=?Retrofitance.wanAndroidApi.hotKeys()
????????hotKeys.postValue(keys.data)
????????Log.d("Coroutine?Sample",?(System.currentTimeMillis()?-?start).toString())
????}
}
Retrofit并發(fā)結(jié)果合并
針對(duì)多并發(fā)執(zhí)行,結(jié)果統(tǒng)一處理,然后再執(zhí)行其他內(nèi)容的場(chǎng)景。未使用協(xié)程之前,我們可以采用RxJava的zip操作符處理。協(xié)程async/await輕松勝任。
fun?viewModelAsync()?{
????viewModelScope.launch(Dispatchers.IO)?{
????????val?start?=?System.currentTimeMillis()
????????val?result?=?async?{?Retrofitance.wanAndroidApi.banners()?}
????????val?keys?=?async?{?Retrofitance.wanAndroidApi.hotKeys()?}
????????Log.d(
????????????????"Coroutine?Sample",
????????????????(result.await().data.size?+?keys.await().data.size).toString()
????????????)
????????Log.d("Coroutine?Sample",?(System.currentTimeMillis()?-?start).toString())
????}
}
和上一個(gè)例子的代碼相同,但是執(zhí)行時(shí)間卻會(huì)少很多,因?yàn)檫@個(gè)不是順序執(zhí)行而是并發(fā)執(zhí)行。
總結(jié)
個(gè)人認(rèn)為Kotlin協(xié)程的基本使用重點(diǎn)關(guān)注如下三個(gè)方面:
線程切換 如何避免內(nèi)存泄露(與LifecycleOwner、ViewModel等配合使用) 搭配Retrofit
當(dāng)然,Kotlin Coroutine庫(kù)里面還有很多操作符和方法有待探索,即學(xué)即用。
源碼
https://github.com/onlyloveyd/LearningCoroutine
