Android 官方架構元件(二)——LiveData

刺目啊1199發表於2018-12-27

初到掘金,人生地不熟,喜歡的朋友,點個贊鼓勵下新手唄~

參考連結:
https://developer.android.google.cn/topic/libraries/architecture/livedata https://mp.weixin.qq.com/s/ir3DBkGt5mna3RDjTpRFOQ

LiveData是google釋出的lifecycle-aware components中的一個元件,除了能實現資料和View的繫結響應之外,它最大的特點就是具備生命週期感知功能

LiveData的優點

  • 能夠確保資料和UI統一

LiveData採用了觀察者模式,當資料發生變化時,主動通知被觀察者 。

  • 解決記憶體洩露問題

由於LiveData會在Activity/Fragment等具有生命週期的lifecycleOwner元件呼叫onDestory的時候自動解綁,所以解決了可能存在的記憶體洩漏問題。之前我們為了避免這個問題,一般有註冊繫結的地方都要解綁(即註冊跟解綁要成對出現),而LiveData利用生命週期感知功能解決了這一問題,可以實現只需關心註冊,而解綁會根據生命週期自動進行的功能。

  • 當Activity停止時不會引起崩潰

當Activity元件處於inactive非活動狀態時,它不會收到LiveData資料變化的通知。

  • 不需要手動處理生命週期的變化

觀察者並不需要手動處理生命週期變化對自身的邏輯的影響,只需要關心如何處理獲取到的資料。LiveData能夠感知Activity/Fragment等元件的生命週期變化,所以就完全不需要在程式碼中告訴LiveData元件的生命週期狀態,當資料發生變化時,只在生命週期處於active下通知觀察者,而在inactive下,不會通知觀察者。

  • 確保總能獲取到最新的資料

什麼意思呢?第一種情況,當觀察者處於active活動狀態。LiveData基於觀察者模式,所以當資料發生變化,觀察者能夠馬上獲取到最新變化;第二種情況,當觀察者處於inactive非活動狀態。LiveData只能生命週期active下傳送資料給觀察者。舉個例子,當Activity處於後臺(inactive)時,LiveData接收到了新的資料,但這時候LiveData並不會通知該Activity,但是當該Activity重新返回前臺(active)時會繼續接收到最新的資料。一句話概括,LiveData是粘性的。

  • configuration changes時,不需要額外的處理來儲存資料

我們知道,當你把資料儲存在元件中時,當configuration change(比如語言、螢幕方向變化)時,元件會被recreate,然而系統並不能保證你的資料能夠被恢復的。當我們採用LiveData儲存資料時,因為資料和元件分離了。當元件被recreate,資料還是存在LiveData中,並不會被銷燬。

  • 資源共享

通過繼承LiveData類,然後將該類定義成單例模式,在該類封裝監聽一些系統屬性變化,然後通知LiveData的觀察者。


LiveData的簡單使用

LiveData註冊的觀察者的兩種方式:

// 這個方法新增的observer會受到owner生命週期的影響,在owner處於active狀態時,有資料變化,會通知,
// 當owner處於inactive狀態,不會通知,並且當owner的生命週期狀態時DESTROYED時,自動removeObserver
public void observe (LifecycleOwner owner, Observer<? super T> observer)

// 這個方法新增的observer不存在生命週期概念,只要有資料變化,LiveData都會通知,並且不會自動remove
public void observeForever (Observer<? super T> observer)
複製程式碼

LiveData釋出修改的兩種方式:

// 這個方法必須在主執行緒呼叫
protected void setValue (T value)

// 這個方式主要用於在非主執行緒呼叫
protected void postValue (T value)
複製程式碼
LiveData 官方API文件:https://developer.android.google.cn/reference/androidx/lifecycle/LiveData

舉個例子:在Activity頁面有一TextView,需要展示使用者User的資訊,User 類定義:

public class User {
    public String name;
    public int sex;

    public User(String name, int sex) {
        this.name = name;
        this.sex = sex;
    }

    @Override
    public String toString() {
        return "User{" +
                ", name='" + name + '\'' +
                ", sex='" + sex+ '\'' +
                '}';
    }
}複製程式碼

常規的做法:

// 獲取User的資料後
mTvUser.setText(user.toString());複製程式碼

這樣做的一個問題,如果獲取或者修改User的來源不止一處,那麼需要在多個地方更新TextView,並且如果在多處UI用到了User,那麼也需要在多處更新。

怎麼優化這個問題呢?使用 LiveData。很多時候,LiveData與ViewModel組合使用(ViewModel後續文章會分析),讓LiveData持有User 實體,作為一個被觀察者,當User改變時,通知所有使用User的觀察者自動change。

首先構建一個UserViewModel如下:

public class UserViewModel extends ViewModel {

    //宣告userLiveData
    private MutableLiveData<User> userLiveData;
    
    //獲取userLiveData
    public LiveData getUserLiveData() {
        if(userLiveData == null) {
            userLiveData = new MutableLiveData<>();
        }
        return userLiveData;
    }
}複製程式碼

然後註冊觀察者:

public class TestActivity extends AppCompatActivity {

    private UserViewModel mModel;
    private TextView mUserTextView;

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState, @Nullable PersistableBundle persistentState) {
        super.onCreate(savedInstanceState, persistentState);

        mModel = ViewModelProviders.of(this).get(UserViewModel.class);

        //建立用來更新ui的觀察者,重寫onChange方法,該方法會在資料發生變化時
        //通過LiveData呼叫觀察者的onChange對資料變化響應
        final Observer<User> userObserver = new Observer<User>() {
            @Override
            public void onChanged(@Nullable User user) {
                LogUtil.i(TAG, user.toString());
                //當收到LiveData發來的新資料時,更新
                mUserTextView.setText(user.toString());
            }
        };

        //獲取監聽User資料的LiveData
        LiveData userLiveData = mModel.getUserLiveData();

        //註冊User資料觀察者
        userLiveData.observe(this, userObserver);
    }
}複製程式碼

資料來源傳送改變的時候:

mButton.setOnClickListener(new OnClickListener() {
    @Override
    public void onClick(View v) {
        User user = new User("zhang san", 1)
        //呼叫LiveData的setValue方法通知觀察者資料變化
        mModel.getUserLiveData().setValue(user);
    }
});複製程式碼

這樣使用到 User 的地方,UI 會自動更新,日誌如下:

com.example.cimua I/TestActivity : User{name='zhang san', sex=1}複製程式碼

好了,LiveData的基本使用就是這麼簡單~~~

LiveData更多用法可以看官方文件https://developer.android.google.cn/topic/libraries/architecture/livedata

LiveData基本使用步驟總結:

  1. 建立LiveData
  2. 建立觀察者Observer
  3. 呼叫LiveData的observe方法將LiveData以及Observer建立起釋出-訂閱關係
  4. 在適當的時機呼叫LiveData的setValue或者postValue釋出新資料通知觀察者


LiveData原始碼分析

LiveData是基於觀察者模式構建的,所以,我們分析LiveData的原始碼主要可以分成兩部分:

  • 訂閱:即呼叫LiveData的observe方法註冊
  • 釋出:即呼叫LiveData的setValue或者postValue方法釋出資料

LiveData的訂閱流程分析

現在我們先看看observe()方法,因為observeForever()方法的實現跟observe()是類似的,我們就不看了,這裡只看observe():

// 我們已經知道,LiveData能夠感知生命週期的變化。從傳入的第一個引數是LifecycleOwner型別,我們已經
// 可以知道,原來LiveData是基於Lifecycle架構的基礎上擴充套件的,我們在前一篇Lifecyle文章中已經分析過
// Lifecyle元件了,LiveData就是封裝了特定的LifecycleObserver並將其註冊到LifecycleOwner中,用以感知
// 生命週期
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<T> observer) {

    // 如果LifecycleOwner的生命週期狀態已經是DESTROYED,例如Activity已經destroy,
    // 那麼就沒必要新增觀察者,直接返回就可以了
    if (owner.getLifecycle().getCurrentState() == DESTROYED) {
        // ignore
        return;
    }

    // 建立繼承了GenericLifecycleObserver的LifecycleBoundObserver,並且將這個LifecycleBoundObserver 
    // 存進觀察者集合mObservers中統一管理
    LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
    ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
    if (existing != null && !existing.isAttachedTo(owner)) {
        // 同一個observer物件,只有對應的lifecycleOwner不一樣,才可以重新新增,否則直接拋異常
        throw new IllegalArgumentException("Cannot add the same observer"
                + " with different lifecycles");
    }
    if (existing != null) {
        return;
    }

    // 呼叫observe()這個方法新增的observer,只有Activity/Fragment等生命週期元件可見時
    // 才會收到資料更新的通知,為了知道什麼時候Activity/Fragment是可見的,這裡需要註冊到
    // Lifecycle中感知生命週期
    // 也是因為這個,observe()比observeForever()多了一個引數lifecycleOwner
    owner.getLifecycle().addObserver(wrapper);
}複製程式碼

接著我們在看看observe()方法中最重要的LifecycleBoundObserver:

private abstract class ObserverWrapper {
    final Observer<T> mObserver;
    boolean mActive;
    int mLastVersion = START_VERSION;

    ObserverWrapper(Observer<T> observer) {
        mObserver = observer;
    }

    abstract boolean shouldBeActive();

    boolean isAttachedTo(LifecycleOwner owner) {
        return false;
    }

    void detachObserver() {
    }

    void activeStateChanged(boolean newActive) {
        if (newActive == mActive) {
            return;
        }
        // immediately set active state, so we'd never dispatch anything to inactive
        // owner
        mActive = newActive;

        // LiveData.this.mActiveCount表示處於active狀態的observer的數量
        // 當mActiveCount大於0時,LiveData處於active狀態
        // 注意區分observer的active狀態和 LiveData 的active狀態
        boolean wasInactive = LiveData.this.mActiveCount == 0;
        LiveData.this.mActiveCount += mActive ? 1 : -1;
        if (wasInactive && mActive) {
            // inactive -> active
            onActive();
        }
        if (LiveData.this.mActiveCount == 0 && !mActive) {
            // mActiveCount在我們修改前等於1,也就是說,LiveData從active
            // 狀態變到了inactive
            onInactive();
        }
        
        //如果是active狀態,通知觀察者資料變化(dispatchingValue方法在下一節釋出中分析)
        if (mActive) {
            // observer從inactive到active,此時客戶拿到的資料可能不是最新的,這裡需要dispatch
            // 關於他的實現,我們下一節再看
            dispatchingValue(this);
        }
    }
}

class LifecycleBoundObserver extends ObserverWrapper implements GenericLifecycleObserver {
    @NonNull final LifecycleOwner mOwner;

    LifecycleBoundObserver(@NonNull LifecycleOwner owner, Observer<T> observer) {
        super(observer);
        mOwner = owner;
    }

    // 這個方法返回的是當前是否是active狀態
    @Override
    boolean shouldBeActive() {
        // 只有當生命週期狀態是STARTED或者RESUMED時返回true,其他情況返回false
        return mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED);
    }

    // 這個方法是由Lifecycle結構中的mLifecycleRegistry所呼叫,一旦LifecycleOwner的生命週期
    // 發生變化,都會呼叫到onStateChanged這個方法進行生命週期轉換的通知
    @Override
    public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {
        // 上面我們說過,使用LiveData只需要關心註冊,不需要關心何時解綁,這裡就告訴我們答案:
        // 當生命週期狀態為DESTROYED,會自動removeObserver實現解綁,不會導致記憶體洩露。
        if (mOwner.getLifecycle().getCurrentState() == DESTROYED) {
            removeObserver(mObserver);
            return;
        }

        // 一開始建立LifecycleBoundObserver例項的時候,mActive預設為false,
        // 當註冊到Lifecycle後,Lifecycle會同步生命週期狀態給我們(也就是回撥本方法),
        // 不熟悉lifecycle的讀者,可以看前一篇講述Lifecycle的文章
        activeStateChanged(shouldBeActive());
    }

    @Override
    boolean isAttachedTo(LifecycleOwner owner) {
        return mOwner == owner;
    }

    @Override
    void detachObserver() {
        mOwner.getLifecycle().removeObserver(this);
    }
}複製程式碼

到這裡,LiveData的訂閱流程就基本分析完了。

LiveData的釋出流程分析

上面我們已經說了,LiveData釋出修改有setValue已經postValue兩種方式,其中setValue只能在主執行緒呼叫,postValue則沒有這個限制。

我們從setValue的分析入手釋出流程:

@MainThread
protected void setValue(T value) {
    // 判斷當前呼叫執行緒是否是主執行緒,如果不是,直接拋IllegalStateException異常
    assertMainThread("setValue");
    // 每次更新value,都會使mVersion + 1,ObserverWrapper也有一個欄位,叫mLastVersion
    // 通過比較這兩個欄位,可以避免重複通知觀察者,還可以用於實現LiveData的粘性事件特性(後面會說到)
    mVersion++;
    // 將這次資料儲存在LiveData的mData變數中,mData的值永遠是最新的值
    mData = value;
    // 釋出
    dispatchingValue(null);
}複製程式碼

接著再看看dispatchingValue方法:

// 如果引數initiator為null的話,表示要將新資料釋出通知給所有observer
// 如果引數不為null的話,表示只通知給傳入的觀察者initiator
private void dispatchingValue(@Nullable ObserverWrapper initiator) {
    if (mDispatchingValue) {
        mDispatchInvalidated = true;
        return;
    }

    // 在observer的回撥裡面又觸發了資料的修改
    // 設定mDispatchInvalidated為true後,可以讓下面的迴圈知道
    // 資料被修改了,從而開始一輪新的迭代。
    //
    // 比方說,dispatchingValue -> observer.onChanged -> setValue -> dispatchingValue
    // 這裡return的是後面那個dispatchingValue,然後在第一個
    // dispatchingValue會重新遍歷所有的observer,並呼叫他們的onChanged。
    //
    // 如果想避免這種情況,可以在回撥裡面使用 postValue 來更新資料
    mDispatchingValue = true;
    do {
        mDispatchInvalidated = false;
        if (initiator != null) {
            // 呼叫 observer.onChanged()
            considerNotify(initiator);
            initiator = null;
        } else {
            // initiator不為空,遍歷mObservers集合,試圖通知所有觀察者
            for (Iterator<Map.Entry<Observer<T>, ObserverWrapper>> iterator =
                    mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {
                considerNotify(iterator.next().getValue());
                if (mDispatchInvalidated) {
                    // 某個客戶在回撥裡面更新了資料,break後,這個for迴圈會重新開始
                    break;
                }
            }
        }
    } while (mDispatchInvalidated);
    mDispatchingValue = false;
}複製程式碼

看過我那篇 lifecycle 原始碼分析的讀者應該對 dispatchingValue 處理迴圈呼叫的方式很熟悉了。以這裡為例,為了防止迴圈呼叫,我們在呼叫客戶程式碼前先置位一個標誌(mDispatchingValue),結束後再設為 false。如果在回撥裡面又觸發了這個方法,可以通過 mDispatchingValue 來檢測。

檢測到迴圈呼叫後,再設定第二個標誌(mDispatchInvalidated),然後返回。返回又會回到之前的呼叫,前一個呼叫通過檢查 mDispatchInvalidated,知道資料被修改,於是開始一輪新的迭代。

接著繼續看considerNotify方法:

private void considerNotify(ObserverWrapper observer) {
    // 如果observer的狀態不是active,那麼不向該observer通知,直接返回
    if (!observer.mActive) {
        return;
    }
    // Check latest state b4 dispatch. Maybe it changed state but we didn't get the event yet.
    //
    // we still first check observer.active to keep it as the entrance for events. So even if
    // the observer moved to an active state, if we've not received that event, we better not
    // notify for a more predictable notification order.
    if (!observer.shouldBeActive()) {
        observer.activeStateChanged(false);
        return;
    }

    // 上面的原始碼分析,我們知道每一次setValue或者postValue的呼叫都會是mVersion自增1,
    // mLastVersion的作用是為了與mVersion作比較,這個比較作用主要有兩點:
    // 1.如果說mLastVersion >= mVersion,證明這個觀察者已經接受過本次釋出事件通知,不需要重複通知了,直接返回
    // 2.實現粘性事件。比如有一個資料(LiveData)在A頁面setValue()之後,則該資料(LiveData)中的
    // 全域性mVersion+1,也就標誌著資料版本改變,然後再從A頁面開啟B頁面,在B頁面中開始訂閱該LiveData,
    // 由於剛訂閱的時候內部的資料版本都是從-1開始,此時內部的資料版本就和該LiveData全域性的資料
    // 版本mVersion不一致,根據上面的原理圖,B頁面開啟的時候生命週期方法一執行,則會進行notify,
    // 此時又同時滿足頁面是從不可見變為可見、資料版本不一致等條件,所以一進B頁面,B頁面的訂閱就會被響應一次
    if (observer.mLastVersion >= mVersion) {
        return;
    }

    // 設定mLastVersion = mVersion,以免重複通知觀察者
    observer.mLastVersion = mVersion;

    // 這裡就最終呼叫了我們一開始通過observe()方法傳入的observer中的onChange()方法
    // 即新資料被髮布給了observer
    observer.mObserver.onChanged((T) mData);
}複製程式碼

setValue的分析就到此為止了~

在setValue的基礎上,分析postValue就比較簡單了:

private final Runnable mPostValueRunnable = new Runnable() {
	@Override
	public void run() {
	    Object newValue;
	    synchronized (mDataLock) {
	        newValue = mPendingData;
	        mPendingData = NOT_SET;
	    }
	    //noinspection unchecked
	    setValue((T) newValue);
	}
};

protected void postValue(T value) {
	boolean postTask;
	synchronized (mDataLock) {
		postTask = mPendingData == NOT_SET;
		mPendingData = value;
	}
	if (!postTask) {
		return;
	}

        // 通過handler將mPostValueRunnable分發到主執行緒執行,其實最終執行的也是setValue方法
	ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);
}複製程式碼

LiveData的原始碼就分析這麼多了,更詳細就需要到android原始碼裡面找了。


總結

將LiveData的原始碼抽象為一張流程圖來展示,下面的其他問題都可以在這張圖中找到答案:Android 官方架構元件(二)——LiveData

  • LiveData為什麼能夠感知生命週期?

lifecycle-aware compents的核心就是生命週期感知,要明白LiveData為什麼能感知生命週期,就要知道Google的這套生命週期感知背後的原理是什麼,下面是我基於之前lifeycycle這套東西剛出來時候對原始碼進行的一個分析總結(現在的最新程式碼可能和之前有點出入,但是原理上基本是一樣的):

首先Activity/Fragment是LifecycleOwner(26.1.0以上的support包中Activity已經預設實現了LifecycleOwner介面),內部都會有一個LifecycleRegistry存放生命週期State、Event等。而真正核心的操作是,每個Activity/Fragment在啟動時都會自動新增進來一個Headless Fragment(無介面的Fragment),由於新增進來的Fragment與Activity的生命週期是同步的,所以當Activity執行相應生命週期方法的時候,同步的也會執行Headless Fragment的生命週期方法,由於這個這個Headless Fragment對我們開發者來說是隱藏的,它會在執行自己生命週期方法的時候更新Activity的LifecycleRegistry裡的生命週期State、Event, 並且notifyStateChanged來通知監聽Activity生命週期的觀察者。這樣就到達了生命週期感知的功能,所以其實是一個隱藏的Headless Fragment來實現了監聽者能感知到Activity的生命週期。

基於這套原理,只要LiveData註冊了對Activity/Fragment的生命週期監聽,也就擁有了感知生命週期的能力。

  • LiveData為什麼可以避免記憶體洩露?

由於LiveData會在Activity/Fragment等具有生命週期的lifecycleOwner onDestory的時候自動解綁,所以解決了可能存在的記憶體洩漏問題。之前我們為了避免這個問題,一般有註冊繫結的地方都要解綁,而LiveData利用生命週期感知功能解決了這一問題。

我們可以知道,當Activity/Fragment的生命週期發生改變時,LiveData中的監聽都會被回撥,所以避免記憶體洩漏就變得十分簡單,可以看上圖,當LiveData監聽到Activity onDestory時則removeObserve,使自己與觀察者自動解綁。這樣就避免了記憶體洩漏。 

  • LiveData為什麼可以解決空指標異常?

我們通常在一個非同步任務回來後需要更新View,而此時頁面可能已經被回收,導致經常會出現View空異常,而LiveData由於具備生命週期感知功能,在介面可見的時候才會進行響應,如介面更新等,如果在介面不可見的時候發起notify,會等到介面可見的時候才進行響應更新。所以就很好的解決了空異常的問題。

  • LiveData為什麼是粘性的?

所謂粘性,也就是說訊息在訂閱之前釋出了,訂閱之後依然可以接受到這個訊息,像EventBus實現粘性的原理是,把釋出的粘性事件暫時存在全域性的集合裡,之後當發生訂閱的那一刻,遍歷集合,將事件拿出來執行。

而LiveData之所以本身就是粘性的,結合上面的原理圖我們來分析一下,比如有一個資料(LiveData)在A頁面setValue()之後,則該資料(LiveData)中的全域性mVersion+1,也就標誌著資料版本改變,然後再從A頁面開啟B頁面,在B頁面中開始訂閱該LiveData,由於剛訂閱的時候內部的資料版本都是從-1開始,此時內部的資料版本就和該LiveData全域性的資料版本mVersion不一致,根據上面的原理圖,B頁面開啟的時候生命週期方法一執行,則會進行notify,此時又同時滿足頁面是從不可見變為可見、資料版本不一致等條件,所以一進B頁面,B頁面的訂閱就會被響應一次。這就是所謂的粘性,A頁面在發訊息的時候B頁面是還沒建立還沒訂閱該資料的,但是一進入B頁面一訂閱,之前在A中發的訊息就會被響應。

初到掘金,人生地不熟,喜歡的朋友,點個贊鼓勵下新手唄~

相關文章