kotlin協(xié)程在Android中的基礎(chǔ)應(yīng)用
通過(guò)前面的三個(gè)章節(jié),現(xiàn)在我們已經(jīng)了解了kotlin
協(xié)程的基本使用和相關(guān)基礎(chǔ)知識(shí)點(diǎn)。如:
- 協(xié)程的基礎(chǔ)使用方式和基本原理宾袜。
-
CoroutineContext
:協(xié)程上下文中包含的Element
以及下上文的作用叉谜,傳遞错敢。 -
CoroutineDispatcher
:協(xié)程調(diào)度器的使用 -
CoroutineStart
:協(xié)程啟動(dòng)模式在不同模式下的區(qū)別 -
CoroutineScope
:協(xié)程作用域的分類践樱,以及不同作用域下的異常處理厂画。 - 掛起函數(shù)以及
suspend
關(guān)鍵字的作用,以及Continuation
的掛起恢復(fù)流程拷邢。 -
CoroutineExceptionHandler
:協(xié)程異常處理袱院,結(jié)合supervisorScope
和SupervisorJob
的使用。
這一章節(jié)中瞭稼,我們將主要講解kotlin協(xié)程在Android中的基礎(chǔ)使用忽洛。我們先引入相關(guān)擴(kuò)展庫(kù)組件庫(kù):
implementation "androidx.activity:activity-ktx:1.2.2"
implementation "androidx.fragment:fragment-ktx:1.3.3"
復(fù)制代碼
Android使用kotlin協(xié)程
我們?cè)谥暗恼鹿?jié)中使用協(xié)程的方式都是通過(guò)runBlocking
或者使用GlobalScope
的launch
、async
方式啟動(dòng)环肘,當(dāng)然也可以通過(guò)創(chuàng)建一個(gè)新的CoroutineScope
,然后通過(guò)launch
或者async
方式啟動(dòng)一個(gè)新的協(xié)程欲虚。我們?cè)谥v解協(xié)程異常處理
的篇章中就提到,通過(guò)SupervisorJob
和CoroutineExceptionHandler
實(shí)現(xiàn)了一個(gè)和supervisorScope
相同的作用域悔雹。
private fun testException(){
val exceptionHandler = CoroutineExceptionHandler { coroutineContext, throwable ->
Log.d("exceptionHandler", "${coroutineContext[CoroutineName].toString()} 處理異常 :$throwable")
}
val supervisorScope = CoroutineScope(SupervisorJob() + exceptionHandler)
with(supervisorScope) {
launch{
}
//省略...
}
}
復(fù)制代碼
在第一節(jié)中我們提到runBlocking
它會(huì)將常規(guī)的阻塞代碼連接到一起复哆,主要用于main函數(shù)和測(cè)試中。而GlobalScope
又是一個(gè)全局頂級(jí)協(xié)程腌零,我們?cè)谥暗陌咐谢径际褂眠@種方式梯找。但是這個(gè)協(xié)程是在整個(gè)應(yīng)用程序生命周期內(nèi)運(yùn)行的,如果我們用GlobalScope
啟動(dòng)協(xié)程,我們啟動(dòng)一個(gè)將會(huì)變得極其繁瑣莱没,而且需要對(duì)于各種引用的處理以及管控異常取消操作初肉。
我們可以先忽略CoroutineExceptionHandler
協(xié)程異常處理。因?yàn)椴还苁侨魏畏绞絾?dòng)協(xié)程饰躲,如果不在程上下文中添加CoroutineExceptionHandler
牙咏,當(dāng)產(chǎn)生未捕獲的異常時(shí)都會(huì)導(dǎo)致應(yīng)用崩潰。
那么下面代碼會(huì)出現(xiàn)什么問(wèn)題嘹裂?
private fun start() {
GlobalScope.launch{
launch {
//網(wǎng)絡(luò)請(qǐng)求1...
throw NullPointerException("空指針")
}
val result = withContext(Dispatchers.IO) {
//網(wǎng)絡(luò)請(qǐng)求2...
requestData()
"請(qǐng)求結(jié)果"
}
btn.text = result
launch {
//網(wǎng)絡(luò)請(qǐng)求3...
}
}
}
復(fù)制代碼
因?yàn)槲覀兊?code>GlobalScope默認(rèn)使用的是
Dispatchers.Default
妄壶,這會(huì)導(dǎo)致我們?cè)诜侵骶€程上刷新UI。子協(xié)程產(chǎn)生異常會(huì)產(chǎn)生相互干擾寄狼。子協(xié)程異常取消會(huì)導(dǎo)致父協(xié)程取消丁寄,同時(shí)其他子協(xié)程也將會(huì)被取消。
如果我們這個(gè)時(shí)候
activity
或者framgent
退出泊愧,因?yàn)閰f(xié)程是在GlobalScope
中運(yùn)行伊磺,所以即使activity
或者framgent
退出,這個(gè)協(xié)程還是在運(yùn)行,這個(gè)時(shí)候會(huì)產(chǎn)生各種泄露問(wèn)題删咱。同時(shí)此協(xié)程當(dāng)執(zhí)行到刷新操作時(shí)屑埋,因?yàn)槲覀兊慕缑嬉呀?jīng)銷毀,這個(gè)時(shí)候執(zhí)行UI刷新將會(huì)產(chǎn)生崩潰痰滋。
如果我們要解決上面的問(wèn)題摘能。我們得這么做:
var job:Job? = null
private fun start() {
job = GlobalScope.launch(Dispatchers.Main + SupervisorJob()) {
launch {
throw NullPointerException("空指針")
}
val result = withContext(Dispatchers.IO) {
//網(wǎng)絡(luò)請(qǐng)求...
"請(qǐng)求結(jié)果"
}
launch {
//網(wǎng)絡(luò)請(qǐng)求3...
}
btn.text = result
}
}
override fun onDestroy() {
super.onDestroy()
job?.cancel()
}
復(fù)制代碼
我們先需要通過(guò)launch
啟動(dòng)時(shí)加入Dispatchers.Main
來(lái)保證我們是在主線程刷新UI续崖,同時(shí)還需要再GlobalScope.launch
的協(xié)程上下文中加入SupervisorJob
來(lái)避免子協(xié)程的異常取消會(huì)導(dǎo)致整個(gè)協(xié)程樹被終結(jié)。 最后我們還得把每次
通過(guò)GlobalScope
啟動(dòng)的Job
保存下來(lái)团搞,在activity
或者framgent
退出時(shí)調(diào)用job.cancel
取消整個(gè)協(xié)程樹严望。這么來(lái)一遍感覺(jué)還行,但是我們不是寫一次啊逻恐,每次寫的時(shí)候會(huì)不會(huì)感覺(jué)超麻煩像吻,甚至懷疑人生。
所以官方在kotlin協(xié)程中提供了一個(gè)默認(rèn)在主線程運(yùn)行的協(xié)程:MainScope
,我們可以通過(guò)它來(lái)啟動(dòng)協(xié)梢莽。
public fun MainScope(): CoroutineScope = ContextScope(SupervisorJob() + Dispatchers.Main)
復(fù)制代碼
我們可以看到MainScope
的創(chuàng)建默認(rèn)就使用了SupervisorJob
和 Dispatchers.Main
萧豆。說(shuō)明我們可以通過(guò)MainScope
來(lái)處理UI組件刷新奸披。同時(shí)由于MainScope
采用的是SupervisorJob
昏名,所以我們各個(gè)子協(xié)程中的異常導(dǎo)致的取消操作并不會(huì)導(dǎo)致MainScope
的取消。這就很好的簡(jiǎn)化了我們通過(guò)GlobalScope
去啟動(dòng)一個(gè)協(xié)程的過(guò)程阵面。
private val mainScope = MainScope()
private fun start() {
mainScope.launch {
launch {
throw NullPointerException("空指針")
}
val result = withContext(Dispatchers.IO) {
//網(wǎng)絡(luò)請(qǐng)求...
"請(qǐng)求結(jié)果"
}
launch {
//網(wǎng)絡(luò)請(qǐng)求3...
}
btn.text = result
}
}
override fun onDestroy() {
super.onDestroy()
mainScope.cancel()
}
復(fù)制代碼
通過(guò)使用MainScope
我們是不是省略了很多操作轻局。同時(shí)我們也不需要保存每一個(gè)通過(guò)MainScope
啟動(dòng)的Job
了,直接在最后銷毀的時(shí)候調(diào)用mainScope.cancel()
就能取消所有通過(guò)mainScope
啟動(dòng)的協(xié)程样刷。
這里多提一點(diǎn):可能這里有的人會(huì)想仑扑,我使用GlobalScope
也不保存啟動(dòng)的Job
,直接GlobalScope.cancel
不行嗎置鼻?如果是這樣的話镇饮,那么恭喜你喜提超級(jí)崩潰BUG一個(gè)。這里就不擴(kuò)展了箕母〈⒚辏可以自己動(dòng)手去試試,畢竟實(shí)踐出真理嘶是。
那可能還有人想钙勃,我連創(chuàng)建MainScope
都懶得寫,而且腦子經(jīng)常不好使聂喇,容易忘記調(diào)用mainScope
進(jìn)行cancel
操作怎么辦辖源。
官方早就為我們這些懶人想好了解決方案,這個(gè)時(shí)候我們只需要再集成一個(gè)ktx運(yùn)行庫(kù)就可以了希太。
在Activity與Framgent中使用協(xié)程
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.3.1"
復(fù)制代碼
這個(gè)時(shí)候我們就可以在activity
或者framgent
直接使用lifecycleScope
進(jìn)行啟動(dòng)協(xié)程克饶。我們看來(lái)看看activity中的lifecycleScope
實(shí)現(xiàn)
public val LifecycleOwner.lifecycleScope: LifecycleCoroutineScope
get() = lifecycle.coroutineScope
復(fù)制代碼
我們可以到lifecycleScope
它是通過(guò)lifecycle
得到一個(gè)coroutineScope
,是一個(gè)LifecycleCoroutineScope
對(duì)象誊辉。
public 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
}
}
}
復(fù)制代碼
我們可以看到lifecycleScope
采用的和MainScope
一樣的創(chuàng)建CoroutineScope
矾湃,同時(shí)它又通過(guò)結(jié)合lifecycle
來(lái)實(shí)現(xiàn)當(dāng)lifecycle
狀態(tài)處于DESTROYED
狀態(tài)的時(shí)候自動(dòng)關(guān)閉所有的協(xié)程。
public abstract class LifecycleCoroutineScope internal constructor() : CoroutineScope {
internal abstract val lifecycle: Lifecycle
public fun launchWhenCreated(block: suspend CoroutineScope.() -> Unit): Job = launch {
lifecycle.whenCreated(block)
}
public fun launchWhenStarted(block: suspend CoroutineScope.() -> Unit): Job = launch {
lifecycle.whenStarted(block)
}
public fun launchWhenResumed(block: suspend CoroutineScope.() -> Unit): Job = launch {
lifecycle.whenResumed(block)
}
}
internal class LifecycleCoroutineScopeImpl(
override val lifecycle: Lifecycle,
override val coroutineContext: CoroutineContext
) : LifecycleCoroutineScope(), LifecycleEventObserver {
init {
if (lifecycle.currentState == Lifecycle.State.DESTROYED) {
coroutineContext.cancel()
}
}
fun register() {
launch(Dispatchers.Main.immediate) {
if (lifecycle.currentState >= Lifecycle.State.INITIALIZED) {
lifecycle.addObserver(this@LifecycleCoroutineScopeImpl)
} else {
coroutineContext.cancel()
}
}
}
override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
if (lifecycle.currentState <= Lifecycle.State.DESTROYED) {
lifecycle.removeObserver(this)
coroutineContext.cancel()
}
}
}
復(fù)制代碼
同時(shí)我們也可以通過(guò)launchWhenCreated
芥映、launchWhenStarted
洲尊、launchWhenResumed
來(lái)啟動(dòng)協(xié)程远豺,等到lifecycle
處于對(duì)應(yīng)狀態(tài)時(shí)自動(dòng)觸發(fā)此處創(chuàng)建的協(xié)程。
比如我們可以這么操作:
class MainTestActivity : AppCompatActivity() {
init {
lifecycleScope.launchWhenResumed {
Log.d("init", "在類初始化位置啟動(dòng)協(xié)程")
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}
復(fù)制代碼
D/onResume: onResume
D/init: 在類初始化位置啟動(dòng)協(xié)程
復(fù)制代碼
按照我們正常情況加載順序坞嘀,是不是應(yīng)該init
先執(zhí)行輸出躯护?然而在實(shí)際情況中它是在等待Activity
進(jìn)入onResume
狀態(tài)以后才執(zhí)行接著看launchWhenResumed
中調(diào)用的whenResumed
實(shí)現(xiàn)。
public suspend fun <T> Lifecycle.whenResumed(block: suspend CoroutineScope.() -> T): T {
return whenStateAtLeast(Lifecycle.State.RESUMED, block)
}
public suspend fun <T> Lifecycle.whenStateAtLeast(
minState: Lifecycle.State,
block: suspend CoroutineScope.() -> T
): T = withContext(Dispatchers.Main.immediate) {
val job = coroutineContext[Job] ?: error("when[State] methods should have a parent job")
val dispatcher = PausingDispatcher()
val controller =
LifecycleController(this@whenStateAtLeast, minState, dispatcher.dispatchQueue, job)
try {
withContext(dispatcher, block)
} finally {
controller.finish()
}
}
@MainThread
internal class LifecycleController(
private val lifecycle: Lifecycle,
private val minState: Lifecycle.State,
private val dispatchQueue: DispatchQueue,
parentJob: Job
) {
private val observer = LifecycleEventObserver { source, _ ->
if (source.lifecycle.currentState == Lifecycle.State.DESTROYED) {
handleDestroy(parentJob)
} else if (source.lifecycle.currentState < minState) {
dispatchQueue.pause()
} else {
dispatchQueue.resume()
}
}
init {
if (lifecycle.currentState == Lifecycle.State.DESTROYED) {
handleDestroy(parentJob)
} else {
lifecycle.addObserver(observer)
}
}
private inline fun handleDestroy(parentJob: Job) {
parentJob.cancel()
finish()
}
@MainThread
fun finish() {
lifecycle.removeObserver(observer)
dispatchQueue.finish()
}
}
復(fù)制代碼
我們可以看到丽涩,實(shí)際上是調(diào)用了whenStateAtLeast
棺滞,同時(shí)使用了withContext
進(jìn)行了一個(gè)同步操作。然后在LifecycleController
中通過(guò)添加LifecycleObserver
來(lái)監(jiān)聽(tīng)狀態(tài)矢渊,通過(guò)lifecycle
當(dāng)前狀態(tài)來(lái)對(duì)比我們?cè)O(shè)定的觸發(fā)狀態(tài)继准,最終決定是否恢復(fù)執(zhí)行矮男。
現(xiàn)在我們對(duì)于Activity
中的lifecycleScope
的創(chuàng)建以及銷毀流程有了一個(gè)大概的了解崔泵。同理Fragment
中的lifecycleScope
實(shí)現(xiàn)原理也是和Activity
是一樣的幌甘,這里我們就不再重復(fù)講解线婚。我們做個(gè)簡(jiǎn)單的實(shí)驗(yàn):
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
lifecycleScope.launch {
delay(2000)
Toast.makeText(this@MainActivity,"haha",Toast.LENGTH_SHORT).show()
}
}
}
復(fù)制代碼
這個(gè)時(shí)候是不是比之前的使用方式簡(jiǎn)單多了,我們既不用關(guān)心創(chuàng)建過(guò)程,也不用關(guān)心銷毀的過(guò)程。
這個(gè)時(shí)候我們就需要提到CoroutineExceptionHandler
協(xié)程異常處理。通過(guò)之前的章節(jié)我們知道,啟動(dòng)一個(gè)協(xié)程以后,如果未在協(xié)程上下文中添加CoroutineExceptionHandler
情況下蜕提,一旦產(chǎn)生了未捕獲的異常,那么我們的程序?qū)?huì)崩潰退出镣煮。
class MainActivity : AppCompatActivity() {
val exceptionHandler = CoroutineExceptionHandler { coroutineContext, throwable ->
Log.d("exceptionHandler", "${coroutineContext[CoroutineName]} $throwable")
}
fun load() {
lifecycleScope.launch(exceptionHandler) {
//省略...
}
lifecycleScope.launch(exceptionHandler) {
//省略...
}
lifecycleScope.launch(exceptionHandler) {
//省略...
}
}
}
復(fù)制代碼
當(dāng)出現(xiàn)這種情況的時(shí)候恨胚,像筆者這種有嚴(yán)重偷懶情結(jié)的人就開始抓狂了。為什么要寫這么多遍 lifecycleScope.launch
,同時(shí)每一次啟動(dòng)都要手動(dòng)添加CoroutineExceptionHandler
绸栅。難道就不能再簡(jiǎn)便一點(diǎn)嗎辰企?
當(dāng)然可以十减,首先我們自定義一個(gè)異常處理,由驹,我們?cè)趯?shí)現(xiàn)上只做一個(gè)簡(jiǎn)單的異常日志輸出:
/**
* @param errCode 錯(cuò)誤碼
* @param errMsg 簡(jiǎn)要錯(cuò)誤信息
* @param report 是否需要上報(bào)
*/
class GlobalCoroutineExceptionHandler(private val errCode: Int, private val errMsg: String = "", private val report: Boolean = false) : CoroutineExceptionHandler {
override val key: CoroutineContext.Key<*>
get() = CoroutineExceptionHandler
override fun handleException(context: CoroutineContext, exception: Throwable) {
val msg = exception.stackTraceToString()
Log.e("$errCode","GlobalCoroutineExceptionHandler:${msg}")
}
}
復(fù)制代碼
然后我們?cè)谕ㄟ^(guò)kotlin的擴(kuò)展函數(shù)來(lái)簡(jiǎn)化我們的使用,去掉重復(fù)寫lifecycleScope.launch
和exceptionHandler
的過(guò)程邪锌,我們就定義三個(gè)常用方法蜕企。
inline fun AppCompatActivity.requestMain(
errCode: Int = -1, errMsg: String = "", report: Boolean = false,
noinline block: suspend CoroutineScope.() -> Unit) {
lifecycleScope.launch(GlobalCoroutineExceptionHandler(errCode, errMsg, report)) {
block.invoke(this)
}
}
inline fun AppCompatActivity.requestIO(
errCode: Int = -1, errMsg: String = "", report: Boolean = false,
noinline block: suspend CoroutineScope.() -> Unit): Job {
return lifecycleScope.launch(Dispatchers.IO + GlobalCoroutineExceptionHandler(errCode, errMsg, report)) {
block.invoke(this)
}
}
inline fun AppCompatActivity.delayMain(
errCode: Int = -1, errMsg: String = "", report: Boolean = false,
delayTime: Long, noinline block: suspend CoroutineScope.() -> Unit) {
lifecycleScope.launch(GlobalCoroutineExceptionHandler(errCode, errMsg, report)) {
withContext(Dispatchers.IO) {
delay(delayTime)
}
block.invoke(this)
}
}
復(fù)制代碼
這個(gè)時(shí)候我們就可以愉快的在Activity
中使用了
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
requestMain {
delay(2000)
Toast.makeText(this@MainActivity,"haha",Toast.LENGTH_SHORT).show()
}
requestIO {
loadNetData()
}
delayMain(100){
Toast.makeText(this@MainActivity,"haha",Toast.LENGTH_SHORT).show()
}
}
private suspend fun loadNetData(){
//網(wǎng)絡(luò)加載
}
}
復(fù)制代碼
同樣的我們?cè)贁U(kuò)展一套基于Fragment
的方法
inline fun Fragment.requestMain(
errCode: Int = -1, errMsg: String = "", report: Boolean = false,
noinline block: suspend CoroutineScope.() -> Unit) {
lifecycleScope.launch(GlobalCoroutineExceptionHandler(errCode, errMsg, report)) {
block.invoke(this)
}
}
inline fun Fragment.requestIO(
errCode: Int = -1, errMsg: String = "", report: Boolean = false,
noinline block: suspend CoroutineScope.() -> Unit) {
lifecycleScope.launch(Dispatchers.IO + GlobalCoroutineExceptionHandler(errCode, errMsg, report)) {
block.invoke(this)
}
}
inline fun Fragment.delayMain(
errCode: Int = -1, errMsg: String = "", report: Boolean = false, delayTime: Long,
noinline block: suspend CoroutineScope.() -> Unit) {
lifecycleScope.launch(GlobalCoroutineExceptionHandler(errCode, errMsg, report)) {
withContext(Dispatchers.IO) {
delay(delayTime)
}
block.invoke(this)
}
}
復(fù)制代碼
然后也可以愉快的在Fragment
中使用了
class HomeFragment:Fragment() {
init {
lifecycleScope.launchWhenCreated {
Toast.makeText(context,"Fragment創(chuàng)建了", Toast.LENGTH_SHORT).show()
}
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_main,container,false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
requestMain {
//...
}
requestIO {
//...
}
delayMain(100){
//...
}
}
}
復(fù)制代碼
這里需要提一下,可能有的人不太明白,為什么要把Activity
和Fragment
都分開寫嚣伐,他們都是使用的lifecycleScope
糖赔,我們直接通過(guò)lifecycleScope
擴(kuò)展就不可以了嗎。假如我們這么擴(kuò)展:
inline fun LifecycleCoroutineScope.requestMain(
errCode: Int = -1, errMsg: String = "", report: Boolean = false,
noinline block: suspend CoroutineScope.() -> Unit) {
launch(GlobalCoroutineExceptionHandler(errCode, errMsg, report)) {
block.invoke(this)
}
}
復(fù)制代碼
我們以Dailog
為例,來(lái)啟動(dòng)一個(gè)協(xié)程:
val dialog = Dialog(this)
dialog.show()
(dialog.context as LifecycleOwner).lifecycleScope.requestMain {
withContext(Dispatchers.IO){
//網(wǎng)絡(luò)加載
}
// 刷新UI
}
dialog.cancel()
復(fù)制代碼
那么可能會(huì)出現(xiàn)一個(gè)什么問(wèn)題轩端?是的放典,內(nèi)存泄露的問(wèn)題以及錯(cuò)誤的引用問(wèn)題。雖然我的dialog
被銷毀了,但是我們lifecycleScope
并不處于DESTROYED
狀態(tài)奋构,所以我們的協(xié)程依然會(huì)執(zhí)行壳影,這個(gè)時(shí)候我們就會(huì)出現(xiàn)內(nèi)存泄露和崩潰問(wèn)題。
通過(guò)上面的學(xué)習(xí)弥臼,我們已經(jīng)基本掌握了協(xié)程在Activity
和Fragment
中的使用方式宴咧。接下來(lái)我們講解在Viewmodel
中使用協(xié)程。
ViewModel中使用協(xié)程
如果我們想和在Activity
和Fragment
中一樣的簡(jiǎn)便径缅、快速的在ViewModel
使用協(xié)程掺栅。那么我們就需要集成下面這個(gè)官方的ViewModel
擴(kuò)展庫(kù)岭接。
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1"
復(fù)制代碼
與Activity
和Fragment
不同的是畜隶,在ViewModel
我們使用的不是lifecycleScope
茂嗓,而是使用viewModelScope
隔缀,使用viewModelScope
,使用viewModelScope
响谓。重要的事情說(shuō)三遍捌归。
這里一定要注意噢亚再,之前就有好幾個(gè)人問(wèn)我為什么在viewmodel
里面用不了協(xié)程鼠锈,我開始納悶半天咋就用不了呢闪檬。最后一問(wèn)結(jié)果是在ViewModel
使用lifecycleScope
,這樣做是不對(duì)滴购笆。
public 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)
)
}
internal class CloseableCoroutineScope(context: CoroutineContext) : Closeable, CoroutineScope {
override val coroutineContext: CoroutineContext = context
override fun close() {
coroutineContext.cancel()
}
}
復(fù)制代碼
viewModelScope
相比較lifecycleScope
實(shí)現(xiàn)會(huì)稍微簡(jiǎn)單一點(diǎn)粗悯。都是使用的SupervisorJob() + Dispatchers.Main
上下文,同時(shí)最終的取消操作也類似lifecycleScope
由桌,只不過(guò)viewModelScope
取消是在ViewModel
的銷毀的時(shí)候取消为黎。
final void clear() {
mCleared = true;
if (mBagOfTags != null) {
synchronized (mBagOfTags) {
for (Object value : mBagOfTags.values()) {
closeWithRuntimeException(value);
}
}
}
onCleared();
}
<T> T setTagIfAbsent(String key, T newValue) {
T previous;
synchronized (mBagOfTags) {
previous = (T) mBagOfTags.get(key);
if (previous == null) {
mBagOfTags.put(key, newValue);
}
}
T result = previous == null ? newValue : previous;
if (mCleared) {
closeWithRuntimeException(result);
}
return result;
}
private static void closeWithRuntimeException(Object obj) {
if (obj instanceof Closeable) {
try {
((Closeable) obj).close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
復(fù)制代碼
同樣的通過(guò)上面的總結(jié),我們也為ViewModel
擴(kuò)展一套常用的方法
inline fun ViewModel.requestMain(
errCode: Int = -1, errMsg: String = "", report: Boolean = false,
noinline block: suspend CoroutineScope.() -> Unit) {
viewModelScope.launch(GlobalCoroutineExceptionHandler(errCode, errMsg, report)) {
block.invoke(this)
}
}
inline fun ViewModel.requestIO(
errCode: Int = -1, errMsg: String = "", report: Boolean = false,
noinline block: suspend CoroutineScope.() -> Unit) {
viewModelScope.launch(Dispatchers.IO + GlobalCoroutineExceptionHandler(errCode, errMsg, report)) {
block.invoke(this)
}
}
inline fun ViewModel.delayMain(
errCode: Int = -1, errMsg: String = "", report: Boolean = false, delayTime: Long,
noinline block: suspend CoroutineScope.() -> Unit) {
viewModelScope.launch(GlobalCoroutineExceptionHandler(errCode, errMsg, report)) {
withContext(Dispatchers.IO) {
delay(delayTime)
}
block.invoke(this)
}
}
復(fù)制代碼
然后我們就可以愉快的在ViewModel
進(jìn)行使用協(xié)程了行您。
class MainViewModel:ViewModel() {
init {
requestMain {
Log.d("MainViewModel", "主線程中啟動(dòng)協(xié)程")
}
requestIO {
Log.d("MainViewModel", "IO線程中啟動(dòng)協(xié)程進(jìn)行網(wǎng)絡(luò)加載")
}
delayMain(100){
Log.d("MainViewModel", "主線程中啟動(dòng)協(xié)程并延時(shí)一定時(shí)間")
}
}
}
復(fù)制代碼
好了,常規(guī)使用協(xié)程的方式我都已經(jīng)學(xué)會(huì)剪廉。但是我們?cè)谝恍┉h(huán)境下如法使用使用lifecycleScope
和viewModelScope
的時(shí)候我們又該怎么辦娃循。比如:在Service
、Dialog
斗蒋、PopWindow
以及一些其他的環(huán)境中又該如何使用捌斧。
其他環(huán)境下使用協(xié)程
在這些環(huán)境中我們可以采用通用的方式進(jìn)行處理,其實(shí)還是根據(jù)協(xié)程作用域的差異分為兩類:
-
協(xié)同作用域
:這一類我們就模仿MainScope
自定義一個(gè)CoroutineScope
泉沾。 -
主從(監(jiān)督)作用域
:這一類我們直接使用MainScope
捞蚂,然后在此基礎(chǔ)上做一些擴(kuò)展即可。
如果對(duì)這兩個(gè)概念還不理解的跷究,麻煩移步到第二章節(jié)里面仔細(xì)閱讀一遍姓迅,這里就不再解釋。
我們接下來(lái)模仿MainScope
創(chuàng)建一個(gè)CoroutineScope
,它是在主線程下執(zhí)行丁存,并且它的Job
不是SupervisorJob
肩杈。
@Suppress("FunctionName")
public fun NormalScope(): CoroutineScope = CoroutineScope(Dispatchers.Main)
復(fù)制代碼
然后我再基于NormalScope
和MainScope
進(jìn)行使用。我們就以Service
為例來(lái)實(shí)現(xiàn)解寝。
abstract class BaseService :Service(){
private val normalScope = NormalScope()
override fun onDestroy() {
normalScope.cancel()
super.onDestroy()
}
protected fun requestMain(
errCode: Int = -1, errMsg: String = "", report: Boolean = false,
block: suspend CoroutineScope.() -> Unit) {
normalScope.launch(GlobalCoroutineExceptionHandler(errCode, errMsg, report)) {
block.invoke(this)
}
}
protected fun requestIO(
errCode: Int = -1, errMsg: String = "", report: Boolean = false,
block: suspend CoroutineScope.() -> Unit): Job {
return normalScope.launch(Dispatchers.IO + GlobalCoroutineExceptionHandler(errCode, errMsg, report)) {
block.invoke(this)
}
}
protected fun delayMain(
delayTime: Long,errCode: Int = -1, errMsg: String = "", report: Boolean = false,
block: suspend CoroutineScope.() -> Unit) {
normalScope.launch(GlobalCoroutineExceptionHandler(errCode, errMsg, report)) {
withContext(Dispatchers.IO) {
delay(delayTime)
}
block.invoke(this)
}
}
}
復(fù)制代碼
我們創(chuàng)建一個(gè)抽象類BaseService
類扩然,然后再定義一些基礎(chǔ)使用方法后,我就可以快速的使用了
class MainService : BaseService() {
override fun onBind(intent: Intent): IBinder? = null
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
requestIO {
//網(wǎng)絡(luò)加載
}
return super.onStartCommand(intent, flags, startId)
}
}
復(fù)制代碼
同理在Dialog
聋伦、PopWindow
以及一些其他的環(huán)境中可以依照此方法夫偶,定義符合我們自己需求的CoroutineScope
。一定要記得不要跨域使用觉增,以及及時(shí)的關(guān)閉協(xié)程兵拢。
又到了文章末尾,在此章節(jié)中我們已經(jīng)了解了協(xié)程結(jié)合Activity
抑片、Fragment
卵佛、Lifecycle
、Viewmodel
的基礎(chǔ)使用敞斋,以及如何簡(jiǎn)單的自定義一個(gè)協(xié)程截汪,如果還有不清楚的地方,可在下方留言植捎。
作者:一個(gè)被攝影耽誤的程序猿
鏈接:https://juejin.cn/post/6956115368578383902
來(lái)源:稀土掘金
著作權(quán)歸作者所有衙解。商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請(qǐng)注明出處焰枢。