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 類圖
Event and State
Even 和 State 是 Lifecycle 中很重要的概念,結合自己的程式碼與 google 官方的配圖相信應該很容易理解.
關鍵原始碼
我們在檢視原始碼時發現 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 的使用
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
構造方法傳遞兩個引數 ViewModelStore
和 Factory
,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;
}
複製程式碼
總結一下:
- 如果使用的 support 庫為 27.1.0 以上則 Activity 和 Fragment 都實現了
ViewModelStoreOwner
介面,提供ViewModelStore
例項 - 如果使用的 support 庫為 27.1.0 以下,則會建立一個 HolderFragment,同樣實現了
ViewModelStoreOwner
介面,提供ViewModelStore
例項 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 的記憶體洩漏