一點點入坑JetPack:LiveData篇

MDove發表於2019-02-23

前言

結束了Lifecycle篇和ViewModel篇,終於到了相對複雜的LiveData篇了。

最開始瞭解LiveData我是拒絕的,因為你不能上來就讓我用,馬上就用。第一我要試一下,我不原意用完以後...duang、duang都是bug....

一點點入坑JetPack:ViewModel篇

一點點入坑JetPack:Lifecycle篇

一點點入坑JetPack:LiveData篇

後來用完之後,好嗨呦,感覺人生已經達到了高潮...

正文

當然不想聽我瞎bb的,可以直接官方文件。如果想圖個樂,順便了解了解新技術。那歡迎光臨紅...,男賓一位,裡邊請!

一、概況

官網:LiveData是一個可觀察的資料持有者類。與常規observable不同,LiveData是生命週期感知的。

從官方文件上我們可以看到倆個關鍵詞:可觀察生命週期感知。簡單來說,Google給我們提供了一個可以被觀察的,並且擁有生命週期感知能力的類。那有什麼用呢?

直接上demo:

二、入門

1.1、初級官方demo

class NameViewModel : ViewModel() {
    // 這裡new了一個MutableLiveData,它是LiveData的實現類,LiveData是抽象的,很明顯不能被new
    val currentName: LiveData<String> by lazy {
        MutableLiveData<String>()
    }
}

class NameActivity : AppCompatActivity() {
    private lateinit var mModel: NameViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        mModel = ViewModelProviders.of(this).get(NameViewModel::class.java)
        // 這個this是LifecycleOwner
        mModel.currentName.observe(this, Observer { newName ->
            // mNameTextView一個TextView
            mNameTextView.text = newName
        })
        // 更新被觀察者資料,LiveData會通知觀察者
        mModel.currentName.postValue("MDove")
    }
}
複製程式碼

如果看到這幾行程式碼,豁然開朗,那麼可以跳過看下面的部分。如果感覺有些疑惑,不著急我們們不打針不吃藥,坐下就是和你嘮...

1.2、demo解釋

最開始我們先宣告瞭一個NameViewModel的ViewModel,這部分內容在ViewModel篇有所提及。內部有一個MutableLiveData的成員變數。說白了就是一個LiveData型別的String,我們使用時是藉助LiveData的特性,但本質還是用String。

也就是說這裡如果我們要用一個List,那麼此時就是MutableLiveData<List<String>>()

Activity之中,我們先獲取ViewModel,然後mModel.currentName.observe(...,...),這裡我們就是在觀察LiveData。我們只需要在回撥中處理我們自己的UI操作即可了。也就是demo中的mNameTextView.text = newName

LiveData會在每一次postValue(...)或者value=...時,observe()便會回撥,哪怕是null。

注意

這裡有倆個點需要特別注意:

  • 1、LiveData是生命週期感知的,在當前的LifecycleOwner不處於活動狀態(例如onPasue()onStop())時,LiveData是不會回撥observe()的,因為沒有意義。
  • 2、如果LiveData沒有被observe(),那麼此時你呼叫這個LiveData的postValue(...)/value=...,是沒有任何作用。這個我們可以在原始碼中看到。

1.3、不同的LiveData實現類(系統實現)

MutableLiveData

上文我們們已經見過了,沒什麼特別的,就是LiveData的實現類。就相當於List和ArrayList的關係。

MediatorLiveData

MutableLiveData的子類,它是一個比較強大的LiveData,我們的map()switchMap()都是基於它進行實現的。 最大的特點是可以同時監聽多個LiveData。

三、進階

官網的這個小破demo,屬實太寒酸了,你倒是加點特技啊?就這中初級用法,誰能覺得好用呢!所以,如果對LiveData稍稍有點感覺,那我們們不要停,一起決戰到天明。

3.1、map()

初用過RxJava的小夥伴,估計會和我一樣,被各種“姿勢”的操作符所正經,比如常用的:map、flatMap...而LiveData中同樣有這樣的操作。

一個很常見的場景:我們通過一個唯一id去查詢這個id下的實體類,並且要同時展示二者的資料。很簡單的業務邏輯,在LiveData中的展示是這樣的:

3.1.1、使用

class NameViewModel : ViewModel() {
    val userIdLiveData = MutableLiveData<Long>()
    // 偽碼:當userIdLiveData發生變化時,userLiveData中的map就會呼叫,那麼我們就可以得到罪行的id
    val userLiveData: LiveData<User> = Transformations.map(userIdLiveData) { id->
        // 通過id拿到User,return一個User的例項user
        user
    }
}

// Activity中
mModel.userLiveData.observe(this, Observer { user ->
    // user變化後通知mNameTextView更新UI
    mNameTextView.text = user.name
})
// 給userIdLiveData設定id為1
mModel.userIdLiveData.postValue("1")
複製程式碼

針對這個業務場景,我們只需要監聽我們使用者通知UI變化的LiveData(userLiveData),然後通過userIdLiveData.postValue("1")來驅動資料的變化。

這裡可能和我們傳統的MVP的思想並不相同,畢竟MVVM和MVP還是有區別的,而MVVM的這種方式被稱之為:資料驅動

3.1.2、map()原始碼

我們直接點到Transformations.map()中。

@MainThread
public static <X, Y> LiveData<Y> map(
        @NonNull LiveData<X> source,
        @NonNull final Function<X, Y> mapFunction) {
    final MediatorLiveData<Y> result = new MediatorLiveData<>();
    result.addSource(source, new Observer<X>() {
        @Override
        public void onChanged(@Nullable X x) {
            result.setValue(mapFunction.apply(x));
        }
    });
    return result;
}
複製程式碼

很簡單,就是使用了MediatorLiveData,然後通過一個高階函式,將高階函式返回的內容,set到LiveData上,完成map()。

既然提到了MediatorLiveData,以及它的addSource()的方法,那麼我們就來看看它的原始碼。

3.1.3、MediatorLiveData原始碼

這部分沒啥意思,可以直接跳過看3.1.4、map()原始碼總結...

進入MediatorLiveData之中,我們會發現程式碼比較少。這裡抽出倆塊比較重點的內容,我們一同來感受一下:

 @MainThread
public <S> void addSource(@NonNull LiveData<S> source, @NonNull Observer<? super S> onChanged) {
    Source<S> e = new Source<>(source, onChanged);
    Source<?> existing = mSources.putIfAbsent(source, e);
    if (existing != null && existing.mObserver != onChanged) {
        throw new IllegalArgumentException(
                "This source was already added with the different observer");
    }
    if (existing != null) {
        return;
    }
    if (hasActiveObservers()) {
        e.plug();
    }
}
複製程式碼

從這段程式碼中,我們粗略可以得到一個資訊,這裡把我們的LiveData和Observer封裝成了Source物件,並且這個物件,不能重複新增。

此外,Source的plug()方法,被呼叫。接下來我們去看一看這個內部類Source的實現:

private static class Source<V> implements Observer<V> {
    final LiveData<V> mLiveData;
    final Observer<? super V> mObserver;
    int mVersion = START_VERSION;

    Source(LiveData<V> liveData, final Observer<? super V> observer) {
        mLiveData = liveData;
        mObserver = observer;
    }

    void plug() {
        mLiveData.observeForever(this);
    }

    void unplug() {
        mLiveData.removeObserver(this);
    }

    @Override
    public void onChanged(@Nullable V v) {
        if (mVersion != mLiveData.getVersion()) {
            mVersion = mLiveData.getVersion();
            mObserver.onChanged(v);
        }
    }
}
複製程式碼

首先Source是一個觀察者,可以看到,我們外部使用的Observer會以Source的成員變數的形式,新增到傳入的LiveData中。值得注意的是,這裡使用了mLiveData.observeForever(this);

observeForever()用法可以看到,我們並沒有傳遞LifecycleOwner,因此它並不具備生命感知能力。從註釋中也可見一斑:This means that the given observer will receive all events and will never be automatically removed.

3.1.4、map()原始碼總結

打住,打住吧。其實沒必要繼續看了。一句話總結:map()的原理就是基於MediatorLiveData,MediatorLiveData內部會將傳遞進來的LiveData和Observer封裝成內部類,然後放在內部維護的一個Map中。並且自動幫我們完成observeForever()和removeObserver()

3.2、switchMap()

3.2.1、使用

switchMap()的場景可以應用在切換LiveData上。這話怎麼解釋呢?

很常見的業務場景:比如你的業務用的是map(),map()中使用你自己寫的絡,而且LiveData執行的很良好,抽著煙喝著酒,啥事都沒有...就比如,上面map()那樣的程式碼:

val userLiveData: LiveData<User> = Transformations.map(userIdLiveData) { id->
    // 自己的一段邏輯
    user
}
// Activity中
mViewModel.userLiveData.observe(this,Observer{->user
    //更新UI
})
複製程式碼

突然有一天,這個地方資料結構、UI都沒變,唯獨變了邏輯。此時你一個同事寫好了一個方法,讓你替換一下就好了了。不過當你呼叫的時候突然返現,這個方法返回一個LiveData物件!

當然此時我們可以讓UI重新observe()這個LiveData物件:

val otherLiveData:LiveData<User> = // 同事的邏輯

// Activity中重新observe()
mViewModel.otherLiveData.observe(this,Observer{->user
    //更新UI
})
複製程式碼

可是這樣的話,自己之前寫的東西不都白費了麼?所以此時,我們可以使用switchMap(),我們只需要很少的改動,就可以設配這次需求的變動:

val userLiveData: LiveData<User> = Transformations.switchMap(userIdLiveData) { id->
    // 直接把同事的程式碼放在這裡即可
}
複製程式碼

3.2.2、switchMap()原始碼

有了上邊map()原始碼基礎,我們可以很容易的看出switchMap()的端倪:

@MainThread
public static <X, Y> LiveData<Y> switchMap(
        @NonNull LiveData<X> source,
        @NonNull final Function<X, LiveData<Y>> switchMapFunction) {
    final MediatorLiveData<Y> result = new MediatorLiveData<>();
    result.addSource(source, new Observer<X>() {
        LiveData<Y> mSource;

        @Override
        public void onChanged(@Nullable X x) {
            // 從Function中拿到返回的LiveData,也就是我們新的LiveData(文章背景中同事寫的LiveData)
            LiveData<Y> newLiveData = switchMapFunction.apply(x);
            if (mSource == newLiveData) {
                return;
            }
            // remove掉舊的LiveData
            if (mSource != null) {
                result.removeSource(mSource);
            }
            mSource = newLiveData;
            if (mSource != null) {
                // add新的LiveData
                result.addSource(mSource, new Observer<Y>() {
                    @Override
                    public void onChanged(@Nullable Y y) {
                        // 通知LiveData發生變化
                        result.setValue(y);
                    }
                });
            }
        }
    });
    return result;
}
複製程式碼

我們對比一下switchMap()map()的引數型別:

  • Function<X, LiveData> switchMapFunction
  • Function<X, Y> mapFunction

很明顯一個是返回LiveData型別,一個是換一種類型。這也說明了,這倆個方法的不一樣之處 。

程式碼解釋可以看註釋,很直白的思路。

3.3、MediatorLiveData的使用

上述我們看過了map()switchMap()的用法。各位應該都注意到MediatorLiveData這個類的作用。

上邊我們一直都在操作一個LiveData,但是我們需求很容易遇到多種狀態的變化。就像官方的demo:

 LiveData liveData1 = ...;
 LiveData liveData2 = ...;

 MediatorLiveData liveDataMerger = new MediatorLiveData<>();
 liveDataMerger.addSource(liveData1, value -> liveDataMerger.setValue(value));
 liveDataMerger.addSource(liveData2, value -> liveDataMerger.setValue(value));
複製程式碼

如同demo所示,我們可以同時add多個LiveData,根據不同的LiveData的變化,處理我們不同的邏輯。最後通過MediatorLiveData回撥到我們的UI上。

四、原始碼分析

註冊Observer

@MainThread
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
    // 如果當前生命週期是DESTROYED,直接return
    if (owner.getLifecycle().getCurrentState() == DESTROYED) {
        return;
    }
    // 這個包裝類,做了一件事情,在DESTROYED,移除Observer
    LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
    // 新增在已經Observer,已存在且在Attach上後直接拋異常,也就是不能重複add
    ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
    if (existing != null && !existing.isAttachedTo(owner)) {
        throw new IllegalArgumentException("Cannot add the same observer"
                + " with different lifecycles");
    }
    if (existing != null) {
        return;
    }
    // 把Wrapper新增到LifecycleOwner上
    owner.getLifecycle().addObserver(wrapper);
}
複製程式碼

Observer如何被響應:

public interface Observer<T> {
    /**
     * Called when the data is changed.
     * @param t  The new data
     */
    void onChanged(T t);
}
複製程式碼

觸發的起點,很明顯是我們在set/postValue的時候:

@MainThread
protected void setValue(T value) {
    // 記住這個值,它是用來表示資料是否發生變化的
    mVersion++;
    mData = value;
    dispatchingValue(null);
}

void dispatchingValue(@Nullable ObserverWrapper initiator) {
    // 省略部分程式碼 
    for (Iterator<Map.Entry<Observer<? super T>, ObserverWrapper>> iterator =
        mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {
        // 往裡走
        considerNotify(iterator.next().getValue());
        if (mDispatchInvalidated) {
            break;
        }
    }
    // 省略部分程式碼
}

private void considerNotify(ObserverWrapper observer) {
    // 如果observer不在活動期,則直接return。也就是上述說observer不在前臺,將不會接受回撥。
    if (!observer.mActive) {
        return;
    }
    // 省略部分程式碼
    // 很直白的version對比
    if (observer.mLastVersion >= mVersion) {
        return;
    }
    observer.mLastVersion = mVersion;
    // 回撥
    observer.mObserver.onChanged((T) mData);
}
複製程式碼

observer.mActive在哪被賦值?很多地方。除了一些邊界條件的賦值外,一個比較正式的賦值,ObserverWrapper中的void activeStateChanged(boolean newActive)方法:

void activeStateChanged(boolean newActive) {
    if (newActive == mActive) {
        return;
    }
    mActive = newActive;
}
// 此方法最終會調到此方法
@Override
boolean shouldBeActive() {
    return mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED);
}
public boolean isAtLeast(@NonNull State state) {
    return compareTo(state) >= 0;
}
複製程式碼

很簡單,每次生命週期回撥,observer.mActive都會被賦值,而只有到Lifecycle是活動狀態是,mActive才會true。因此只有在我們的Activity為前臺時我們的LiveData才會被回撥。

尾聲

到此關於LiveData的部分就整完了,不知道各位看官是否感受到LiveData的好用之處了呢?如果沒有,不如自己寫一寫,用身體去感受來自LiveData的爽快~

個人公眾號:鹹魚正翻身

相關文章