Kotlin 協(xié)程與架構(gòu)組件一起使用及底層原理分析

kotlin的協(xié)程封裝了線程的API库物,這個線程框架可以讓我們很方便得編寫異步代碼。

雖然協(xié)程已經(jīng)很方便了侨核,但是如果再配合Google提供的架構(gòu)組件的KTX擴展一起使用,那就更方便了灌灾。

1. 添加KTX依賴

//將 Kotlin 協(xié)程與架構(gòu)組件一起使用

//ViewModelScope
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1'
//LifecycleScope
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.2.0'
//liveData
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.2.0'

2. viewModelScope

2.1 用老方式在ViewModel中使用協(xié)程

在使用ViewModelScope之前搓译,先來回顧一下以前在ViewModel中使用協(xié)程的方式。自己管理CoroutineScope紧卒,在不需要的時候(一般是在onCleared())進行取消侥衬。否則,可能造成資源浪費跑芳、內(nèi)存泄露等問題。

class JetpackCoroutineViewModel : ViewModel() {
    //在這個ViewModel中使用協(xié)程時,需要使用這個job來方便控制取消
    private val viewModelJob = SupervisorJob()
    
    //指定協(xié)程在哪里執(zhí)行,并且可以由viewModelJob很方便地取消uiScope
    private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)
    
    fun launchDataByOldWay() {
        uiScope.launch {
            //在后臺執(zhí)行
            val result = getNetData()
            //修改UI
            log(result)
        }
    }
    
    override fun onCleared() {
        super.onCleared()
        viewModelJob.cancel()
    }
    
    //將耗時任務(wù)切到IO線程去執(zhí)行
    private suspend fun getNetData() = withContext(Dispatchers.IO) {
        //模擬網(wǎng)絡(luò)耗時
        delay(1000)
        //模擬返回結(jié)果
        "{}"
    }
}

看起來有很多的樣板代碼直颅,而且在不需要的時候取消協(xié)程很容易忘博个。

2.2 新方式在ViewModel中使用協(xié)程

正是在這種情況下,Google為我們創(chuàng)造了ViewModelScope功偿,它通過向ViewModel類添加擴展屬性來方便我們使用協(xié)程盆佣,而且在ViewModel被銷毀時會自動取消其子協(xié)程。

class JetpackCoroutineViewModel : ViewModel() {
    fun launchData() {
        viewModelScope.launch {
            //在后臺執(zhí)行
            val result = getNetData()
            //修改UI
            log(result)
        }
    }

    //將耗時任務(wù)切到IO線程去執(zhí)行
    private suspend fun getNetData() = withContext(Dispatchers.IO) {
        //模擬網(wǎng)絡(luò)耗時
        delay(1000)
        //模擬返回結(jié)果
        "{}"
    }

}

所有CoroutineScope的初始化和取消都已經(jīng)為我們完成了械荷,只需要在代碼里面使用viewModelScope即可開啟一個新協(xié)程共耍,而且還不用擔心忘記取消的問題。

下面我們來看看Google是怎么實現(xiàn)的吨瞎。

2.3 viewModelScope的底層實現(xiàn)

點進去看看源碼痹兜,知根知底,萬一后面遇到什么奇怪的bug颤诀,在知道原理的情況下字旭,才能更快的想到解決辦法。

private const val JOB_KEY = "androidx.lifecycle.ViewModelCoroutineScope.JOB_KEY"

/**
 * [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]
 */
public val ViewModel.viewModelScope: CoroutineScope
    get() {
        //先從緩存中取值崖叫,有就直接返回
        val scope: CoroutineScope? = this.getTag(JOB_KEY)
        if (scope != null) {
            return scope
        }
        //沒有緩存就新建一個CloseableCoroutineScope
        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是什么遗淳,它其實是一個ViewModel的擴展屬性,它的實際類型是CloseableCoroutineScope心傀。這名字看起來就是一個可以取消的協(xié)程屈暗,果不其然,它實現(xiàn)了Closeable并在close方法中進行了取消脂男。

每次在使用viewModelScope的時候养叛,會先從緩存中取,如果沒有才去新建一個CloseableCoroutineScope疆液。需要注意的是一铅,CloseableCoroutineScope的執(zhí)行是在主線程中執(zhí)行的。

我們現(xiàn)在需要知道的是緩存是怎么存儲和取出的堕油。

//ViewModel.java

// Can't use ConcurrentHashMap, because it can lose values on old apis (see b/37042460)
@Nullable
private final Map<String, Object> mBagOfTags = new HashMap<>();
/**
 * Returns the tag associated with this viewmodel and the specified key.
 */
@SuppressWarnings({"TypeParameterUnusedInFormals", "unchecked"})
<T> T getTag(String key) {
    if (mBagOfTags == null) {
        return null;
    }
    synchronized (mBagOfTags) {
        return (T) mBagOfTags.get(key);
    }
}

/**
 * Sets a tag associated with this viewmodel and a key.
 * If the given {@code newValue} is {@link Closeable},
 * it will be closed once {@link #clear()}.
 * <p>
 * If a value was already set for the given key, this calls do nothing and
 * returns currently associated value, the given {@code newValue} would be ignored
 * <p>
 * If the ViewModel was already cleared then close() would be called on the returned object if
 * it implements {@link Closeable}. The same object may receive multiple close calls, so method
 * should be idempotent.
 */
@SuppressWarnings("unchecked")
<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) {
        // It is possible that we'll call close() multiple times on the same object, but
        // Closeable interface requires close method to be idempotent:
        // "if the stream is already closed then invoking this method has no effect." (c)
        closeWithRuntimeException(result);
    }
    return result;
}

現(xiàn)在我們知道了潘飘,原來是存在了ViewModel的mBagOfTags中肮之,它是一個HashMap。

知道了怎么存的卜录,那么它是在什么時候用的呢戈擒?

@MainThread
final void clear() {
    mCleared = true;
    // Since clear() is final, this method is still called on mock objects
    // and in those cases, mBagOfTags is null. It'll always be empty though
    // because setTagIfAbsent and getTag are not final so we can skip
    // clearing it
    if (mBagOfTags != null) {
        synchronized (mBagOfTags) {
            for (Object value : mBagOfTags.values()) {
                // see comment for the similar call in setTagIfAbsent
                closeWithRuntimeException(value);
            }
        }
    }
    onCleared();
}

private static void closeWithRuntimeException(Object obj) {
    if (obj instanceof Closeable) {
        try {
            ((Closeable) obj).close();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

我在ViewModel中搜索了一下mBagOfTags,發(fā)現(xiàn)有一個clear方法艰毒,在里面將mBagOfTags遍歷一遍筐高,然后將所有value是Closeable的全部close。在上面的源碼中丑瞧,第一次使用viewModelScope的時候柑土,會創(chuàng)建一個CloseableCoroutineScope,它實現(xiàn)了Closeable接口绊汹,并實現(xiàn)了close方法稽屏,剛好用來做取消操作。

看到這里西乖,我們知道了:viewModelScope構(gòu)建的協(xié)程是在ViewModel的clear方法回調(diào)時取消協(xié)程的狐榔。

而且,clear方法里面居然還有我們熟悉的onCleared方法調(diào)用获雕。而onCleared我們知道是干什么的薄腻,當這個ViewModel不再使用時會回調(diào)這個方法,一般我們需要在此方法中做一些收尾工作届案,如取消觀察者訂閱庵楷、關(guān)閉資源之類的。

那么萝玷,大膽猜測一下嫁乘,這個clear()方法應(yīng)該也是在ViewModel要結(jié)束生命的時候調(diào)用的。

搜索了一下球碉,發(fā)現(xiàn)clear方法是在ViewModelStore里面調(diào)用的蜓斧。

public class ViewModelStore {

    private final HashMap<String, ViewModel> mMap = new HashMap<>();

    final void put(String key, ViewModel viewModel) {
        ViewModel oldViewModel = mMap.put(key, viewModel);
        if (oldViewModel != null) {
            oldViewModel.onCleared();
        }
    }

    final ViewModel get(String key) {
        return mMap.get(key);
    }

    Set<String> keys() {
        return new HashSet<>(mMap.keySet());
    }

    /**
     *  Clears internal storage and notifies ViewModels that they are no longer used.
     */
    public final void clear() {
        for (ViewModel vm : mMap.values()) {
            vm.clear();
        }
        mMap.clear();
    }
}

ViewModelStore是一個容器,用于盛放ViewModel睁冬。在ViewModelStore的clear方法中調(diào)用了該ViewModelStore中所有ViewModel的clear方法挎春。那么ViewModelStore的clear是在哪里調(diào)用的?我跟著追蹤豆拨,發(fā)現(xiàn)是在ComponentActivity的構(gòu)造方法中直奋。

public ComponentActivity() {
    Lifecycle lifecycle = getLifecycle();
    getLifecycle().addObserver(new LifecycleEventObserver() {
        @Override
        public void onStateChanged(@NonNull LifecycleOwner source,
                @NonNull Lifecycle.Event event) {
            if (event == Lifecycle.Event.ON_DESTROY) {
                if (!isChangingConfigurations()) {
                    getViewModelStore().clear();
                }
            }
        }
    });
}

在Activity的生命周期走到onDestroy的時候調(diào)用ViewModelStore的clear做收尾工作。但是施禾,注意一下脚线,這個調(diào)用有個前提,此次走onDestroy不是因為配置更改才會去調(diào)用clear方法弥搞。

好的邮绿,到此為止渠旁,咱們理通了viewModelScope的協(xié)程是怎么做到自動取消的(ViewModel的mBagOfTags),以及是在什么時候進行取消的(ViewModel的clear()時)船逮。

3. lifecycleScope

對于Lifecycle顾腊,Google貼心地提供了LifecycleScope,我們可以直接通過launch來創(chuàng)建Coroutine挖胃。

3.1 使用

舉個簡單例子杂靶,比如在Activity的onCreate里面,每隔100毫秒更新一下TextView的文字。

lifecycleScope.launch {
    repeat(100000) {
        delay(100)
        tvText.text = "$it"
    }
}

因為LifeCycle是可以感知組件的生命周期的酱鸭,所以Activity一旦onDestroy了吗垮,相應(yīng)的上面這個lifecycleScope。launch閉包的調(diào)用也會取消凹髓。

另外抱既,lifecycleScope還貼心地提供了launchWhenCreated、launchWhenStarted扁誓、launchWhenResumed方法,這些方法的閉包里面有協(xié)程的作用域蝗敢,它們分別是在CREATED、STARTED足删、RESUMED時被執(zhí)行寿谴。

//方式1
lifecycleScope.launchWhenStarted {
    repeat(100000) {
        delay(100)
        tvText.text = "$it"
    }
}
//方式2
lifecycleScope.launch {
    whenStarted { 
        repeat(100000) {
            delay(100)
            tvText.text = "$it"
        }
    }
}

不管是直接調(diào)用launchWhenStarted還是在launch中調(diào)用whenStarted都能達到同樣的效果。

3.2 LifecycleScope的底層實現(xiàn)

先來看下lifecycleScope.launch是怎么做到的

/**
 * [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

好家伙失受,又是擴展屬性讶泰。這次擴展的是LifecycleOwner,返回了一個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
            }
        }
    }

Lifecycle的coroutineScope也是擴展屬性狼犯,它是一個LifecycleCoroutineScope。從注釋可以看到领铐,在Lifecycle被銷毀之后悯森,這個協(xié)程會跟著取消。這里首先會從mInternalScopeRef中取之前存入的緩存绪撵,如果沒有再生成一個LifecycleCoroutineScopeImpl放進去瓢姻,并調(diào)用LifecycleCoroutineScopeImpl的register函數(shù)。這里的mInternalScopeRef是Lifecycle類里面的一個屬性: AtomicReference<Object> mInternalScopeRef = new AtomicReference<>(); (AtomicReference可以讓一個對象保證原子性)音诈。這里使用AtomicReference當然是為了線程安全幻碱。

既然生成的是LifecycleCoroutineScopeImpl绎狭,那么就先來看看這個東西是什么

internal class LifecycleCoroutineScopeImpl(
    override val lifecycle: Lifecycle,
    override val coroutineContext: CoroutineContext
) : LifecycleCoroutineScope(), LifecycleEventObserver {
    init {
        // in case we are initialized on a non-main thread, make a best effort check before
        // we return the scope. This is not sync but if developer is launching on a non-main
        // dispatcher, they cannot be 100% sure anyways.
        if (lifecycle.currentState == Lifecycle.State.DESTROYED) {
            coroutineContext.cancel()
        }
    }

    fun register() {
        //啟了個協(xié)程,當前Lifecycle的state大于等于INITIALIZED收班,就注冊一下Lifecycle的觀察者坟岔,觀察生命周期
        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) {
        //觀察到當前生命周期小于等于DESTROYED,那么就移除當前這個觀察者并且取消協(xié)程
        if (lifecycle.currentState <= Lifecycle.State.DESTROYED) {
            lifecycle.removeObserver(this)
            coroutineContext.cancel()
        }
    }
}

在上面的代碼中摔桦,有2個重要的函數(shù):register和onStateChanged社付。register函數(shù)是在初始化LifecycleCoroutineScopeImpl的時候調(diào)用的,先在register函數(shù)中添加一個觀察者用于觀察生命周期的變化邻耕,然后在onStateChanged函數(shù)中判斷生命周期到DESTROYED時就移除觀察者并且取消協(xié)程鸥咖。

有個小細節(jié),為啥register函數(shù)中能直接啟協(xié)程兄世?是因為LifecycleCoroutineScopeImpl繼承了LifecycleCoroutineScope,啼辣,而LifecycleCoroutineScope實現(xiàn)了CoroutineScope接口(其實是在LifecycleCoroutineScopeImpl中實現(xiàn)的)。

public abstract class LifecycleCoroutineScope internal constructor() : CoroutineScope {
    internal abstract val lifecycle: Lifecycle
    ......
}

現(xiàn)在我們流程理清楚了御滩,lifecycleScope使用時會構(gòu)建一個協(xié)程鸥拧,同時會觀察組件的生命周期,在適當?shù)臅r機(DESTROYED)取消協(xié)程削解。

在上面的實例我們見過一段代碼:

//方式1
lifecycleScope.launchWhenStarted {
    repeat(100000) {
        delay(100)
        tvText.text = "$it"
    }
}
//方式2
lifecycleScope.launch {
    whenStarted { 
        repeat(100000) {
            delay(100)
            tvText.text = "$it"
        }
    }
}

可以直接通過lifecycleScope提供的launchWhenCreated富弦、launchWhenStarted、launchWhenResumed在相應(yīng)的生命周期時執(zhí)行協(xié)程氛驮。

點進去看一下

abstract class LifecycleCoroutineScope internal constructor() : CoroutineScope {
    internal abstract val lifecycle: Lifecycle

    /**
     * Launches and runs the given block when the [Lifecycle] controlling this
     * [LifecycleCoroutineScope] is at least in [Lifecycle.State.CREATED] state.
     *
     * The returned [Job] will be cancelled when the [Lifecycle] is destroyed.
     * @see Lifecycle.whenCreated
     * @see Lifecycle.coroutineScope
     */
    fun launchWhenCreated(block: suspend CoroutineScope.() -> Unit): Job = launch {
        lifecycle.whenCreated(block)
    }

    /**
     * Launches and runs the given block when the [Lifecycle] controlling this
     * [LifecycleCoroutineScope] is at least in [Lifecycle.State.STARTED] state.
     *
     * The returned [Job] will be cancelled when the [Lifecycle] is destroyed.
     * @see Lifecycle.whenStarted
     * @see Lifecycle.coroutineScope
     */

    fun launchWhenStarted(block: suspend CoroutineScope.() -> Unit): Job = launch {
        lifecycle.whenStarted(block)
    }

    /**
     * Launches and runs the given block when the [Lifecycle] controlling this
     * [LifecycleCoroutineScope] is at least in [Lifecycle.State.RESUMED] state.
     *
     * The returned [Job] will be cancelled when the [Lifecycle] is destroyed.
     * @see Lifecycle.whenResumed
     * @see Lifecycle.coroutineScope
     */
    fun launchWhenResumed(block: suspend CoroutineScope.() -> Unit): Job = launch {
        lifecycle.whenResumed(block)
    }
}

原來這些函數(shù)就是LifecycleOwner的擴展屬性lifecycleScope所返回的LifecycleCoroutineScope類里面的函數(shù)腕柜。這幾個函數(shù)里面啥也沒干,直接調(diào)用了lifecycle對應(yīng)的函數(shù)

/**
 * Runs the given block when the [Lifecycle] is at least in [Lifecycle.State.CREATED] state.
 *
 * @see Lifecycle.whenStateAtLeast for details
 */
suspend fun <T> Lifecycle.whenCreated(block: suspend CoroutineScope.() -> T): T {
    return whenStateAtLeast(Lifecycle.State.CREATED, block)
}

/**
 * Runs the given block when the [Lifecycle] is at least in [Lifecycle.State.STARTED] state.
 *
 * @see Lifecycle.whenStateAtLeast for details
 */
suspend fun <T> Lifecycle.whenStarted(block: suspend CoroutineScope.() -> T): T {
    return whenStateAtLeast(Lifecycle.State.STARTED, block)
}

/**
 * Runs the given block when the [Lifecycle] is at least in [Lifecycle.State.RESUMED] state.
 *
 * @see Lifecycle.whenStateAtLeast for details
 */
suspend fun <T> Lifecycle.whenResumed(block: suspend CoroutineScope.() -> T): T {
    return whenStateAtLeast(Lifecycle.State.RESUMED, block)
}

這幾個函數(shù)原來是suspend函數(shù)矫废,并且是擴展Lifecycle的函數(shù)盏缤。它們最終都調(diào)用到了whenStateAtLeast函數(shù),并傳入了執(zhí)行協(xié)程的最小的生命周期狀態(tài)標志(minState)蓖扑。

suspend fun <T> Lifecycle.whenStateAtLeast(
    minState: Lifecycle.State,
    block: suspend CoroutineScope.() -> 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 {
        //執(zhí)行協(xié)程
        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) {
            //DESTROYED->取消協(xié)程
            handleDestroy(parentJob)
        } else if (source.lifecycle.currentState < minState) {
            dispatchQueue.pause()
        } else {
            //執(zhí)行
            dispatchQueue.resume()
        }
    }

    init {
        // If Lifecycle is already destroyed (e.g. developer leaked the lifecycle), we won't get
        // an event callback so we need to check for it before registering
        // see: b/128749497 for details.
        if (lifecycle.currentState == Lifecycle.State.DESTROYED) {
            handleDestroy(parentJob)
        } else {
            //觀察生命周期變化
            lifecycle.addObserver(observer)
        }
    }

    @Suppress("NOTHING_TO_INLINE") // avoid unnecessary method
    private inline fun handleDestroy(parentJob: Job) {
        parentJob.cancel()
        finish()
    }

    /**
     * Removes the observer and also marks the [DispatchQueue] as finished so that any remaining
     * runnables can be executed.
     */
    @MainThread
    fun finish() {
        //移除生命周期觀察者
        lifecycle.removeObserver(observer)
        //標記已完成 并執(zhí)行剩下的可執(zhí)行的Runnable
        dispatchQueue.finish()
    }
}

whenStateAtLeast也是一個Lifecycle的擴展函數(shù)唉铜,核心邏輯是在LifecycleController中添加了LifecycleObserver來監(jiān)聽生命周期狀態(tài),通過狀態(tài)來決定是暫停執(zhí)行還是恢復執(zhí)行赵誓,或者是取消執(zhí)行打毛。當執(zhí)行完成之后,也就是finally那里俩功,從執(zhí)行LifecycleController的finish進行收尾工作:移除生命周期監(jiān)聽幻枉,開始執(zhí)行余下的任務(wù)。

執(zhí)行完成一次诡蜓,就會移除生命周期觀察者熬甫,相當于我們寫到launchWhenResumed之類的函數(shù)里面的閉包只會被執(zhí)行一次。執(zhí)行完成之后蔓罚,即使再經(jīng)過onPause->onResume也不會再次執(zhí)行椿肩。

4. liveData

在我們平時使用LiveData的過程中瞻颂,可能會涉及到這種場景:去請求網(wǎng)絡(luò)拿結(jié)果,然后通過LiveData將數(shù)據(jù)轉(zhuǎn)出去郑象,在Activity里面收到通知倒谷,然后更新UI强经。非常常見的場景变姨,這種情況下纽匙,我們可以通過官方的liveData構(gòu)造器函數(shù)來簡化上面的場景代碼。

4.1 使用

val netData: LiveData<String> = liveData {
    //觀察的時候在生命周期內(nèi),則會馬上執(zhí)行
    val data = getNetData()
    emit(data)
}

//將耗時任務(wù)切到IO線程去執(zhí)行
private suspend fun getNetData() = withContext(Dispatchers.IO) {
    //模擬網(wǎng)絡(luò)耗時
    delay(5000)
    //模擬返回結(jié)果
    "{}"
}

在上面的例子中击奶,getNetData()是一個suspend函數(shù)辈双。使用LiveData構(gòu)造器函數(shù)異步調(diào)用getNetData(),然后使用emit()提交結(jié)果柜砾。在Activity那邊如果觀察了這個netData湃望,并且處于活動狀態(tài),那么就會收到結(jié)果痰驱。我們知道证芭,suspend函數(shù)需要在協(xié)程作用域中調(diào)用,所以liveData的閉包里面也是有協(xié)程作用域的担映。

有個小細節(jié)檩帐,如果組件在觀察此netData時剛好處于活動狀態(tài),那么liveData閉包里面的代碼會立刻執(zhí)行另萤。

除了上面這種用法,還可以在liveData里面發(fā)出多個值诅挑。

val netData2: LiveData<String> = liveData {
    delay(3000)
    val source = MutableLiveData<String>().apply {
        value = "11111"
    }
    val disposableHandle = emitSource(source)

    delay(3000)
    disposableHandle.dispose()
    val source2 = MutableLiveData<String>().apply {
        value = "22222"
    }
    val disposableHandle2 = emitSource(source2)
}

需要注意的是四敞,后一個調(diào)用emitSource的時候需要把前一個emitSource的返回值調(diào)用一下dispose函數(shù),切斷拔妥。

4.2 liveData的底層實現(xiàn)

老規(guī)矩忿危,Ctrl+鼠標左鍵 點進去看源碼

@UseExperimental(ExperimentalTypeInference::class)
fun <T> liveData(
    context: CoroutineContext = EmptyCoroutineContext,
    timeoutInMs: Long = DEFAULT_TIMEOUT,
    @BuilderInference block: suspend LiveDataScope<T>.() -> Unit
): LiveData<T> = CoroutineLiveData(context, timeoutInMs, block)

//咱們在liveData后面的閉包里面寫的代碼就是傳給了這里的block,它是一個suspend函數(shù)没龙,有LiveDataScope的上下文

首先铺厨,映入眼簾的是liveData函數(shù)居然是一個全局函數(shù),這意味著你可以在任何地方使用它硬纤,而不局限于Activity或者ViewModel里面解滓。

其次,liveData函數(shù)返回的是一個CoroutineLiveData對象筝家?居然返回的是一個對象洼裤,沒有在這里執(zhí)行任何代碼。那我的代碼是在哪里執(zhí)行的溪王?

這就得看CoroutineLiveData類的代碼了

internal class CoroutineLiveData<T>(
    context: CoroutineContext = EmptyCoroutineContext,
    timeoutInMs: Long = DEFAULT_TIMEOUT,
    block: Block<T>
) : MediatorLiveData<T>() {
    private var blockRunner: BlockRunner<T>?
    private var emittedSource: EmittedSource? = null

    init {
        // use an intermediate supervisor job so that if we cancel individual block runs due to losing
        // observers, it won't cancel the given context as we only cancel w/ the intention of possibly
        // relaunching using the same parent context.
        val supervisorJob = SupervisorJob(context[Job])

        // The scope for this LiveData where we launch every block Job.
        // We default to Main dispatcher but developer can override it.
        // The supervisor job is added last to isolate block runs.
        val scope = CoroutineScope(Dispatchers.Main.immediate + context + supervisorJob)
        blockRunner = BlockRunner(
            liveData = this,
            block = block,
            timeoutInMs = timeoutInMs,
            scope = scope
        ) {
            blockRunner = null
        }
    }

    internal suspend fun emitSource(source: LiveData<T>): DisposableHandle {
        clearSource()
        val newSource = addDisposableSource(source)
        emittedSource = newSource
        return newSource
    }

    internal suspend fun clearSource() {
        emittedSource?.disposeNow()
        emittedSource = null
    }

    override fun onActive() {
        super.onActive()
        blockRunner?.maybeRun()
    }

    override fun onInactive() {
        super.onInactive()
        blockRunner?.cancel()
    }
}

里面代碼比較少腮鞍,主要就是繼承了MediatorLiveData值骇,然后在onActive的時候執(zhí)行BlockRunner的maybeRun函數(shù)。BlockRunner的maybeRun里面執(zhí)行的實際上就是我們在liveData里面寫的代碼塊移国,而onActive方法實際上是從LiveData那里繼承過來的吱瘩,當有一個處于活躍狀態(tài)的觀察者監(jiān)聽LiveData時會被調(diào)用。

這就解釋得通了迹缀,我上面的案例中是在Activity的onCreate(處于活躍狀態(tài))里面觀察了netData使碾,所以liveData里面的代碼會被立刻執(zhí)行。


//typealias 類型別名
//在下面的BlockRunner中會使用到這個裹芝,這個東西用于承載我們在liveData后面閉包里面的代碼
internal typealias Block<T> = suspend LiveDataScope<T>.() -> Unit

/**
 * Handles running a block at most once to completion.
 */
internal class BlockRunner<T>(
    private val liveData: CoroutineLiveData<T>,
    private val block: Block<T>,
    private val timeoutInMs: Long,
    private val scope: CoroutineScope,
    private val onDone: () -> Unit
) {
    @MainThread
    fun maybeRun() {
       ...
        //這里的scope是CoroutineScope(Dispatchers.Main.immediate + context + supervisorJob)
        runningJob = scope.launch {
            val liveDataScope = LiveDataScopeImpl(liveData, coroutineContext)
            //這里的block執(zhí)行的是我們在liveData里面寫的代碼部逮,在執(zhí)行block時將liveDataScope實例傳入,liveDataScope上下文有了
            block(liveDataScope)
            //完成
            onDone()
        }
    }
    ...
}

internal class LiveDataScopeImpl<T>(
    internal var target: CoroutineLiveData<T>,
    context: CoroutineContext
) : LiveDataScope<T> {

    ...
    // use `liveData` provided context + main dispatcher to communicate with the target
    // LiveData. This gives us main thread safety as well as cancellation cooperation
    private val coroutineContext = context + Dispatchers.Main.immediate
    
    //因為在執(zhí)行l(wèi)iveData閉包的時候有LiveDataScopeImpl上下文嫂易,所以可以使用emit函數(shù)
    override suspend fun emit(value: T) = withContext(coroutineContext) {
        target.clearSource()
        //給target這個LiveData設(shè)置value兄朋,而liveData返回的就是這個target,在組件中觀察此target怜械,就會收到這里的value數(shù)據(jù)
        target.value = value
    }
}

在BlockRunner的maybeRun函數(shù)的里面啟了個協(xié)程颅和,這個scope是在CoroutineLiveData里面初始化的:CoroutineScope(Dispatchers.Main.immediate + context + supervisorJob),接著就是在這個scope里面執(zhí)行我們在liveData后面閉包里面寫的代碼缕允,并且有LiveDataScopeImpl的上下文峡扩。有了LiveDataScopeImpl的上下文,那么我們就可以使用LiveDataScopeImpl里面的emit方法障本,而emit方法其實很簡單教届,就是將一個數(shù)據(jù)交給一個LiveData對象,而這個LiveData就是liveData{}返回的那個驾霜。此時因為LiveData的數(shù)據(jù)發(fā)生了變化案训,如果有組件觀察了該LiveData并且該組件處于活動狀態(tài),那么該組件就會收到數(shù)據(jù)發(fā)生變化的回調(diào)粪糙。

整個過程大致流程就是在liveData{}里面構(gòu)建了一個協(xié)程强霎,返回了一個LiveData,然后我們寫的閉包里面的代碼實際上是在一個協(xié)程里面執(zhí)行的蓉冈,我們調(diào)用emit方法時就是在更新這個LiveData里面的value城舞。既然是返回的LiveData,那么天然就與組件生命周期關(guān)聯(lián)上了寞酿,只有在組件處于活動狀態(tài)才能得到結(jié)果家夺,而且也避免了一些內(nèi)存泄露的問題。

5. 小結(jié)

不得不說伐弹,官方提供的東西就是方便秦踪,可以極大地方便我們使用協(xié)程。在使用協(xié)程且還沒有與架構(gòu)組件一起使用的小伙伴兒趕快用起來。美滋滋~

參考資料

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末椅邓,一起剝皮案震驚了整個濱河市柠逞,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌景馁,老刑警劉巖板壮,帶你破解...
    沈念sama閱讀 216,591評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異合住,居然都是意外死亡绰精,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評論 3 392
  • 文/潘曉璐 我一進店門透葛,熙熙樓的掌柜王于貴愁眉苦臉地迎上來笨使,“玉大人,你說我怎么就攤上這事僚害×蛞” “怎么了?”我有些...
    開封第一講書人閱讀 162,823評論 0 353
  • 文/不壞的土叔 我叫張陵萨蚕,是天一觀的道長靶草。 經(jīng)常有香客問我,道長岳遥,這世上最難降的妖魔是什么奕翔? 我笑而不...
    開封第一講書人閱讀 58,204評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮浩蓉,結(jié)果婚禮上派继,老公的妹妹穿的比我還像新娘。我一直安慰自己捻艳,他們只是感情好互艾,可當我...
    茶點故事閱讀 67,228評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著讯泣,像睡著了一般。 火紅的嫁衣襯著肌膚如雪阅悍。 梳的紋絲不亂的頭發(fā)上好渠,一...
    開封第一講書人閱讀 51,190評論 1 299
  • 那天,我揣著相機與錄音节视,去河邊找鬼拳锚。 笑死,一個胖子當著我的面吹牛寻行,可吹牛的內(nèi)容都是我干的霍掺。 我是一名探鬼主播,決...
    沈念sama閱讀 40,078評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼杆烁!你這毒婦竟也來了牙丽?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,923評論 0 274
  • 序言:老撾萬榮一對情侶失蹤兔魂,失蹤者是張志新(化名)和其女友劉穎烤芦,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體析校,經(jīng)...
    沈念sama閱讀 45,334評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡构罗,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,550評論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了智玻。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片遂唧。...
    茶點故事閱讀 39,727評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖吊奢,靈堂內(nèi)的尸體忽然破棺而出盖彭,到底是詐尸還是另有隱情,我是刑警寧澤事甜,帶...
    沈念sama閱讀 35,428評論 5 343
  • 正文 年R本政府宣布谬泌,位于F島的核電站,受9級特大地震影響逻谦,放射性物質(zhì)發(fā)生泄漏掌实。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,022評論 3 326
  • 文/蒙蒙 一邦马、第九天 我趴在偏房一處隱蔽的房頂上張望贱鼻。 院中可真熱鬧,春花似錦滋将、人聲如沸邻悬。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,672評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽父丰。三九已至,卻和暖如春掘宪,著一層夾襖步出監(jiān)牢的瞬間蛾扇,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,826評論 1 269
  • 我被黑心中介騙來泰國打工魏滚, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留镀首,地道東北人。 一個月前我還...
    沈念sama閱讀 47,734評論 2 368
  • 正文 我出身青樓鼠次,卻偏偏與公主長得像更哄,于是被迫代替她去往敵國和親芋齿。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,619評論 2 354

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