本文章已授權微信公眾號郭霖(guolin_blog)轉載。
本文章主要是對ViewModel進行原始碼分析,建議對著示例程式碼閱讀文章,示例程式碼如下:
本文章使用的是Android SDK 29的原始碼分析。
定義
Android框架管理UI控制器的生命週期(例如:Activity和Fragment),Framework可能決定銷燬或者重新建立一個UI控制器,以響應某些使用者操作或者裝置事件,這些操作或者事件完全超出你的控制。
如果系統銷燬或者重新建立一個UI控制器,那麼你儲存在其中的任何與UI相關的臨時資料都丟失,例如:你的應用程式在某個Activity中包含一個使用者列表,當配置資訊更改重新建立Activity時,新的Activity必須重新獲取使用者列表。對於簡單資料,Activity可以使用onSaveInstanceState()方法,並且在onCreate()方法中從Bundle中恢復資料,但是這種方法只適用於少量的、可以序列化和反序列化的資料,而不是潛在的大量資料的使用者列表或者是很多的Bitmap。
另外一個問題是UI控制器經常需要進行非同步呼叫,這可能需要一些時間才能返回,UI控制器需要管理這些呼叫,並確保系統在銷燬後對其進行清理,以避免潛在的記憶體洩露,這種管理需要大量的維護,並且為了配置更改而重新建立物件的情況下,這是對資源的浪費,因為物件可能不得不重新發出它已經發出的呼叫。
UI控制器(例如:Activity和Fragment)主要用於顯示UI資料、響應使用者操作或者處理作業系統通訊(例如:許可權請求),要求UI控制器也負責從資料庫或者網路載入資料會使類膨脹,將過多的責任分配給UI控制器會導致單個類檢視自己處理應用程式的所有工作,而不是將工作委託給其他類,這樣也會使測試變得更加困難。
將檢視資料所有權從UI控制器的邏輯中分離出來會更加簡單、更有效,所以官方推出這樣一個元件:ViewModel。
ViewModel是一個負責準備和管理Activity或者Fragment的類,它還可以處理Activity和Fragment與應用程式其餘部分的通訊(例如:呼叫業務邏輯類)。
ViewModel總是在一個Activity或者一個Fragment建立的,並且只要對應的Activity或者Fragment處於活動狀態的話,它就會被保留(例如:如果它是個Activity,就會直到它finished)。
換句話說,這意味著一個ViewModel不會因為配置的更改(例如:旋轉)而被銷燬,所有的新例項將被重新連線到現有的ViewModel。
ViewModel的目的是獲取和儲存Activity或者Fragment所需的資訊,Activity或者Fragment應該能夠觀察到ViewModel中的變化,通常通過LiveData或者Android Data Binding公開這些資訊。
要注意的是,ViewModel的唯一職責是管理UI的資料,它不應該訪問你的檢視層次結構或者保留對Activity或者Fragment的引用。
以下這張圖片表示Activity經歷螢幕旋轉而後結束的過程中所處的各種生命週期狀態,還在關聯的Activity生命週期的旁邊顯示了ViewModel的生命週期:
示例程式碼
專案加上如下依賴:
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0-alpha02'
複製程式碼
由於我這邊用到了DataBinding,所以加上如下程式碼:
dataBinding {
enabled = true
}
複製程式碼
專案結構如下圖:
我這邊定義了一個繼承了ViewModel,並且實現了Observable的ObservableViewModel類,來通知控制元件資料的變化,也可以使用LiveData來實現這樣的功能,程式碼如下:
package com.tanjiajun.viewmodeldemo.viewmodel
import androidx.databinding.Observable
import androidx.databinding.PropertyChangeRegistry
import androidx.lifecycle.ViewModel
/**
* Created by TanJiaJun on 2019-11-24.
*/
open class ObservableViewModel : ViewModel(), Observable {
private val callbacks = PropertyChangeRegistry()
override fun addOnPropertyChangedCallback(callback: Observable.OnPropertyChangedCallback?) =
callbacks.add(callback)
override fun removeOnPropertyChangedCallback(callback: Observable.OnPropertyChangedCallback?) =
callbacks.remove(callback)
fun notifyChange() =
callbacks.notifyCallbacks(this, 0, null)
fun notifyPropertyChanged(fieldId: Int) =
callbacks.notifyCallbacks(this, fieldId, null)
}
複製程式碼
第一個例子:ViewModel不會因為配置更改而被銷燬
在MainActivity中建立MainViewModel,程式碼如下:
// MainActivity.kt
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main).also {
it.viewModel = ViewModelProviders.of(this)[MainViewModel::class.java].apply {
name = "譚嘉俊"
age = 25
gender = "男"
}
it.handlers = this
}
}
複製程式碼
這個Activity所對應的介面可以跟隨手機螢幕旋轉,而且沒有通過android:configChanges指定屬性,讓Activity在指定屬性變化的時候,只會呼叫Activity的onConfigurationChanged()方法,而不會被銷燬重建,程式碼如下:
android:configChanges="orientation|screenSize|keyboardHidden"
複製程式碼
當我們旋轉手機螢幕的時候,發現這個Activity的內容沒有發生變化,符合我們的預期。
第二個例子是:Fragment使用Activity共享的ViewModel處理資料
定義NameViewModel,並且繼承我在上面說的ObservableViewModel,程式碼如下:
package com.tanjiajun.viewmodeldemo.viewmodel
import androidx.databinding.Bindable
import androidx.databinding.library.baseAdapters.BR
/**
* Created by TanJiaJun on 2019-11-24.
*/
class NameViewModel : ObservableViewModel() {
@get:Bindable
var name = ""
set(value) {
field = value
notifyPropertyChanged(BR.name)
}
}
複製程式碼
在FirstNameFragment使用一個和NameActivity生命週期相同的NameViewModel,程式碼如下:
// FirstNameFragment.kt
private var viewModel: NameViewModel? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// retainInstance方法下面會解析
retainInstance = true
viewModel = activity?.let {
ViewModelProviders.of(it)[NameViewModel::class.java].apply {
name = "譚嘉俊"
}
}
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? =
DataBindingUtil.inflate<FragmentFirstNameBinding>(
inflater,
R.layout.fragment_first_name,
container,
false
)
.also {
// 使用NameActivity共享的NameViewModel
it.viewModel = viewModel
it.handlers = this
}
.root
複製程式碼
retainInstance方法控制在Activity重新建立(例如:配置更改)期間是否重新建立Fragment例項,如果設為true的話,在Activity重新建立的時候,Fragment的生命週期會有點不一樣,onCreate(Bundle)方法將不會被呼叫,因為Fragment沒有重新建立,onDestroy()不會被呼叫,但是onDetach()方法會被呼叫,因為Fragment只是從它附加的Activity分離而已,**onAttach(Activity)方法和onActivityCreated(Bundle)**方法仍然會被呼叫。
// SecondNameFragment.kt
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? =
DataBindingUtil.inflate<FragmentSecondNameBinding>(
inflater,
R.layout.fragment_second_name,
container,
false
)
.also { it.handlers = this }
.root
// 點選按鈕後會改變NameViewModel中name的屬性值
override fun onChangeNameToAppleClick(view: View) {
activity?.let { ViewModelProviders.of(it)[NameViewModel::class.java].name = "蘋果" }
}
複製程式碼
點選SecondFragment的按鈕後,我們再按後退鍵,退回到上個Fragment,可以看到name已經已經從**”譚嘉俊“變成”蘋果“了,這裡的NameViewModel的生命週期是和NameActivity的生命週期一樣,也就是這兩個Fragment拿到的都是同一個ViewModel**,所以我們可以這樣處理附加在同一個Activity的多個Fragment之間的資料。
原始碼分析
我們看下ViewModelProviders這個類,程式碼如下:
// 建立一個ViewModelProvider,在Fragment處於活動狀態時保留ViewModel
@NonNull
@MainThread
public static ViewModelProvider of(@NonNull Fragment fragment) {
return of(fragment, null);
}
// 建立一個ViewModelProvider,在Activity處於活動狀態時保留ViewModel
@NonNull
@MainThread
public static ViewModelProvider of(@NonNull FragmentActivity activity) {
return of(activity, null);
}
@NonNull
@MainThread
public static ViewModelProvider of(@NonNull Fragment fragment, @Nullable Factory factory) {
// 檢查Fragment是否附加在Application
Application application = checkApplication(checkActivity(fragment));
// 在上面的方法中factory是傳null
if (factory == null) {
// 建立一個單例的AndroidViewModelFactory
factory = ViewModelProvider.AndroidViewModelFactory.getInstance(application);
}
// 建立ViewModelProvider,這裡會拿到Fragment的ViewModelStore,下面會分析
return new ViewModelProvider(fragment.getViewModelStore(), factory);
}
@NonNull
@MainThread
public static ViewModelProvider of(@NonNull FragmentActivity activity,
@Nullable Factory factory) {
// 檢查Activity是否附加在Application
Application application = checkApplication(activity);
// 在上面的方法中factory是傳null
if (factory == null) {
// 建立一個單例的AndroidViewModelFactory
factory = ViewModelProvider.AndroidViewModelFactory.getInstance(application);
}
// 建立ViewModelProvider,這裡會拿到Activity的ViewModelStore,下面會分析
return new ViewModelProvider(activity.getViewModelStore(), factory);
}
複製程式碼
我們看下Activity的**getViewModelStore()**方法,程式碼如下:
@NonNull
@Override
public ViewModelStore getViewModelStore() {
// 檢查Activity是否附加在Application
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) {
// 通過getLastNonConfigurationInstance()方法得到NonConfigurationInstances,下面會分析
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
// 從NonConfigurationInstances恢復ViewModelStore
mViewModelStore = nc.viewModelStore;
}
// 如果是空的話建立ViewModelStore物件
if (mViewModelStore == null) {
mViewModelStore = new ViewModelStore();
}
}
return mViewModelStore;
}
複製程式碼
在分析getLastNonConfigurationInstances()方法之前,我們看下onRetainNonConfigurationInstance()方法,它在Activity類中是一個返回null的方法,我們可以找到Activity的子類ComponentActivity,可以看到它重寫了這個方法,程式碼如下:
// ComponentActivity.java
// NonConfigurationInstances是一個final的靜態類,裡面有兩個變數:custom和viewModelStore
static final class NonConfigurationInstances {
Object custom;
ViewModelStore viewModelStore;
}
@Override
@Nullable
public final Object onRetainNonConfigurationInstance() {
// onRetainCustomNonConfigurationInstance()方法已棄用
Object custom = onRetainCustomNonConfigurationInstance();
ViewModelStore viewModelStore = mViewModelStore;
if (viewModelStore == null) {
// 如果viewModelStore是null的話,證明沒人呼叫getViewModelStore(),所以看看我們最後一個NonConfigurationInstance是否存在ViewModelStore
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
// 如果有的話,就從NonConfigurationInstances取出ViewModelStore
viewModelStore = nc.viewModelStore;
}
}
if (viewModelStore == null && custom == null) {
// 如果ViewModelStore還是null而且custom也是null的話,證明沒有NonConfigurationInstances
return null;
}
// 如果有ViewModelStore或者有custom的話,就建立NonConfigurationInstances物件,並且對其進行賦值
NonConfigurationInstances nci = new NonConfigurationInstances();
nci.custom = custom;
nci.viewModelStore = viewModelStore;
return nci;
}
複製程式碼
onRetainNonConfigurationInstance()方法是在一個Activity因為配置改變而被銷燬時被呼叫,這時就會建立一個新的例項,它會在onStop()方法和onDestroy()方法兩者之間呼叫,我們可以在這裡返回物件,甚至是Activity的例項也可以,之後我們可以在新的Activity的例項通過**getLastNonConfigurationInstance()**方法來檢索,拿到我們想要的物件。
我們之前也用過onSaveInstanceState方法,呼叫這個方法可以在Activity被終止之前檢索每個例項的狀態,以便可以在onCreate方法或者onRestoreInstanceState方法中恢復狀態,兩個方法都會傳入我們之前想要保留的Bundle物件,注意你還要給你的View設定id,因為它是通過id儲存當前有焦點的View,在Android P版本中,這個方法將在onStop()方法之後呼叫,在之前的版本中,這個方法將在onStop()方法之前呼叫,並不能保證它將在onPause()方法之前或之後呼叫,這個方法預設實現儲存了關於Activity的檢視層次狀態的臨時資訊,例如:EditText中的文字和ListView或者RecyclerView中的滾動條位置。
那onSaveInstanceState方法和onRetainNonConfigurationInstance()方法還有什麼區別呢?其中一點是前者是儲存到Bundle,Bundle是有型別限制和大小限制的,而且也要在主執行緒序列化和反序列化資料,而後者是儲存到Object,型別和大小都沒有限制。
我們繼續看原始碼,從上面分析可知,會建立ViewModelStore物件,我們看下ViewModelStore的原始碼,程式碼如下:
public class ViewModelStore {
// 建立一個key為String,value為ViewModel的HashMap物件
private final HashMap<String, ViewModel> mMap = new HashMap<>();
final void put(String key, ViewModel viewModel) {
ViewModel oldViewModel = mMap.put(key, viewModel);
if (oldViewModel != null) {
oldViewModel.onCleared();
}
}
final ViewModel get(String key) {
return mMap.get(key);
}
Set<String> keys() {
return new HashSet<>(mMap.keySet());
}
// 清除內部儲存並且通知儲存在這個HashMap中的所有的ViewModel不再被使用
public final void clear() {
for (ViewModel vm : mMap.values()) {
vm.clear();
}
mMap.clear();
}
}
複製程式碼
它是通過HashMap存放ViewModel的,然後我們回到上面ViewModelProviders的of方法,可以看到它建立了ViewModelProvider物件,看下它的構造方法,程式碼如下:
// ViewModelProvider.java
public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
mFactory = factory;
mViewModelStore = store;
}
複製程式碼
構造方法就兩個引數,第一個是ViewModelStore,用於存放ViewModel;第二個引數是Factory,用於例項化多個ViewModel的工廠。
在我們的示例程式碼中,我們呼叫了ViewModelProvider的get方法,傳入的是我們建立的ViewModel的Class物件,看下相關的程式碼,程式碼如下:
// ViewModelProvider.java
private static final String DEFAULT_KEY =
"androidx.lifecycle.ViewModelProvider.DefaultKey";
@NonNull
@MainThread
public <T extends ViewModel> T get(@NonNull Class<T> modelClass) {
// 得到ViewModel的Class物件的Java語言規範定義的底層類的規範名稱
String canonicalName = modelClass.getCanonicalName();
if (canonicalName == null) {
throw new IllegalArgumentException("Local and anonymous classes can not be ViewModels");
}
// 呼叫下面的get方法,傳入“DEFAULT_KEY:modelClass的規範名稱”字串和ViewModel的Class物件
return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
}
@SuppressWarnings("unchecked")
@NonNull
@MainThread
public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
// 通過key從ViewModelStore中的HashMap中得到ViewModel
ViewModel viewModel = mViewModelStore.get(key);
// 判斷從ViewModelStore中得到的ViewModel是否是Class物件的一個例項,也就是說判斷ViewModelStore中是否存在我們想要的ViewModel
if (modelClass.isInstance(viewModel)) {
// 如果有的話就返回對應的ViewModel
return (T) viewModel;
} else {
//noinspection StatementWithEmptyBody
if (viewModel != null) {
// TODO: log a warning.
}
}
// 如果沒有的話就建立ViewModel
if (mFactory instanceof KeyedFactory) {
viewModel = ((KeyedFactory) (mFactory)).create(key, modelClass);
} else {
// 根據上面的程式碼可知,mFactory是Factory的實現類NewInstanceFactory的子類AndroidViewModelFactory,所以我們呼叫的是這段邏輯
viewModel = (mFactory).create(modelClass);
}
// 將建立好的ViewModel存放到ViewModelStore
mViewModelStore.put(key, viewModel);
// 返回ViewModel
return (T) viewModel;
}
複製程式碼
我們看下AndroidViewModelFactory的create方法,程式碼如下:
// ViewModelProvider中的靜態內部類AndroidViewModelFactory
@NonNull
@Override
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
// 判斷AndroidViewModel所表示的類或介面是否與modelClass所表示的類或介面相同,或者是否為其超類或超介面
if (AndroidViewModel.class.isAssignableFrom(modelClass)) {
//noinspection TryWithIdenticalCatches
try {
// 建立ViewModel物件
return modelClass.getConstructor(Application.class).newInstance(mApplication);
} catch (NoSuchMethodException e) {
throw new RuntimeException("Cannot create an instance of " + modelClass, e);
} catch (IllegalAccessException e) {
throw new RuntimeException("Cannot create an instance of " + modelClass, e);
} catch (InstantiationException e) {
throw new RuntimeException("Cannot create an instance of " + modelClass, e);
} catch (InvocationTargetException e) {
throw new RuntimeException("Cannot create an instance of " + modelClass, e);
}
}
// 根據我們的示例程式碼,我們傳入的modelClass不是AndroidViewModel,而且也不是為其超類或者超介面,所以會執行以下邏輯
return super.create(modelClass);
}
複製程式碼
ViewModel是不能傳入任何有Context引用的物件,這樣導致記憶體洩露,如果需要使用的話,可以使用AndroidViewModel。
然後會呼叫它的父類NewInstanceFactory的create方法,程式碼如下:
@SuppressWarnings("ClassNewInstance")
@NonNull
@Override
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
//noinspection TryWithIdenticalCatches
try {
// 建立由modelClass類物件表示的類的新例項
return modelClass.newInstance();
} catch (InstantiationException e) {
throw new RuntimeException("Cannot create an instance of " + modelClass, e);
} catch (IllegalAccessException e) {
throw new RuntimeException("Cannot create an instance of " + modelClass, e);
}
}
複製程式碼
剛才我們看的是Activity的getViewModelStore()方法,現在看下Fragment的**getViewModelStore()**方法,程式碼如下:
// Fragment.java
@NonNull
@Override
public ViewModelStore getViewModelStore() {
// 判斷Fragment是否已經與Activity分離
if (mFragmentManager == null) {
throw new IllegalStateException("Can't access ViewModels from detached fragment");
}
return mFragmentManager.getViewModelStore(this);
}
複製程式碼
呼叫了FragmentManagerImpl的getViewModelStore方法,程式碼如下:
// FragmentManagerImpl.java
@NonNull
ViewModelStore getViewModelStore(@NonNull Fragment f) {
return mNonConfig.getViewModelStore(f);
}
複製程式碼
成員變數mNonConfig是FragmentManagerVIewModel的引用,我們看下FragmentManagerViewModel的getViewModelStore方法,程式碼如下:
// FragmentManagerViewModel.java
@NonNull
ViewModelStore getViewModelStore(@NonNull Fragment f) {
ViewModelStore viewModelStore = mViewModelStores.get(f.mWho);
if (viewModelStore == null) {
viewModelStore = new ViewModelStore();
mViewModelStores.put(f.mWho, viewModelStore);
}
return viewModelStore;
}
複製程式碼
成員變數mViewModelStores是key為String、value為ViewModelStore的HashMap的引用,它是跟隨Fragment的生命週期,根據Frament的內部唯一名稱從這個HashMap中得到ViewModelStore,如果是空的話,就建立一個新的ViewModelStore物件,並且放入mViewModelStores,然後返回這個物件;如果不是空的話,就返回剛才從HashMap取得的ViewModelStore。
到這裡,ViewModel的建立和得到的原始碼就分析得差不多了,然後我們看下ViewModel什麼時候被銷燬,在上面分析ViewModelStore原始碼的時候,我們看到有個clear方法,這個方法用來清除內部儲存並且通知儲存在這個HashMap中的所有的ViewModel不再被使用,如果ViewModel跟隨的Activity的生命週期的話,它會在如下程式碼呼叫這個方法:
public ComponentActivity() {
// 省略部分程式碼
getLifecycle().addObserver(new LifecycleEventObserver() {
@Override
public void onStateChanged(@NonNull LifecycleOwner source,
@NonNull Lifecycle.Event event) {
// 判斷是否接收到Activity的destroy狀態
if (event == Lifecycle.Event.ON_DESTROY) {
// 如果接收到,判斷是否因為配置更改導致的destroy
if (!isChangingConfigurations()) {
// 如果不是,呼叫ViewModelStore的clear方法
getViewModelStore().clear();
}
}
}
});
// 省略部分程式碼
}
複製程式碼
如果ViewModel跟隨的是Fragment的生命週期的話,它會在如下程式碼呼叫這個方法:
// FragmentManagerViewModel.java
void clearNonConfigState(@NonNull Fragment f) {
if (FragmentManagerImpl.DEBUG) {
Log.d(FragmentManagerImpl.TAG, "Clearing non-config state for " + f);
}
// 清除並且刪除Fragment的子配置狀態
FragmentManagerViewModel childNonConfig = mChildNonConfigs.get(f.mWho);
if (childNonConfig != null) {
childNonConfig.onCleared();
mChildNonConfigs.remove(f.mWho);
}
// 清除並且刪除Fragment的ViewModelStore
ViewModelStore viewModelStore = mViewModelStores.get(f.mWho);
if (viewModelStore != null) {
viewModelStore.clear();
mViewModelStores.remove(f.mWho);
}
}
複製程式碼
在如下程式碼呼叫clearNonConfigState這個方法:
// FragmentManagerImpl.java
@SuppressWarnings("ReferenceEquality")
void moveToState(Fragment f, int newState, int transit, int transitionStyle,
boolean keepActive) {
// 省略部分程式碼
if (f.mState <= newState) {
// 省略部分程式碼
} else if (f.mState > newState) {
switch (f.mState) {
// 省略部分程式碼
case Fragment.CREATED:
if (newState < Fragment.CREATED) {
// 省略部分程式碼
if (f.getAnimatingAway() != null || f.getAnimator() != null) {
// 省略部分程式碼
} else {
if (DEBUG) Log.v(TAG, "movefrom CREATED: " + f);
boolean beingRemoved = f.mRemoving && !f.isInBackStack();
// 判斷Fragment是否正在remove,同時還沒放入後退棧,或者判斷是否FragmentManagerViewModel是否應該銷燬
if (beingRemoved || mNonConfig.shouldDestroy(f)) {
boolean shouldClear;
// 判斷mHost是否是ViewModelStoreOwner的例項
if (mHost instanceof ViewModelStoreOwner) {
// 如果是,shouldClear的值就是FragmentManagerViewModel是否已經清除
shouldClear = mNonConfig.isCleared();
} else if (mHost.getContext() instanceof Activity) {
Activity activity = (Activity) mHost.getContext();
shouldClear = !activity.isChangingConfigurations();
} else {
shouldClear = true;
}
// 根據beingRemoved或者shouldClear的值來判斷是否需要清除ViewModel
if (beingRemoved || shouldClear) {
// 如果是,呼叫clearNonConfigState方法
mNonConfig.clearNonConfigState(f);
}
// 執行Fragment的onDestroy()方法
f.performDestroy();
dispatchOnFragmentDestroyed(f, false);
} else {
f.mState = Fragment.INITIALIZING;
}
// 省略部分程式碼
}
}
}
}
// 省略部分程式碼
}
複製程式碼
到這裡,ViewModel的銷燬的原始碼分析得差不多了。
onSaveInstanceState和ViewModel
onSaveInstanceState是生命週期的一個回撥方法,用來儲存以下兩種狀態下的少量UI相關的資料:
- 應用的程式在後臺的時候由於記憶體限制而被終止。
- 配置更改。
onSaveInstanceState不是被設計用來儲存類似Bitmap這樣大的資料,而是儲存小的、與UI相關的、能夠被序列化和反序列化的資料,上面也提及過了,這裡就不再贅述了。
ViewModel有以下好處:
- ViewModel可以架構設計更加良好,UI程式碼和資料分離,使程式碼更加遵循單一職責原則、更加模組化、更易於測試。
- ViewModel能儲存更大、更復雜的資料,而且資料型別也沒有限制,甚至可以儲存Activity例項。
要注意的是,ViewModel只能在配置更改造成相關的銷燬下得到保留,而不能在被終止的程式中得到保留,也就是說在應用的程式在後臺的時候由於記憶體限制而被終止,ViewModel也會被銷燬。
因此我們最好兩者結合來處理儲存和恢復UI狀態,如果要保證資料不丟失,就要對資料進行本地持久化。
題外話
如果應用在特定配置更改期間無需更新資源,並且因效能限制你需要避免Activity重啟,則可宣告Activity自行處理配置更改,從而阻止系統重啟Activity。
我們可以通過在AndroidManifest檔案中,找到相應**元素,新增android:configChanges屬性,在宣告多個配置值的時候,可以通過|**字元對其進行分隔。
Android官方不建議對大多數應用使用此方法,因為這樣做可能會提高使用備用資源的難度。
有如下屬性:
-
density:顯示密度發生變更,例如:使用者可能已指定不同的顯示比例,或者有不同的顯示現處於活躍狀態。**請注意:**此項為 API 級別 24 中的新增配置。
-
fontScale:字型縮放係數發生變更,例如:使用者已選擇新的全域性字號。
-
keyboard:鍵盤型別發生變更,例如:使用者插入外接鍵盤。
-
keyboardHidden:鍵盤無障礙功能發生變更,例如:使用者顯示硬鍵盤。
-
layoutDirection:佈局方向發生變更,例如:自從左至右 (LTR) 更改為從右至左 (RTL)。**請注意:**此項為 API 級別 17 中的新增配置。
-
locale:語言區域發生變更,例如:使用者已為文字選擇新的顯示語言。
-
mcc:IMSI 移動裝置國家/地區程式碼 (MCC) 發生變更,例如:檢測到 SIM 並更新 MCC。
-
mnc:IMSI 移動裝置網路程式碼 (MNC) 發生變更,例如:檢測到 SIM 並更新 MNC。
-
navigation:導航型別(軌跡球/方向鍵)發生變更。(這種情況通常不會發生。)
-
orientation:螢幕方向發生變更,例如:使用者旋轉裝置。請注意:如果應用面向 Android 3.2(API 級別 13)或更高版本的系統,則還應宣告screenSize配置,因為當裝置在橫向與縱向之間切換時,該配置也會發生變更。
-
screenLayout:螢幕佈局發生變更,例如:不同的顯示現可能處於活躍狀態。
-
screenSize:當前可用螢幕尺寸發生變更。該值表示當前可用尺寸相對於當前縱橫比的變更,當使用者在橫向與縱向之間切換時,它便會發生變更。**請注意:**此項為 API 級別 13 中的新增配置。
-
smallestScreenSize:物理螢幕尺寸發生變更。該值表示與方向無關的尺寸變更,因此它只有在實際物理螢幕尺寸發生變更(如切換到外部顯示器)時才會變化。對此配置所作變更對應smallestWidth配置的變化。**請注意:**此項為 API 級別 13 中的新增配置。
-
touchscreen:觸控式螢幕發生變更。(這種情況通常不會發生。)
-
uiMode:介面模式發生變更,例如:使用者已將裝置置於桌面或車載基座,或者夜間模式發生變更。如需瞭解有關不同介面模式的更多資訊,請參閱UiModeManager。**請注意:**此項為 API 級別 8 中的新增配置。
所有這些配置變更都可能影響到應用所看到資源值,因此呼叫**onConfigurationChanged()**方法時,通常有必要再次檢索所有資源(包括檢視佈局、可繪製物件等),以正確處理變更。
我的GitHub:TanJiaJunBeyond
Android通用框架:Android通用框架(Kotlin-MVVM)
我的掘金:譚嘉俊
我的簡書:譚嘉俊
我的CSDN:譚嘉俊