Android 架構元件 - Lifycycle, LiveData, ViewModel

slyser發表於2018-05-25

Android Architecture Components 提供從管理 UI 元件的生命週期到處理資料永續性的一系列庫,來幫助您設計健壯,易測試和維護的 App。

本文將簡單介紹 Lifcycle, LiveData, ViewModel 的使用並對一些重要的原始碼進行分析

整合

請參閱官方文件 在專案中新增元件

Lifecycle

介紹

假設我們這裡有一個具有生命週期的元件 A (例如 Activity 或 Fragment),而另一個元件 B 需要響應 A 元件的生命週期,傳統的方式是在元件 A 的生命週期依賴元件 B,但是這種方式導致程式碼健壯性較低,同時易導致一系列的錯誤。使用 Lifecycle 元件,您可以將元件 B 的程式碼從元件 A 的生命週期方法中移到元件本身

Lifecycle 的使用

以 MyLocationManager 為例,新增相關生命週期的註解。

class MyLocationManager : LifecycleObserver {

    @OnLifecycleEvent(ON_CREATE)
    fun onCreate() {
        glog("location onCreate")
    }

    @OnLifecycleEvent(ON_START)
    fun onStart() {
        glog("locatoin onstart")
    }

    @OnLifecycleEvent(ON_DESTROY)
    fun onDestroy() {
        glog("locatoin ondestory")
    }
}
複製程式碼

然後建立 MyLocationManager 例項,然後就可以監聽到 Activity 或 Fragment 的生命週期變化。

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val locationManager = LocationManager()
        lifecycle.addObserver(locationManager)
    }
複製程式碼

addObserver 這個方法有一個小細節需要注意一下:

    /**
     * Adds a LifecycleObserver that will be notified when the LifecycleOwner changes
     * state.
     * <p>
     * The given observer will be brought to the current state of the LifecycleOwner.
     * For example, if the LifecycleOwner is in {@link State#STARTED} state, the given observer
     * will receive {@link Event#ON_CREATE}, {@link Event#ON_START} events.
     *
     * @param observer The observer to notify.
     */
    @MainThread
    public abstract void addObserver(@NonNull LifecycleObserver observer);
複製程式碼

如果 LifecycleOwner 當前的狀態是 STATED,當我們呼叫 addObserver 方法時,會接收到 ON_CREATE 和 ON_START events.

以 MyLocationManager 為例說明,當我們在 Activity 的 onResume 方法中 addObserver 時,MyLocationManager 仍然會執行 onCreate 和 onStart 方法.

UML 類圖

UML 類圖

Event and State

Even 和 State 是 Lifecycle 中很重要的概念,結合自己的程式碼與 google 官方的配圖相信應該很容易理解.

states and event

關鍵原始碼

我們在檢視原始碼時發現 Lifecycle 只在 Activity 的 onSaveInstanceState 中 呼叫了 mLifecycleRegistry.markState(Lifecycle.State.CREATED) 方法,在其他生命週期中並沒有 Lifecycle 的相關程式碼,那 support 庫中是怎麼做到 disptch event 的呢?

原來在 onCreate 中有一行關鍵程式碼,它建立了一個不可見的 fragment 去分發 event

ReportFragment.injectIfNeededIn(this);
複製程式碼
    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        dispatchCreate(mProcessListener);
        dispatch(Lifecycle.Event.ON_CREATE);
    }

	...

    @Override
    public void onDestroy() {
        super.onDestroy();
        dispatch(Lifecycle.Event.ON_DESTROY);
        // just want to be sure that we won't leak reference to an activity
        mProcessListener = null;
    }
複製程式碼

思考一下 google 為什麼要採用這樣的方式,而不是直接在 Activity 的生命週期去直接分發 event 呢? 以剛才的 MyLocationManager 為例, 如果列印 Activity 和 MyLocationManager 的完整生命週期日誌,會得到以下結果:

    05-25 06:59:17.382 32431-32431/com.slyser.arc D/arc: activity onCreate
    location onCreate
05-25 06:59:17.385 32431-32431/com.slyser.arc D/arc: activity onStart
    location onStart
05-25 06:59:17.386 32431-32431/com.slyser.arc D/arc: activity onResume
    location onResume
05-25 06:59:36.675 32431-32431/com.slyser.arc D/arc: location onStop
    activity onStop
    location onDestroy
05-25 06:59:36.676 32431-32431/com.slyser.arc D/arc: activity onDestroy
複製程式碼

可以看到進入 Activity 時的執行順序: Activity -> MyLocationManager. 退出 Activity 時的執行順序剛好相反: MyLocationManager -> Activity. 而直接在 Activity 的生命週期中直接分發 event 是達不到這種效果的。

LiveData

介紹

LiveData 是一個可觀察的資料持有者。與常規可觀察性不同,LiveData 具有生命週期感知能力,這意味著它尊從其他應用程式元件(例如 Activity, Fragment, Service)的生命週期。 這種設計確保 LiveData 只更新處於活動生命週期狀態的應用程式元件觀察者。如果觀察者的生命週期處於 STARTED 或 RESUMED 狀態,則 LiveData 會將觀察者視為活動狀態。LiveData 僅將更新通知給活躍的觀察者,未註冊和非活動的觀察者不會收到有關更新的通知。

LiveData 的使用

首先我們先建立一個 LiveData,然後一個簡單的方法呼叫,我們在監聽資料變化時傳入了兩個引數,前者 owner 用於將 livedata 與生命週期繫結,後者監聽資料的變化,這樣你就能使用 LiveData 了

public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<T> observer)
複製程式碼
class MainActivity : AppCompatActivity() {
    val liveData = MutableLiveData<Person>()
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        liveData.observe({this@MainActivity.lifecycle}, {
            it?.let { glog(it.name) }
        })
        findViewById<Button>(R.id.btn).setOnClickListener { 
            liveData.value = Person("zhangsan")
        }
    }
    data class Person(var name:String)
}
複製程式碼

關鍵原始碼

之前介紹概念中有一段這樣的話很重要:

如果觀察者的生命週期處於 STARTED 或 RESUMED 狀態,則 LiveData 會將觀察者視為活動狀態。LiveData 僅將更新通知給活躍的觀察者。

怎麼理解這段話呢,執行如下程式碼,看下日誌的列印內容:

class MainActivity : AppCompatActivity() {
    private val person : Person = Person("live data onCreate")
    val liveData = MutableLiveData<Person>()
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        liveData.observe({this@MainActivity.lifecycle}, {
            it?.let { glog(it.name) }
        })
        liveData.value = person
    }

    override fun onStart() {
        super.onStart()
        person.name = "live data onStart"
    }

    data class Person(var name:String)
}
複製程式碼

日誌列印出的 name 是 live data onStart, 在 onCreate 方法中明明改變了 LiveData 的值,為什麼列印的 name 不是 live data onCreate 呢?

原來我們在雖然在 onCreate 中呼叫了 setValue 方法,但是此時 LiveData 的 active 狀態為 false 並不會通知資料變化.

 @MainThread
    protected void setValue(T value) {
        assertMainThread("setValue");
        mVersion++; // 管理資料變化的版本
        mData = value;
        dispatchingValue(null);
    }
複製程式碼
    private void dispatchingValue(@Nullable ObserverWrapper initiator) {
      	...
        do {
            mDispatchInvalidated = false;
            if (initiator != null) {
                considerNotify(initiator);
                initiator = null;
            } else {
                for (Iterator<Map.Entry<Observer<T>, ObserverWrapper>> iterator =
                        mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {
                    considerNotify(iterator.next().getValue());
                    if (mDispatchInvalidated) {
                        break;
                    }
                }
            }
        } while (mDispatchInvalidated);
        mDispatchingValue = false;
    }
複製程式碼
    private void considerNotify(ObserverWrapper observer) {
        if (!observer.mActive) { // 在 onCreate 中此時的活動狀態為 false
            return;
        }
  		...
         if (observer.mLastVersion >= mVersion) {
            return; //active 狀態變化了,但是資料版本沒有增加,不會呼叫 onChange
        }
        observer.mLastVersion = mVersion;
        observer.mObserver.onChanged((T) mData);
    }
複製程式碼

然後當 Activity 的生命執行到 onStart 後,此時 active 狀態變化為 true,最終呼叫 observer.mObserver.onChanged((T) mData),通知 LiveData 資料變化,但此時 person 的 name 已經變為 live data onStart 了.

 void activeStateChanged(boolean newActive) {
            if (newActive == mActive) {
                return;
            }
    		...
            if (mActive) {
                dispatchingValue(this);
            }
        }
複製程式碼

Transformations

Transformations 可以讓 LiveData 在不同資料型別間進行變換,相關程式碼如下:

       LiveData  userLiveData = ...;
       LiveData  userName = Transformations.map(userLiveData, user -> {
            return user.firstName + " " + user.lastName
       });
     MutableLiveData  userIdLiveData = ...;
       LiveData  userLiveData = Transformations.switchMap(userIdLiveData, id ->
           repository.getUserById(id));
      
       void setUserId(String userId) {
            this.userIdLiveData.setValue(userId);
       }
複製程式碼

ViewModel

介紹

ViewModel 目的在於以生命週期的形式儲存和管理與 UI 相關的資料。 ViewModel 允許資料在配置變化(例如螢幕旋轉)後仍然存活。

生命週期

viewmodel lifecycle

ViewModel 的使用

ViewModel 的使用很簡單,建立一個類繼承 ViewModel

class UserViewModel : ViewModel() {
    
}
複製程式碼

如果你想在 ViewModel 中使用 Context,可以繼承 AndroidViewModel,然後通過一行程式碼即可得到 ViewModel 物件

 val viewModel = ViewModelProviders.of(this).get(UserViewModel::class.java)
複製程式碼

關鍵原始碼

首先看ViewModelProviders.of的相關程式碼

 @NonNull
    @MainThread
    public static ViewModelProvider of(@NonNull FragmentActivity activity,
            @Nullable Factory factory) {
        Application application = checkApplication(activity);
        if (factory == null) {
            factory = ViewModelProvider.AndroidViewModelFactory.getInstance(application);
        }
        return new ViewModelProvider(ViewModelStores.of(activity), factory);
    }
複製程式碼

ViewModelProviders.of 建立了一個 ViewModelProvider 物件,ViewModelProvider 構造方法傳遞兩個引數 ViewModelStoreFactory ,Factory 很簡單,就是讓你自己決定如何建立 ViewModel,如果 factory 為 null 則使用 android 提供的預設實現,ViewModelStore 簡單來說就是個存放 ViewModel 物件的 map,key 預設為類名.

  @NonNull
    @MainThread
    public static ViewModelStore of(@NonNull FragmentActivity activity) {
        if (activity instanceof ViewModelStoreOwner) {
            return ((ViewModelStoreOwner) activity).getViewModelStore();
        }
        return holderFragmentFor(activity).getViewModelStore(); // 相容27.1.0以下版本的 support 庫
    }
複製程式碼

其中ViewModelStoreOwner是一個介面,只有一個方法,在 27.1.0 的 FragmentActivity 已經實現了該介面

    ViewModelStore getViewModelStore();
複製程式碼

27.1.0 以下的版本 google 則通過建立一個不可見的實現 ViewModelStoreOwner介面的 fragment 去做相容,以下是相關關鍵程式碼

        HolderFragment holderFragmentFor(FragmentActivity activity) {
            FragmentManager fm = activity.getSupportFragmentManager();
            HolderFragment holder = findHolderFragment(fm);
            if (holder != null) {
                return holder;
            }
            holder = mNotCommittedActivityHolders.get(activity);
            if (holder != null) {
                return holder;
            }

            if (!mActivityCallbacksIsAdded) {
                mActivityCallbacksIsAdded = true;
                activity.getApplication().registerActivityLifecycleCallbacks(mActivityCallbacks); //註冊生命週期,activity 銷燬時將 HolderFragment 從 mNotCommittedActivityHolders 移除
            }
            holder = createHolderFragment(fm);
            mNotCommittedActivityHolders.put(activity, holder);
            return holder;
        }

複製程式碼

搞清楚了ViewModelStore再回過頭來看ViewModelProvider的 get 方法

 @NonNull
    @MainThread
    public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
        ViewModel viewModel = mViewModelStore.get(key); // mViewModelStore為構造方法傳遞

        if (modelClass.isInstance(viewModel)) { //store 中儲存了 ViewModel 則直接返回
            //noinspection unchecked
            return (T) viewModel;
        } else {
            //noinspection StatementWithEmptyBody
            if (viewModel != null) {
                // TODO: log a warning.
            }
        }

        viewModel = mFactory.create(modelClass); //store 中沒有 ViewModel 則建立一個 ViewModel,並在 ViewModelStore 中的 map 中存放
        mViewModelStore.put(key, viewModel);
        //noinspection unchecked
        return (T) viewModel;
    }
複製程式碼

總結一下:

  1. 如果使用的 support 庫為 27.1.0 以上則 Activity 和 Fragment 都實現了 ViewModelStoreOwner 介面,提供ViewModelStore例項
  2. 如果使用的 support 庫為 27.1.0 以下,則會建立一個 HolderFragment,同樣實現了 ViewModelStoreOwner 介面,提供ViewModelStore例項
  3. ViewModelStore本質是預設 key 為類名 value 為 ViewModel 的 map

在 fragment 間共享資料

我們在不同的 fragment 中呼叫 ViewModelProviders.of() 時,如果參入的引數為 activity,則獲取的 ViewModel 物件為同一例項,程式碼如下:

val viewModel = ViewModelProviders.of(getActivity()).get(UserViewModel::class.java)
複製程式碼

這樣我們就可以在兩個或更多 fragment 間彼此間進行通訊。

最佳實踐

  • 儘可能保持您的 UI 控制器(activity 和 fragment)精簡。他們不應該試圖獲取他們自己的資料;相反,使用 ViewModel 來做到這一點,並通過監聽 LiveData 物件來更新檢視。
  • 嘗試編寫資料驅動的使用者介面,其中您的 UI 控制器的職責是在資料更改時更新檢視,或將使用者操作通知給ViewModel。
  • 把你的資料邏輯放在 ViewModel 類中。 ViewModel 應作為您的 UI 控制器和其他應用程式之間的聯結器。 但要小心,ViewModel 不負責提取資料(例如,來自網路)。 相反,ViewModel 應呼叫相應的元件來獲取資料,然後將結果提供給UI控制器
  • 使用 Data Binding 在檢視和 UI 控制器之間保持乾淨的介面。 這可以使您的檢視更具說明性,並最大限度地減少需要在 activity 和 fragment 中編寫的更新程式碼。 如果你喜歡用 java 程式語言來做到這一點,可以使用像 Butter Knife 這樣的庫來避免樣板程式碼並且能夠更好的抽象。
  • 如果您的 UI 很複雜,請考慮建立一個 presenter 來處理 UI 修改。這可能是一項艱鉅的任務,但它可以使您的 U I元件更易於測試。
  • 避免在 ViewModel 中引用 View 或 Activity 上下文。如果 ViewModel 存活的時間比 Activity(在配置更改的情況下),將會造成 activity 的記憶體洩漏

相關文章