Android应用有一个常常被忽略的问题,但问题出现时你又不得不面对。比如
Activity横竖屏转换时Fragment重影
应用长时间处于后台,并用户重新切到前台时,Activity显示异样或者需要等待一段时间才能显示内容
这类问题都与Activity的恢复重建机制相关,如果你想知道怎么解决这类问题,以及Activity恢复重建机制内部原理。这篇文或许能够帮到你。
并不是任何Activity的销毁行为都会触发Activity数据的保存**。只有销毁行为是被系统发起的并且今后有可能恢复的时候才会触发**。
触发恢复重建机制就是两大类
当由系统发起而非人为手动关闭Activity的时候,Activity有可能在未来的某个时机恢复重建。Android系统提供了两套机制,用以保存和恢复界面状态。
这两套机制我个人分别给其取名为 Save-Restore InstanceState机制和RetainNonConfiguration机制
对于开发者时机操作层面来说,Save-Restore InstanceState机制的核心就是Activity中 onSaveInstanceState() 、onCreate()和onRestoreInstanceState()这三个回调方法。
#Activity
protected void onSaveInstanceState(@NonNull Bundle outState) {
//A、整个view树中的view相关信息有机会保存到整个bundle中
outState.putBundle(WINDOW_HIERARCHY_TAG, mWindow.saveHierarchyState());
outState.putInt(LAST_AUTOFILL_ID, mLastAutofillId);
Parcelable p = mFragments.saveAllState();
if (p != null) {
outState.putParcelable(FRAGMENTS_TAG, p);
}
if (mAutoFillResetNeeded) {
outState.putBoolean(AUTOFILL_RESET_NEEDED, true);
getAutofillManager().onSaveInstanceState(outState);
}
dispatchActivitySaveInstanceState(outState);
}
被系统销毁又重建的Activity onCreate(Bundle savedInstanceState)回调方法中savedInstanceState的方法参数不为null。可以在这个位置取出被系统杀死之前保存的一些状态信息用来构建Activity。
如果Activity是被系统重建的,会触发onRestoreInstanceState(savedInstanceState)方法,开发者可以在savedInstanceState中取出之前被系统销毁时存储的数据,用以在新Activity中恢复状态。
onRestoreInstanceState调用时机是在onStart()之后被调用
默认地,onRestoreInstanceState方法会通过 mWindow.restoreHierarchyState()方法把之前保存的view状态信息分发出去,用以恢复view的状态。
protected void onRestoreInstanceState(@NonNull Bundle savedInstanceState) {
if (mWindow != null) {
Bundle windowState = savedInstanceState.getBundle(WINDOW_HIERARCHY_TAG);
if (windowState != null) {
//window view树有一次机会恢复销毁之前的状态
mWindow.restoreHierarchyState(windowState);
}
}
}
当如横竖屏的切换、语言切换等配置发生改变时也会触发Activity的重建。这种由配置发生改变而导致的Activity重建除了会触发Save-Restore InstanceState机制之外也会触发RetainNonConfigurationInstance机制
RetainNonConfigurationInstance机制的核心是Activity中onRetainNonConfigurationInstance()和 getLastNonConfigurationInstance()这两个回调方法也会回调onRetainNonConfigurationInstance()方法,
用于保存配置发生前的数据,这个数据理论上是没有结构和大小限制的甚至可以把旧Activity本身保存其中。
触发时机会在onStop之前
public Object onRetainNonConfigurationInstance() {
return null;
}
public Object getLastNonConfigurationInstance() {
return mLastNonConfigurationInstances != null
? mLastNonConfigurationInstances.activity : null;
}
坊间流传Jetpack中Viewmodel会比Activity的生命周期长,是怎么回事?
阅读这个章节之前如果你对ViewModel的创建比较了解读起来可能会省力些
在androidx.activity:activity包下的ComponentActivity中关键点,
code 5.5.1
static final class NonConfigurationInstances {
Object custom;
ViewModelStore viewModelStore;
}
引入NonConfigurationInstances类,这个类主要有两个属性,分别用来保存自定义数据和viewModelStore.
code 5.5.2
public final Object onRetainNonConfigurationInstance() {
Object custom = onRetainCustomNonConfigurationInstance();
ViewModelStore viewModelStore = mViewModelStore;
if (viewModelStore == null) {
NonConfigurationInstances nc = (NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) { viewModelStore = nc.viewModelStore; }
}
if (viewModelStore == null && custom == null) {
return null;
}
NonConfigurationInstances nci = new NonConfigurationInstances();
nci.custom = custom;
nci.viewModelStore = viewModelStore;
return nci;
}
重写了onRetainNonConfigurationInstance()方法并把方法设置为了final。onRetainNonConfigurationInstance()方法内部创建NonConfigurationInstances对象nci,把viewModelStore存放到nci,同时收集onRetainCustomNonConfigurationInstance()方法的返回值存在nci里
开发者可重写onRetainCustomNonConfigurationInstance()这个方法返回需要保存的数据。
code 5.5.3
public ComponentActivity() {
...
getLifecycle().addObserver(new LifecycleEventObserver() {
@Override
public void onStateChanged(@NonNull LifecycleOwner source, @NonNull Lifecycle.Event event) {
if (event == Lifecycle.Event.ON_DESTROY) {
mContextAwareHelper.clearAvailableContext()
if (!isChangingConfigurations()) { //不在更改配置状态
getViewModelStore().clear(); //1
}
}
}
});
getLifecycle().addObserver(new LifecycleEventObserver() {
@Override
public void onStateChanged(@NonNull LifecycleOwner source,
@NonNull Lifecycle.Event event) {
ensureViewModelStore();
getLifecycle().removeObserver(this);
}
});
void ensureViewModelStore() { //在NonConfiguration中取出viewModleStore
if (mViewModelStore == null) {
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
mViewModelStore = nc.viewModelStore;
}
if (mViewModelStore == null) {
mViewModelStore = new ViewModelStore();
}
}
}
viewmodel中横竖屏转换,横竖屏切换等配置发生变化导致的重建时,新Activity中可听过ensureViewModelStore()方法获取从旧Activity传递过来viewmodelstore,这样就实现了横竖屏切换的时候viewmodel不丢失。
另外值得注意的点是,Activity onDestory的时候,会通过**isChangingConfigurations()**方法判断activity是否处于配置变化状态,如果不是就会将viewmodelstores清空掉。
这套方式解决了一个问题当Activity由横竖屏转换等配置原因发生变化导致Activity重建的时候,会将旧Activity的viewModelStore传给新Activity。如果不是由于配置发生变化导致的Activity重建会清除掉viewModelStore
那有什么方式能解决非配置变化导致Activity重建时保存ViewModel的数据呢?
结论先行,因配置变化引起的Activity重建可以将ViewModleStore保存,在新Activity中可以直接获取旧Activity中的ViewModel。而对于非配置变化引起的Activity重建不能直接将ViewModelStore对象传递给新Activity。AndroidX中是将ViewModel的数据保存到Bundle中,给Bundle分配一个Key,这样ViewModel的保存和恢复就可以通过Save-Restore Stated Instance机制实现。
稍微展开下实现细节
public final class SavedStateHandle {
..
final Map<String, Object> mRegular;
final Map<String, SavedStateProvider> mSavedStateProviders = new HashMap<>();
private final Map<String, SavingStateLiveData<?>> mLiveDatas = new HashMap<>();
private final SavedStateProvider mSavedStateProvider = new SavedStateProvider() {
@SuppressWarnings("unchecked")
@NonNull
@Override
public Bundle saveState() {
...
Set<String> keySet = mRegular.keySet();
ArrayList keys = new ArrayList(keySet.size());
ArrayList value = new ArrayList(keys.size());
for (String key : keySet) {
keys.add(key);
value.add(mRegular.get(key));
}
Bundle res = new Bundle();
//把mRegular保存的数据存放到Bundle中返回
res.putParcelableArrayList("keys", keys);
res.putParcelableArrayList("values", value);
return res;
}
}
带恢复功能的Viewmodel是通过SavedStateViewModelFactory创建,当Activity重建时,会在Activity的onCreate(Bundle data)带后旧Activity存的数据,这bundle中可以取出旧ViewModel的SavedStateHandle对象并以此为构造参数构建ViewModel。这样新建ViewModel就有了旧ViewModel的数据,数据是通过SavedStateHandle对象为介质进行传递的,ViewModel中可以使用对应的key恢复ViewModel的基本数据类型和可序列化的数据类型。
所以,在具备保存-恢复数据特性的ViewModle中获取数据时使用SavedStateHandle对象上的 get(@NonNull String key)方法。获取LiveData()时使用 MutableLiveData
ViewModle中也会存在非序列化的数据(继承了Parcelable或Serializable)或者不能被Bundle存储的对象,如果要保存恢复这些数据怎么实现呢? Lifecycle 2.3.0-alpha03 开始允许设置自定义的SavedStateProvider这样我们可以把非序列化的数据转化成可序列化的数据保存到Bundle中,实现非序列化的数据的保存和恢复。
ViewModel的数据保存和恢复虽然逻辑相对比较简单,但是里面涉及到的类和细节比较繁杂这个章节只是说明了一下实现的核心思想,如果大家想了解内部更多的实现细节,今后可以另开一篇展开聊。
不足处批评指正,望不吝点赞