Android Jetpack元件 - ViewModel,LiveData使用以及原理

後撤步三分發表於2019-01-17

本文涉及的原始碼版本如下:

  • com.android.support:appcompat-v7:27.1.1
  • android.arch.lifecycle:extensions:1.1.1
  • android.arch.lifecycle:viewmodel:1.1.1
  • android.arch.lifecycle:livedata:1.1.1

什麼是ViewModel, 以及工作原理

ViewModel用於儲存和管理UI相關的資料,ViewModel有自己生命週期,會根據fragment,activity的生命週期銷燬。當配置發生改變時, ViewModel儲存的資料還是存在的,不會被銷燬。(例如旋轉螢幕,旋轉螢幕通常會導致activity重建)

ViewModel 相關類結構

Jetpack-viewmodel

ViewModel:抽象類,onCleared方法用於釋放資源

AndroidViewModel: 繼承了ViewModel, 沒什麼區別, 構造可以傳入一個application

ViewModelStore: ViewModel儲存器, 內部Map儲存

ViewModelStoreOwner: 一個介面, ViewModelStore擁有者。 Fragment和FragmentActivity實現了介面

Factory: 一個工廠介面,用於建立ViewModel。是ViewModelProvider的內部介面

NewInstanceFactory: 繼承Factory, 通過反射class.newInstance()產生一個ViewModel。所以使用這個Factory,ViewModel需要有空參構造器。

AndroidViewModelFactory: 繼承Factory, 也是通過反射產生一個AndroidViewModel, 而且這個AndroidViewModel構造器必須只有一個application引數, 如何反射建立失敗會呼叫NewInstanceFactory去建立。

ViewModelProvider: 是一個幫助類,構造器可以傳入ViewModelStore和Factory, 會根據Factory產生一個ViewModel, 並儲存到ViewModelStore裡。

ViewModel的生命週期

ViewModel的生命週期是跟fragment, activity生命週期關聯的。只有fragment, activity銷燬時才會呼叫ViewModel.onCleared() (配置改成導致銷燬,不會呼叫onCleared)

ViewModel儲存在ViewModelStore, ViewModel的onCleared方法在ViewModelStore的clear方法時被呼叫, ViewModelStore原始碼如下:

private final HashMap<String, ViewModel> mMap = new HashMap<>();
/**
     *  Clears internal storage and notifies ViewModels that they are no longer used.
     */
public final void clear() {
    for (ViewModel vm : mMap.values()) {
        vm.onCleared();
    }
    mMap.clear();
}
複製程式碼

下面看看Fragment, 和FragmentActivity的實現,先看Fragment的實現:

public ViewModelStore getViewModelStore() {
    if (getContext() == null) {
        throw new IllegalStateException("Can't access ViewModels from detached fragment");
    }
    // new一個ViewModelStore, 一個Fragment只有一個ViewModelStore
    if (mViewModelStore == null) {
        mViewModelStore = new ViewModelStore();
    }
    return mViewModelStore;
}

public void onDestroy() {
    mCalled = true;
    // Use mStateSaved instead of isStateSaved() since we're past onStop()
    if (mViewModelStore != null && !mHost.mFragmentManager.mStateSaved) {
        mViewModelStore.clear(); // mStateSaved用於判讀是否由於配置改變導致Fragment重建
    }
}
複製程式碼

再看FragmentActivity的實現

    public ViewModelStore getViewModelStore() {
        if (getApplication() == null) {
            throw new IllegalStateException("Your activity is not yet attached to the "
                    + "Application instance. You can't request ViewModel before onCreate call.");
        }
        if (mViewModelStore == null) {
            mViewModelStore = new ViewModelStore();
        }
        return mViewModelStore;
    }

    protected void onDestroy() {
        super.onDestroy();
		....
        if (mViewModelStore != null && !mRetaining) { 
            // mRetaining用於判讀是否由於配置改變導致Fragment重建
            mViewModelStore.clear();
        }
    }
複製程式碼

ViewModel儲存的資料不會在配置改變時而銷燬, ViewModel是怎樣儲存的呢,看前面的分析在Fragment, FragmentActivity都有一個mViewModelStore的成員變數,只要儲存好這個mViewModelStore這個就可以啦。當配置改變時,在onSaveInstanceState裡儲存資料,然後在onRestoreInstanceState恢復資料,但是這兩個方法儲存資料只能放在Bundle, 只能存小量資料, 過大會拋TransactionTooLargeException異常。所以不合適,看原始碼發現是利用Activity的onRetainNonConfigurationInstance()和getLastNonConfigurationInstance()。

   // FragmentActivity關鍵程式碼
	public final Object onRetainNonConfigurationInstance() {
        Object custom = onRetainCustomNonConfigurationInstance();
        // 這方法繼承自Activty, 且把方法定義成final,不讓子類實現,但是提供onRetainCustomNonConfigurationInstance方法
        FragmentManagerNonConfig fragments = mFragments.retainNestedNonConfig();
        //fragments是當前activity所有fragment需要儲存的資料
        if (fragments == null && mViewModelStore == null && custom == null) {
            return null;
        }
        NonConfigurationInstances nci = new NonConfigurationInstances();
        nci.custom = custom;
        nci.viewModelStore = mViewModelStore;
        nci.fragments = fragments;
        // mViewModelStore 和 fragments 就存在NonConfigurationInstances裡
        return nci;
    }

    protected void onCreate(@Nullable Bundle savedInstanceState) {
        ...
        NonConfigurationInstances nc =
                (NonConfigurationInstances) getLastNonConfigurationInstance();
        // 呼叫getLastNonConfigurationInstance方法獲取NonConfigurationInstances恢復資料
        if (nc != null) {
            mViewModelStore = nc.viewModelStore;
        }
        if (savedInstanceState != null) {
            Parcelable p = savedInstanceState.getParcelable(FRAGMENTS_TAG);
            mFragments.restoreAllState(p, nc != null ? nc.fragments : null);
            //nc.fragments通過mFragments.restoreAllState()恢復
        }    
        ...
    }    
複製程式碼

這裡onRetainNonConfigurationInstance和getLastNonConfigurationInstance方法就不深入啦,配置改變時還想儲存一些自定義object資料可以重寫onRetainCustomNonConfigurationInstance去實現。那Fragment的mViewModelStore呢,就是mFragments.retainNestedNonConfig() 和 mFragments.restoreAllState() 這兩個方法啦。 mFragments是FragmentController例項,最後是呼叫到FragmentManager, 而FragmentManager真正實現是FragmentManagerImpl。下面擷取部分FragmentManagerImpl的關鍵程式碼。

// 這個方法就Activity裡mFragments.retainNestedNonConfig()最終呼叫的方法
FragmentManagerNonConfig retainNonConfig() {
    setRetaining(mSavedNonConfig); // 由於篇幅的原因, 這個方法本文不展開了
    return mSavedNonConfig;
}
//來看mSavedNonConfig這個例項是在哪裡建立的,回到FragmentActivity.onSaveInstanceState方法裡發現呼叫mFragments.saveAllState()儲存狀態,最終來到FragmentManagerImpl的saveAllState()
Parcelable saveAllState() {
    mSavedNonConfig = null;
    saveNonConfig();
}    

void saveNonConfig() {
    ArrayList<Fragment> fragments = null;
    ArrayList<FragmentManagerNonConfig> childFragments = null;
    ArrayList<ViewModelStore> viewModelStores = null;
    if (mActive != null) {
        //mActive是SparseArray<Fragment>, 代表fragment棧, 遍歷mActive, 
        for (int i=0; i<mActive.size(); i++) {
            Fragment f = mActive.valueAt(i);
            if (f != null) {
                ...
                if (viewModelStores != null) {
                     // 把fragment的成員變數mViewModelStore新增到list中
                    viewModelStores.add(f.mViewModelStore);
                }
            }
        }
    }
    if (fragments == null && childFragments == null && viewModelStores == null) {
        mSavedNonConfig = null;
    } else {
        //viewModelStores儲存到FragmentManagerNonConfig裡
        mSavedNonConfig = new FragmentManagerNonConfig(fragments, childFragments,
                                                       viewModelStores);
    }
}
//mFragments.restoreAllState() 最終呼叫FragmentManagerImpl的restoreAllState()
void restoreAllState(Parcelable state, FragmentManagerNonConfig nonConfig) {
    if (state == null) return;
    FragmentManagerState fms = (FragmentManagerState)state;
    //fms是儲存狀態時儲存在Parcelable的,現在拿出來恢復狀態
    if (fms.mActive == null) return; 
    //這裡有很多種情況,這裡大概分析常見的恢復過程
    ...
    mActive = new SparseArray<>(fms.mActive.length);
    //遍歷FragmentState
    for (int i=0; i<fms.mActive.length; i++) { 
        FragmentState fs = fms.mActive[i];
        if (fs != null) {
            ...
            ViewModelStore viewModelStore = null;
            if (viewModelStores != null && i < viewModelStores.size()) {
                viewModelStore = viewModelStores.get(i);
            }
            Fragment f = fs.instantiate(mHost, mContainer, mParent, childNonConfig,
                                        viewModelStore);
            // 傳入viewModelStore, 呼叫instantiate方法重新生成Fragment
            if (DEBUG) Log.v(TAG, "restoreAllState: active #" + i + ": " + f);
            mActive.put(f.mIndex, f);
        }
    }
} 
複製程式碼

什麼是LiveData, 以及工作原理

LiveData是一個可觀察資料的持有者, 但是不像普通的可觀察資料, LiveData是繫結了生命週期,例如activitie, fragment的生命週期。有點抽象, 還是先了解一下關鍵的類。

//Observer是一個介面,觀察者。當資料改變時就回撥onChanged
public interface Observer<T> {
    void onChanged(@Nullable T t);
}

//LiveData類是個抽象類,這裡先看看有什麼暴露出去的方法
public abstract class LiveData<T> {
    //新增觀察者觀察, 傳入LifecycleOwner,用於來繫結生命週期
    public void observe(LifecycleOwner owner, Observer<T> observer)
    //新增觀察者觀察, 但是是沒有LifecycleOwner
    public void observeForever(Observer<T> observer) 
    //移除觀察者   
    public void removeObserver(Observer<T> observer) 
    //移除某個LifecycleOwner裡所有的觀察者    
    public void removeObservers(final LifecycleOwner owner)
    //是否有觀察者    
    public boolean hasObservers()    
    //是否有活的觀察者    
    public boolean hasActiveObservers()
}    
//MutableLiveData繼承LiveData, 多開放2個方法。
public class MutableLiveData<T> extends LiveData<T> {
    public void postValue(T value) //post一個資料 
    public void setValue(T value)  //設定一個資料
    //postValue和setValue的區別是:post可以在子執行緒用,而setValue只能在UI執行緒呼叫
}
複製程式碼

介紹完了主要的類,下面舉個例子:

// 這段程式碼通常在Activity或者Fragment中, 用liveData去新增一個觀察者
// AppCompatActivity或者Fragment已經LifecycleOwner,通常傳this即可
// 當User資料有變化時,onChanged方法會被呼叫, 用於更新UI
liveData.observe(this, new Observer<User>() {
    @Override
    public void onChanged(@Nullable User user) {
    	//更新UI
    }
});

//下面這段程式碼通常在ViewModel中,這裡liveData和上面程式碼的liveData是同一個
//可以通過postValue或者setValue去改成user資料, 有資料改變時, onChanged方法會被回撥
//假如一個場景, 在ViewModel中,通過網路請求去拿user資料,然後postValue就會去通知UI層onChanged去更新UI
//liveData大概就是這個流程
liveData = new MutableLiveData<User>()
liveData.postValue(user)// 或者setValue(user)
複製程式碼

下面還是看看LiveData原始碼去了解一下原理

LiveData工作原理

這裡以observe(LifecycleOwner owner, Observer observer) 和 postValue之間資料的通訊為例分析。對LifecycleOwner不熟悉的可以看我的上一篇文章:LifecycleOwner, 下面看LiveData.observe原始碼:


	@MainThread
    public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<T> observer) {
        //判斷當前生命週期是DESTROYED, 立馬return, 都銷燬了,新增觀察者沒意義
        if (owner.getLifecycle().getCurrentState() == DESTROYED) {
            // ignore
            return;
        }
        
        
        LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
        ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
        //LifecycleBoundObserver繼承ObserverWrapper, LifecycleBoundObserver實現LifecycleOwner
        //mObservers這是一個Map, key是observer, value是ObserverWrapper
        if (existing != null && !existing.isAttachedTo(owner)) {
            throw new IllegalArgumentException("Cannot add the same observer"
                    + " with different lifecycles");
        }
        if (existing != null) {
            return;
        }
        //新增生命週期的觀察者
        owner.getLifecycle().addObserver(wrapper);
    }
複製程式碼

接著看LifecycleBoundObserver和ObserverWrapper片段:

    class LifecycleBoundObserver extends ObserverWrapper implements GenericLifecycleObserver {
        @NonNull final LifecycleOwner mOwner;
        
        @Override
        boolean shouldBeActive() {
            //至少是STARTED狀態
            return mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED);
        }

        //GenericLifecycleObserver繼承自LifecycleObserver, 當生命週期改變時onStateChanged會被回撥
        @Override
        public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {
            //生命週期處於DESTROYED時, 移除觀察者,mObserver是livedata.observe時傳進來
            if (mOwner.getLifecycle().getCurrentState() == DESTROYED) {
                removeObserver(mObserver);
                return;
            }
            //shouldBeActive()方法實現在當前類, 呼叫isAtLeast(STARTED), 至少是STARTED狀態才是true
            //就是生命週期在STARTED和RESUME狀態時, 才是true
            //接著呼叫了父類的activeStateChanged
            activeStateChanged(shouldBeActive());
        }
    }

    private abstract class ObserverWrapper {
        
        void activeStateChanged(boolean newActive) {
            // mActive狀態相同立即返回
            if (newActive == mActive) {
                return;
            }
            // immediately set active state, so we'd never dispatch anything to inactive
            // owner
            mActive = newActive;
            //就是生命週期在STARTED和RESUME時, mActive為true, 其他為false
            boolean wasInactive = LiveData.this.mActiveCount == 0;
            LiveData.this.mActiveCount += mActive ? 1 : -1;
            //當mActive=true時, mActiveCount會加1, 當mActive=false時, mActiveCount會減1。
            //這裡為什麼要加1和減1呢,結合Activity生命週期去考慮,當Activity.onCreate -> Activity.onStart -> Activity.onResume, State由CREATED->STARTED->RESUMED,這個過程當到STARTED時mActive為true,mActiveCount加1後為1, 接著RESUMED, mActive還是true, 這方法第一句(newActive == mActive)條件成立直接return, mActiveCount此時還是1。
            //當Activity.onResume->Activity.onPause->Activity.onStop,State由RESUMED->STARTED->CREATED, Activity.onStop後State為CREATED, isAtLeast(STARTED)就為false, 此時mActive=false, mActiveCount減1,mActiveCount為0啦。
            //當Activity.onStop->Activity.onStart->Activity.onResume, State由CREATED->STARTED->RESUMED, mActive為true,mActiveCount加1後為1, mActiveCount此時為1。
            if (wasInactive && mActive) {
                //根據上面一大段分析後,onActive會在Activity首次顯示UI呼叫,從後臺返回或者從另一個Activity回退後又呼叫一次。(開啟透明Activity不走onStop,這種情況除外)
                onActive();
            }
            if (LiveData.this.mActiveCount == 0 && !mActive) {
                // Activity.onStop後呼叫
                onInactive();
            }
            if (mActive) {
                //分發資料, 跟蹤dispatchingValue方法,會到LiveData.considerNotify方法
                dispatchingValue(this);
            }
        }
    }

    private void considerNotify(ObserverWrapper observer) {
        if (!observer.mActive) { 
            //mActive可以知道是否處於onStop狀態, 是Stop狀態就不要分發資料
            return;
        }
        if (!observer.shouldBeActive()) {
            //shouldBeActive()再次檢查是否observer活著
            //ObserverWrapper的實現類不止LifecycleBoundObserver, 這裡暫時沒看懂為什麼再次檢查
            observer.activeStateChanged(false);
            return;
        }
        if (observer.mLastVersion >= mVersion) {
            //mVersion會在呼叫postValue()或者setValue()處+1
            return;
        }
        observer.mLastVersion = mVersion;
        observer.mObserver.onChanged((T) mData);
        呼叫onChanged分發改變的資料
    }
複製程式碼

接著看postValue方法是是如果把值傳到onChanged裡:

 
protected void postValue(T value) {
    boolean postTask;
    synchronized (mDataLock) { 
        // 一開始看到這裡有點疑問,1. 為什麼加鎖, 1. 為什麼要一個NOT_SET變數(NOT_SET是一個Obj物件)
        // 先帶著疑問往下看
        postTask = mPendingData == NOT_SET;
        mPendingData = value;
    }
    if (!postTask) { // 先往下看,和加鎖問題一起解釋
        return;
    }
    ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);
    // mPostValueRunnable執行在主執行緒
}

private final Runnable mPostValueRunnable = new Runnable() {
    @Override
    public void run() {
        Object newValue;
        synchronized (mDataLock) {  //加鎖, 和postValue裡的鎖是一樣的
            newValue = mPendingData;
            mPendingData = NOT_SET;
        }
        setValue((T) newValue);
    }
};  

@MainThread
protected void setValue(T value) {
    assertMainThread("setValue");
    mVersion++; // 版本號加1,保證mVersion>mLastVersion
    mData = value; // 賦值給mData, 資料會分發到mObserver.onChanged
    dispatchingValue(null);
    
    //到這裡很清晰啦,現在回頭看看postValue和mPostValueRunnable裡的鎖,和NOT_SET的作用
    //1. postValue方法可能會被很多條執行緒呼叫
    //2. mPostValueRunnable非同步執行在主執行緒, 主執行緒是串聯執行的,一次執行一個runnable
    //3. 不加鎖很明顯不行, 多執行緒呼叫時, value從postValue方法賦值給mPendingData, 
    //在mPostValueRunnable主執行緒裡把mPendingData賦值給mData, 
    //mPendingData和mData都是指向同一個物件, mData在主執行緒都是指向了最新的物件。
    //在主執行緒佇列的裡mPostValueRunnable每次執行都是拿了最新的資料,有點多餘
    //4. 假如去掉NOT_SET, value從postValue方法賦值給mPendingData, 
    // 再到setValue的mData, mPendingData和mData都是指向同一個物件,
    // 雖然加了鎖也沒用。但有了NOT_SET就不同啦, mPendingData地址給mData,NOT_SET地址給mPendingData, 
    // 在postValue方法的子執行緒裡新進來的value地址又賦值給mPendingData。
    //5.看回postValue方法裡 if (!postTask) { return } 這個地方,
    // 假如A執行緒呼叫了postValue, 把value賦值給mPendingData並釋放了鎖, 
    // 把A.mPostValueRunnable放到主執行緒的Message中, 
    // 但這時之前的B.mPostValueRunnable還沒執行完,執行到setValue方法,也沒有持有鎖。
    // 這時B執行緒也呼叫了postValue, 這時A.mPostValueRunnable還沒執行,沒有把mPendingData=NOT_SET,
    // B執行緒postTask就為false了, 接著又把value賦值給mPendingData, 接著就直接return。
    // 等A.mPostValueRunnable執行時, mPendingData又被B執行緒更新啦。
    // if (!postTask) { return } 這個判斷能減少主執行緒沒必要mPostValueRunnable執行, 
    // 而且能更新到最新的資料。
    
}

private void dispatchingValue(@Nullable ObserverWrapper initiator) {
    // initiator是null , 下面是遍歷mObservers儲存ObserverWrapper通知所有觀察者
    ...
    for (Iterator<Map.Entry<Observer<T>, ObserverWrapper>> iterator =
         mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {
        considerNotify(iterator.next().getValue()); 
        // considerNotify前面分析過了,看回LiveData.observe的過程, 最終分發資料Observer.onChanged
    }
}
複製程式碼

ViewModel和LiveData結合使用

先說說使用ViewModel和LiveData時注意事項:

  • ViewModel不要持有生命週期的物件,例如activity, fragment。最好context也不要持有
  • 可以使用AndroidViewModel具有application物件
  • activity, fragment不要擁有LiveData物件,LiveData最好儲存在ViewModel裡,ViewModel不會由於配置改變而銷燬
  • activity, fragment也不要擁有資料物件,在Observer.onChange更新UI時,不要把資料存在activity, fragment中
public class UserActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...
        findViewById(R.id.btn_logout).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mViewModel.onLogout(); // 點選按鈕退出登入
            }
        });
        mViewModel = ViewModelProviders.of(this).get(UserViewModel.class);
        //ViewModelProviders是一個工具類,預設包含AndroidViewModelFactory把UserViewModel.class通過反射建立ViewModel並儲存在Activity中
        mViewModel.getUserLiveData().observe(this, new Observer<User>() {
            @Override
            public void onChanged(@Nullable User user) {
            	//在觀察者中更新UI
                if(user != null){
                    mTvName.setText(user.name);
                }else {
                    //沒有登入的狀態
                    mTvName.setText("使用者還沒登入!!!!!");
                }
            }
        });
        mViewModel.loadUserInfo();
    }
}

public class UserViewModel extends ViewModel {
	//userLiveData儲存在UserViewModel
    private final MutableLiveData<User> userLiveData = new MutableLiveData<User>();
    public MutableLiveData<User> getUserLiveData() {
        return userLiveData;
    }
    @Override
    protected void onCleared() {
    	//釋放資源
    }

    public void loadUserInfo() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException ignored) {
                }
                //開啟一個執行緒,非同步操作,建議使用執行緒池,這裡演示就直接new Thread
                User user = new User();
                user.name = "Albert";
                userLiveData.postValue(user); // 通知資料觀察者
            }
        }).start();
    }

    public void onLogout() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException ignored) {
                }
                /開啟一個執行緒,非同步操作
                userLiveData.postValue(null); // 通知資料觀察者
            }
        }).start();
    }
}
複製程式碼

介紹完例子,下面說說優點:

  • 資料更新通知UI更新,資料的更新關聯了生命週期,可以避免onStop時去更新UI.
  • 不需要手動處理生命週期
  • 沒有記憶體洩漏
  • 配置更改,之前載入資料還能使用
  • 可以共享資源, 主要體現在同一個Activity裡fragments可以共享viewmodel,livedata等(這裡不展開,後面有空再分享)

參考:

相關文章