Android版kotlin協(xié)程入門(四):kotlin協(xié)程開發(fā)實(shí)戰(zhàn)

kotlin協(xié)程在Android中的基礎(chǔ)應(yīng)用

通過(guò)前面的三個(gè)章節(jié),現(xiàn)在我們已經(jīng)了解了kotlin協(xié)程的基本使用和相關(guān)基礎(chǔ)知識(shí)點(diǎn)。如:

  1. 協(xié)程的基礎(chǔ)使用方式和基本原理宾袜。
  2. CoroutineContext:協(xié)程上下文中包含的Element以及下上文的作用叉谜,傳遞错敢。
  3. CoroutineDispatcher:協(xié)程調(diào)度器的使用
  4. CoroutineStart:協(xié)程啟動(dòng)模式在不同模式下的區(qū)別
  5. CoroutineScope:協(xié)程作用域的分類践樱,以及不同作用域下的異常處理厂画。
  6. 掛起函數(shù)以及suspend關(guān)鍵字的作用,以及Continuation的掛起恢復(fù)流程拷邢。
  7. CoroutineExceptionHandler:協(xié)程異常處理袱院,結(jié)合supervisorScopeSupervisorJob的使用。

這一章節(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或者使用GlobalScopelaunchasync方式啟動(dòng)环肘,當(dāng)然也可以通過(guò)創(chuàng)建一個(gè)新的CoroutineScope,然后通過(guò)launch或者async方式啟動(dòng)一個(gè)新的協(xié)程欲虚。我們?cè)谥v解協(xié)程異常處理的篇章中就提到,通過(guò)SupervisorJobCoroutineExceptionHandler實(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)就使用了SupervisorJobDispatchers.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.launchexceptionHandler的過(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ù)制代碼

這里需要提一下,可能有的人不太明白,為什么要把ActivityFragment都分開寫嚣伐,他們都是使用的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é)程在ActivityFragment中的使用方式宴咧。接下來(lái)我們講解在Viewmodel中使用協(xié)程。

ViewModel中使用協(xié)程

如果我們想和在ActivityFragment中一樣的簡(jiǎn)便径缅、快速的在ViewModel使用協(xié)程掺栅。那么我們就需要集成下面這個(gè)官方的ViewModel擴(kuò)展庫(kù)岭接。

implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1"
復(fù)制代碼

ActivityFragment不同的是畜隶,在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)境下如法使用使用lifecycleScopeviewModelScope的時(shí)候我們又該怎么辦娃循。比如:在ServiceDialog斗蒋、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ù)制代碼

然后我再基于NormalScopeMainScope進(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卵佛、LifecycleViewmodel的基礎(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)注明出處焰枢。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末蚓峦,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子济锄,更是在濱河造成了極大的恐慌暑椰,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,546評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件荐绝,死亡現(xiàn)場(chǎng)離奇詭異一汽,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)低滩,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,224評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門召夹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人恕沫,你說(shuō)我怎么就攤上這事监憎。” “怎么了婶溯?”我有些...
    開封第一講書人閱讀 164,911評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵鲸阔,是天一觀的道長(zhǎng)偷霉。 經(jīng)常有香客問(wèn)我,道長(zhǎng)隶债,這世上最難降的妖魔是什么腾它? 我笑而不...
    開封第一講書人閱讀 58,737評(píng)論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮死讹,結(jié)果婚禮上瞒滴,老公的妹妹穿的比我還像新娘。我一直安慰自己赞警,他們只是感情好妓忍,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,753評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著愧旦,像睡著了一般世剖。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上笤虫,一...
    開封第一講書人閱讀 51,598評(píng)論 1 305
  • 那天旁瘫,我揣著相機(jī)與錄音,去河邊找鬼琼蚯。 笑死酬凳,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的遭庶。 我是一名探鬼主播宁仔,決...
    沈念sama閱讀 40,338評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼峦睡!你這毒婦竟也來(lái)了翎苫?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,249評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤榨了,失蹤者是張志新(化名)和其女友劉穎煎谍,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體龙屉,經(jīng)...
    沈念sama閱讀 45,696評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡粱快,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,888評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了叔扼。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,013評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡漫雷,死狀恐怖瓜富,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情降盹,我是刑警寧澤与柑,帶...
    沈念sama閱讀 35,731評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響价捧,放射性物質(zhì)發(fā)生泄漏丑念。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,348評(píng)論 3 330
  • 文/蒙蒙 一结蟋、第九天 我趴在偏房一處隱蔽的房頂上張望脯倚。 院中可真熱鬧,春花似錦嵌屎、人聲如沸推正。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,929評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)植榕。三九已至,卻和暖如春尼夺,著一層夾襖步出監(jiān)牢的瞬間尊残,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,048評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工淤堵, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留寝衫,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,203評(píng)論 3 370
  • 正文 我出身青樓粘勒,卻偏偏與公主長(zhǎng)得像竞端,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子庙睡,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,960評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容