本文章已授權微信公衆號郭霖(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:譚嘉俊