Android版kotlin協程入門(四):kotlin協程開發實戰

南方吳彥祖_藍斯發表於2021-10-12

kotlin協程在Android中的基礎應用

透過前面的三個章節,現在我們已經瞭解了 kotlin協程的基本使用和相關基礎知識點。如:

  1. 協程的基礎使用方式和基本原理。
  2. CoroutineContext:協程上下文中包含的 Element以及下上文的作用,傳遞。
  3. CoroutineDispatcher:協程排程器的使用
  4. CoroutineStart:協程啟動模式在不同模式下的區別
  5. CoroutineScope:協程作用域的分類,以及不同作用域下的異常處理。
  6. 掛起函式以及 suspend關鍵字的作用,以及 Continuation的掛起恢復流程。
  7. CoroutineExceptionHandler:協程異常處理,結合 supervisorScopeSupervisorJob的使用。

這一章節中,我們將主要講解kotlin協程在Android中的基礎使用。我們先引入相關擴充套件庫元件庫:

    implementation "androidx.activity:activity-ktx:1.2.2"
    implementation "androidx.fragment:fragment-ktx:1.3.3"複製程式碼

Android使用kotlin協程

我們在之前的章節中使用協程的方式都是透過 runBlocking或者使用 GlobalScopelaunchasync方式啟動,當然也可以透過建立一個新的 CoroutineScope,然後透過 launch或者 async方式啟動一個新的協程。我們在講解 協程異常處理的篇章中就提到,透過 SupervisorJobCoroutineExceptionHandler實現了一個和 supervisorScope相同的作用域。

private fun testException(){
    val exceptionHandler = CoroutineExceptionHandler { coroutineContext, throwable ->
        Log.d("exceptionHandler", "${coroutineContext[CoroutineName].toString()} 處理異常 :$throwable")
    }
    val supervisorScope = CoroutineScope(SupervisorJob() + exceptionHandler)
    with(supervisorScope) {
        launch{
        }        //省略...
    }
}
複製程式碼

在第一節中我們提到 runBlocking它會將常規的阻塞程式碼連線到一起,主要用於main函式和測試中。而 GlobalScope又是一個全域性頂級協程,我們在之前的案例中基本都使用這種方式。但是這個協程是在整個應用程式生命週期內執行的,如果我們用 GlobalScope啟動協程,我們啟動一個將會變得極其繁瑣,而且需要對於各種引用的處理以及管控異常取消操作。

我們可以先忽略 CoroutineExceptionHandler協程異常處理。因為不管是任何方式啟動協程,如果不在程上下文中新增 CoroutineExceptionHandler,當產生未捕獲的異常時都會導致應用崩潰。

那麼下面程式碼會出現什麼問題?

private fun start() {
    GlobalScope.launch{
        launch {            //網路請求1...
            throw  NullPointerException("空指標")
        }
        val result = withContext(Dispatchers.IO) {            //網路請求2...
            requestData()            "請求結果"
        }
         btn.text = result
        launch {            //網路請求3...
        }
    }
}
複製程式碼
  • 因為我們的 GlobalScope預設使用的是 Dispatchers.Default,這會導致我們在非主執行緒上重新整理UI。

  • 子協程產生異常會產生相互干擾。子協程異常取消會導致父協程取消,同時其他子協程也將會被取消。

  • 如果我們這個時候 activity或者 framgent退出,因為協程是在 GlobalScope中執行,所以即使 activity或者 framgent退出,這個協程還是在執行,這個時候會產生各種洩露問題。同時此協程當執行到重新整理操作時,因為我們的介面已經銷燬,這個時候執行UI重新整理將會產生崩潰。

如果我們要解決上面的問題。我們得這麼做:

var job:Job? = nullprivate fun start() {
    job = GlobalScope.launch(Dispatchers.Main + SupervisorJob()) {
        launch {            throw  NullPointerException("空指標")
        }
        val result = withContext(Dispatchers.IO) {            //網路請求...
            "請求結果"
        }
        launch {            //網路請求3...
        }
        btn.text = result
    }
}override fun onDestroy() {    super.onDestroy()
    job?.cancel()
}
複製程式碼

我們先需要透過 launch啟動時加入 Dispatchers.Main來保證我們是在主執行緒重新整理UI,同時還需要再 GlobalScope.launch的協程上下文中加入 SupervisorJob來避免子協程的異常取消會導致整個協程樹被終結。 最後我們還得把 每次透過 GlobalScope啟動的 Job儲存下來,在 activity或者 framgent退出時呼叫 job.cancel取消整個協程樹。這麼來一遍感覺還行,但是我們不是寫一次啊,每次寫的時候會不會感覺超麻煩,甚至懷疑人生。

Android版kotlin協程入門(四):kotlin協程開發實戰

所以官方在kotlin協程中提供了一個預設在主執行緒執行的協程: MainScope,我們可以透過它來啟動協。

public fun MainScope(): CoroutineScope = ContextScope(SupervisorJob() + Dispatchers.Main)
複製程式碼

我們可以看到 MainScope的建立預設就使用了 SupervisorJob和  Dispatchers.Main。說明我們可以透過 MainScope來處理UI元件重新整理。同時由於 MainScope採用的是 SupervisorJob,所以我們各個子協程中的異常導致的取消操作並不會導致 MainScope的取消。這就很好的簡化了我們透過 GlobalScope去啟動一個協程的過程。

private val mainScope = MainScope()private fun start() {
    mainScope.launch {
        launch {            throw  NullPointerException("空指標")
        }
        val result = withContext(Dispatchers.IO) {            //網路請求...
            "請求結果"
        }
        launch {            //網路請求3...
        }
        btn.text = result
    }
} 
override fun onDestroy() {    super.onDestroy()
    mainScope.cancel()
}
複製程式碼

透過使用 MainScope我們是不是省略了很多操作。同時我們也不需要儲存每一個透過 MainScope啟動的 Job了,直接在最後銷燬的時候呼叫 mainScope.cancel()就能取消所有透過 mainScope啟動的協程。

這裡多提一點:可能這裡有的人會想,我使用 GlobalScope也不儲存啟動的 Job,直接 GlobalScope.cancel不行嗎?如果是這樣的話,那麼恭喜你喜提超級崩潰BUG一個。這裡就不擴充套件了。可以自己動手去試試,畢竟實踐出真理。

那可能還有人想,我連建立 MainScope都懶得寫,而且腦子經常不好使,容易忘記呼叫 mainScope進行 cancel操作怎麼辦。

Android版kotlin協程入門(四):kotlin協程開發實戰

官方早就為我們這些懶人想好了解決方案,這個時候我們只需要再整合一個ktx執行庫就可以了。

在Activity與Framgent中使用協程

    implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.3.1"複製程式碼

這個時候我們就可以在 activity或者 framgent直接使用 lifecycleScope進行啟動協程。我們看來看看activity中的 lifecycleScope實現

public val LifecycleOwner.lifecycleScope: LifecycleCoroutineScope    get() = lifecycle.coroutineScope
複製程式碼

我們可以到 lifecycleScope它是透過 lifecycle得到一個 coroutineScope,是一個 LifecycleCoroutineScope物件。

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
            }
        }
    }
複製程式碼

我們可以看到 lifecycleScope採用的和 MainScope一樣的建立 CoroutineScope,同時它又透過結合 lifecycle來實現當 lifecycle狀態處於 DESTROYED狀態的時候自動關閉所有的協程。

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()
        }
    }
}
複製程式碼

同時我們也可以透過 launchWhenCreatedlaunchWhenStartedlaunchWhenResumed來啟動協程,等到 lifecycle處於對應狀態時自動觸發此處建立的協程。

比如我們可以這麼操作:

class MainTestActivity : AppCompatActivity() {
    init {
        lifecycleScope.launchWhenResumed {
            Log.d("init", "在類初始化位置啟動協程")
        }
    }  override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
  }
}
複製程式碼
D/onResume: onResume
D/init: 在類初始化位置啟動協程
複製程式碼

按照我們正常情況載入順序,是不是應該 init先執行輸出?然而在實際情況中它是在等待 Activity進入 onResume狀態以後才執行接著看 launchWhenResumed中呼叫的 whenResumed實現。

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()
    }
}@MainThreadinternal 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()
    }
}
複製程式碼

我們可以看到,實際上是呼叫了 whenStateAtLeast,同時使用了 withContext進行了一個同步操作。然後在 LifecycleController中透過新增 LifecycleObserver來監聽狀態,透過 lifecycle當前狀態來對比我們設定的觸發狀態,最終決定是否恢復執行。

現在我們對於 Activity中的 lifecycleScope的建立以及銷燬流程有了一個大概的瞭解。同理 Fragment中的 lifecycleScope實現原理也是和 Activity是一樣的,這裡我們就不再重複講解。我們做個簡單的實驗:

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()
        }
    }
}
複製程式碼

這個時候是不是比之前的使用方式簡單多了,我們既不用關心建立過程,也不用關心銷燬的過程。

這個時候我們就需要提到 CoroutineExceptionHandler協程異常處理。透過之前的章節我們知道,啟動一個協程以後,如果未在協程上下文中新增 CoroutineExceptionHandler情況下,一旦產生了未捕獲的異常,那麼我們的程式將會崩潰退出。

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) {           //省略...
        }
    }
}
複製程式碼

當出現這種情況的時候,像筆者這種有嚴重偷懶情結的人就開始抓狂了。為什麼要寫這麼多遍  lifecycleScope.launch,同時每一次啟動都要手動新增 CoroutineExceptionHandler。難道就不能再簡便一點嗎?

Android版kotlin協程入門(四):kotlin協程開發實戰

當然可以,首先我們自定義一個異常處理,,我們在實現上只做一個簡單的異常日誌輸出:

/**
 * @param errCode 錯誤碼
 * @param errMsg 簡要錯誤資訊
 * @param report 是否需要上報
 */
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}")
    }
}
複製程式碼

然後我們在透過kotlin的擴充套件函式來簡化我們的使用,去掉重複寫 lifecycleScope.launchexceptionHandler的過程,我們就定義三個常用方法。

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)
    }
}
複製程式碼

這個時候我們就可以愉快的在 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(){        //網路載入
    }
}
複製程式碼

同樣的我們再擴充套件一套基於 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)
    }
}
複製程式碼

然後也可以愉快的在 Fragment中使用了

class HomeFragment:Fragment() {
    init {
        lifecycleScope.launchWhenCreated {
            Toast.makeText(context,"Fragment建立了", 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){            //...
        }
    }
}
複製程式碼

這裡需要提一下,可能有的人不太明白,為什麼要把 ActivityFragment都分開寫,他們都是使用的 lifecycleScope,我們直接透過 lifecycleScope擴充套件就不可以了嗎。假如我們這麼擴充套件:

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)
    }
}
複製程式碼

我們以 Dailog為例,來啟動一個協程:

val dialog = Dialog(this)
dialog.show()
(dialog.context as LifecycleOwner).lifecycleScope.requestMain { 
    withContext(Dispatchers.IO){        //網路載入
    }    // 重新整理UI}
dialog.cancel()
複製程式碼

那麼可能會出現一個什麼問題?是的,記憶體洩露的問題以及錯誤的引用問題。雖然我的 dialog被銷燬了,但是我們 lifecycleScope並不處於 DESTROYED狀態,所以我們的協程依然會執行,這個時候我們就會出現記憶體洩露和崩潰問題。

透過上面的學習,我們已經基本掌握了協程在 ActivityFragment中的使用方式。接下來我們講解在 Viewmodel中使用協程。

ViewModel中使用協程

如果我們想和在 ActivityFragment中一樣的簡便、快速的在 ViewModel使用協程。那麼我們就需要整合下面這個官方的 ViewModel擴充套件庫。

implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1"複製程式碼

ActivityFragment不同的是,在 ViewModel我們使用的不是 lifecycleScope,而是使用 viewModelScope,使用 viewModelScope,使用 viewModelScope。重要的事情說三遍。

這裡一定要注意噢,之前就有好幾個人問我為什麼在 viewmodel裡面用不了協程,我開始納悶半天咋就用不了呢。最後一問結果是 ViewModel使用 lifecycleScope這樣做是不對滴

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()
    }
}
複製程式碼

viewModelScope相比較 lifecycleScope實現會稍微簡單一點。都是使用的 SupervisorJob() + Dispatchers.Main上下文,同時最終的取消操作也類似 lifecycleScope,只不過 viewModelScope取消是在 ViewModel的銷燬的時候取消。

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);
        }
    }
}
複製程式碼

同樣的透過上面的總結,我們也為 ViewModel擴充套件一套常用的方法

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)
    }
}
複製程式碼

然後我們就可以愉快的在 ViewModel進行使用協程了。

class MainViewModel:ViewModel() {
    init {
        requestMain {
            Log.d("MainViewModel", "主執行緒中啟動協程")
        }
        requestIO {
            Log.d("MainViewModel", "IO執行緒中啟動協程進行網路載入")
        }
        delayMain(100){
            Log.d("MainViewModel", "主執行緒中啟動協程並延時一定時間")
        }
    }
}
複製程式碼

好了,常規使用協程的方式我都已經學會。但是我們在一些環境下如法使用使用 lifecycleScopeviewModelScope的時候我們又該怎麼辦。比如:在 ServiceDialogPopWindow以及一些其他的環境中又該如何使用。

Android版kotlin協程入門(四):kotlin協程開發實戰

其他環境下使用協程

在這些環境中我們可以採用通用的方式進行處理,其實還是根據協程作用域的差異分為兩類:

  • 協同作用域:這一類我們就模仿 MainScope自定義一個 CoroutineScope
  • 主從(監督)作用域:這一類我們直接使用 MainScope,然後在此基礎上做一些擴充套件即可。

如果對這兩個概念還不理解的,麻煩移步到第二章節裡面仔細閱讀一遍,這裡就不再解釋。

Android版kotlin協程入門(四):kotlin協程開發實戰

我們接下來模仿 MainScope建立一個 CoroutineScope,它是在主執行緒下執行,並且它的 Job不是 SupervisorJob

@Suppress("FunctionName")public fun NormalScope(): CoroutineScope = CoroutineScope(Dispatchers.Main)
複製程式碼

然後我再基於 NormalScopeMainScope進行使用。我們就以 Service為例來實現。

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)
        }
    }
}
複製程式碼

我們建立一個抽象類 BaseService類,然後再定義一些基礎使用方法後,我就可以快速的使用了

class MainService : BaseService() {
    override fun onBind(intent: Intent): IBinder?  = null
    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        requestIO {            //網路載入
        }        return super.onStartCommand(intent, flags, startId)
    }
}
複製程式碼

同理在 DialogPopWindow以及一些其他的環境中可以依照此方法,定義符合我們自己需求的 CoroutineScope一定要記得不要跨域使用,以及及時的關閉協程。

又到了文章末尾,在此章節中我們已經瞭解了協程結合 ActivityFragmentLifecycleViewmodel的基礎使用,以及如何簡單的自定義一個協程,如果還有不清楚的地方,可在下方留言。

更多Android技術分享可以關注@我,也可以加入QQ群號:Android進階學習群:345659112,一起學習交流。

作者:一個被攝影耽誤的程式猿
來源:稀土掘金
著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69983917/viewspace-2795553/,如需轉載,請註明出處,否則將追究法律責任。

相關文章