概述
Fragment 这个应该不是很陌生的东西,在这里就不做过多的介绍了。 但他有一个方法比较有意思,可以使其Fragment 的实例对象保存在Activity, 不受横竖屏切换导致重新生成对象。这就是我想介绍的Fragment 方法 setRetainInstance
作用
一般的Fragment 所在的Activity 被系统意外杀死重启后,Fragment 会在Activity onCreate 做恢复处理。
但如果在Fragment的内部有工作线程任务,例如加载资源,下载文件,加载图片等。 这些在恢复的时后都需要重新生成新的工作线程任务,原来的工作线程任务必须跟随原来的Fragment销毁而中断,不然会引起内存泄露。诚然这种运行机制可以保证Fragment能恢复相应的状态,但原来的工作任务都被取消了,而需要重新开启。
这样对整个系统都是一种浪费行为, 如果对性能要求比较高的环境下,这是很头疼的问题。
在内存不足的时候会触发android LML ,Linux oom killer ,等杀进程操作,还会有swap等交换处理,相当于消耗cpu资源和io资源。这都对系统稳定造成很大的困扰。
Fragment 方法 setRetainInstance 方法可以保证实例在整个Activity生命周期内一直存在,无需重新创建新的Fragment对象,那些依赖Fragment的工作线程也不会被舍弃,可以继续工作。
方法实现
public void setRetainInstance(boolean retain) {
if (retain && mParentFragment != null) {
throw new IllegalStateException(
"Can't retain fragements that are nested in other fragments");
}
mRetainInstance = retain;
}
这个方法还是很简单的,一个布尔值变量控制,不过需要注意的是如果传进来的参数retain为真,mParentFragment不为空则会抛出异常,大概的还以在这个异常消息也描述的很清楚, 不能保留在嵌套的fragment 。
再看看mRetainInstance这个定义
// If set this fragment would like its instance retained across
// configuration changes.
boolean mRetainInstance;
这里注释给了个信息:如果设置了此片段,则希望其实例在配置更改中保留下来。
方法和变量都看完,再看mRetainInstance的相关调用位置
前面两个是在类FragmentState里面赋值
第一个是在FragmentState构造的时候把fragment的信息传递给FragmentState
public FragmentState(Fragment frag) {
mClassName = frag.getClass().getName();
mIndex = frag.mIndex;
mFromLayout = frag.mFromLayout;
mFragmentId = frag.mFragmentId;
mContainerId = frag.mContainerId;
mTag = frag.mTag;
mRetainInstance = frag.mRetainInstance;
mDetached = frag.mDetached;
mArguments = frag.mArguments;
}
第二个是把FragmentState转换成Fragment实例
public Fragment instantiate(Activity activity, Fragment parent) {
//这个很关键,当mInstance不为空的时候直接返回mInstance,无需做后续的实例创建和数据恢复
//后面在详细讲解mInstance什么时候不为空
if (mInstance != null) {
return mInstance;
}
if (mArguments != null) {
mArguments.setClassLoader(activity.getClassLoader());
}
mInstance = Fragment.instantiate(activity, mClassName, mArguments);
if (mSavedFragmentState != null) {
mSavedFragmentState.setClassLoader(activity.getClassLoader());
mInstance.mSavedFragmentState = mSavedFragmentState;
}
mInstance.setIndex(mIndex, parent);
mInstance.mFromLayout = mFromLayout;
mInstance.mRestored = true;
mInstance.mFragmentId = mFragmentId;
mInstance.mContainerId = mContainerId;
mInstance.mTag = mTag;
mInstance.mRetainInstance = mRetainInstance;
mInstance.mDetached = mDetached;
mInstance.mFragmentManager = activity.mFragments;
if (FragmentManagerImpl.DEBUG) Log.v(FragmentManagerImpl.TAG,
"Instantiated fragment " + mInstance);
return mInstance;
}
FragmentState相当于Fragment的记录对象, 由于FragmentState是一个Parcelable序列化对象,是可以跨进程传递。至于为什么要多出一个序列化的FragmentState类,下面在详细解释。
后面的第三个到第五个是Fragment的调用
第三个就是setRetainInstance 这个方法,这个在上面已经讲过。
第四个是对应的getRetainInstance方法
final public boolean getRetainInstance() {
return mRetainInstance;
}
第五个是dump方法,这个可以通过dumpsys 命令查询fragment状态
第六个,也是比较关键的一个方法
frameworks/base/core/java/android/app/FragmentManager.java
FragmentManagerImpl类的retainNonConfig方法
ArrayList<Fragment> retainNonConfig() {
ArrayList<Fragment> fragments = null;
if (mActive != null) {
for (int i=0; i<mActive.size(); i++) {
Fragment f = mActive.get(i);
//当fragmnet不为空并且mRetainInstance为真则添加到fragments的list集合
if (f != null && f.mRetainInstance) {
if (fragments == null) {
fragments = new ArrayList<Fragment>();
}
fragments.add(f);
f.mRetaining = true;
f.mTargetIndex = f.mTarget != null ? f.mTarget.mIndex : -1;
if (DEBUG) Log.v(TAG, "retainNonConfig: keeping retained " + f);
}
}
}
return fragments;
}
这里先总结一下几个调用的位置,主要有2部分
1 mRetainInstance 在FragmentState和Fragment之间互相传递
2 通过mRetainInstance判断是否添加到集合中。
这都是在干什么呢?
通过上下文 fragment 保存和恢复有两个分支机制,区别这两个分支的关键点就是mRetainInstance这个变量
分支机制1 onSaveInstanceState 的保存
通过查找FragmentState的创建在 FragmentManagerImpl类的saveAllState调用
Parcelable saveAllState() {
//省略...
FragmentState fs = new FragmentState(f);
//省略...
}
此方法字面意思翻译为保存全部状态,可以理解保存所有fragment状态。
在看saveAllState在Activity的onSaveInstanceState内调用。
protected void onSaveInstanceState(Bundle outState) {
outState.putBundle(WINDOW_HIERARCHY_TAG, mWindow.saveHierarchyState());
Parcelable p = mFragments.saveAllState();
if (p != null) {
outState.putParcelable(FRAGMENTS_TAG, p);
}
getApplication().dispatchActivitySaveInstanceState(this, outState);
}
这个我们可以知道onSaveInstanceState 是直接可以保存到AMS去的。
具体onSaveInstanceState的流程机制请看 Activity的内部实例保存-onSaveInstanceState
分支机制2 retainNonConfigurationInstances 的保存
回调之前我们调用的第六个位置retainNonConfig这个方法,在Activity的retainNonConfigurationInstances方法中调用
NonConfigurationInstances retainNonConfigurationInstances() {
Object activity = onRetainNonConfigurationInstance();
HashMap<String, Object> children = onRetainNonConfigurationChildInstances();
ArrayList<Fragment> fragments = mFragments.retainNonConfig();
//省略...
NonConfigurationInstances nci = new NonConfigurationInstances();
nci.activity = activity;
nci.children = children;
nci.fragments = fragments;
nci.loaders = mAllLoaderManagers;
return nci;
}
retainNonConfig返回的集合包含到NonConfigurationInstances对象, 这个是可以在配置切换的时候还可以保留对象的。
具体retainNonConfigurationInstances流程机制请看Activity的内部实例保存-onRetainNonConfigurationInstance
fragment的恢复是在Activity 的onCreate方法中的
protected void onCreate(Bundle savedInstanceState) {
//省略...
if (savedInstanceState != null) {
Parcelable p = savedInstanceState.getParcelable(FRAGMENTS_TAG);
mFragments.restoreAllState(p, mLastNonConfigurationInstances != null
? mLastNonConfigurationInstances.fragments : null);
}
mFragments.dispatchCreate();
getApplication().dispatchActivityCreated(this, savedInstanceState);
mCalled = true;
}
我们主要看这句话
mFragments.restoreAllState(p, mLastNonConfigurationInstances != null
? mLastNonConfigurationInstances.fragments : null);
mLastNonConfigurationInstances这个是上次销毁传递过来的需要保留的对象。
mLastNonConfigurationInstances.fragments 这个就是之前retainNonConfigurationInstances这个方法内部 mFragments.retainNonConfig()这句话获取的fragment集合,完整的把需要保留的fragment保留了下来。
在接下来看看restoreAllState这个方法具体怎么恢复的
void restoreAllState(Parcelable state, ArrayList<Fragment> nonConfig) {
// If there is no saved state at all, then there can not be
// any nonConfig fragments either, so that is that.
if (state == null) return;
FragmentManagerState fms = (FragmentManagerState)state;
if (fms.mActive == null) return;
// First re-attach any non-config instances we are retaining back
// to their saved state, so we don't try to instantiate them again.
if (nonConfig != null) {
for (int i=0; i<nonConfig.size(); i++) {
Fragment f = nonConfig.get(i);
if (DEBUG) Log.v(TAG, "restoreAllState: re-attaching retained " + f);
FragmentState fs = fms.mActive[f.mIndex];
//1 这个很关键,如果是保留集合的fragment,提前赋值给FragmentState对象的mInstance变量
fs.mInstance = f;
f.mSavedViewState = null;
f.mBackStackNesting = 0;
f.mInLayout = false;
f.mAdded = false;
f.mTarget = null;
if (fs.mSavedFragmentState != null) {
fs.mSavedFragmentState.setClassLoader(mActivity.getClassLoader());
f.mSavedViewState = fs.mSavedFragmentState.getSparseParcelableArray(
FragmentManagerImpl.VIEW_STATE_TAG);
}
}
}
// Build the full list of active fragments, instantiating them from
// their saved state.
mActive = new ArrayList<Fragment>(fms.mActive.length);
if (mAvailIndices != null) {
mAvailIndices.clear();
}
for (int i=0; i<fms.mActive.length; i++) {
FragmentState fs = fms.mActive[i];
if (fs != null) {
//2 这个之前有看过这个方法instantiate,主要是把FragmentState转换成Fragment,我们在下满展开解释说明
Fragment f = fs.instantiate(mActivity, mParent);
if (DEBUG) Log.v(TAG, "restoreAllState: active #" + i + ": " + f);
mActive.add(f);
// Now that the fragment is instantiated (or came from being
// retained above), clear mInstance in case we end up re-restoring
// from this FragmentState again.
fs.mInstance = null;
} else {
mActive.add(null);
if (mAvailIndices == null) {
mAvailIndices = new ArrayList<Integer>();
}
if (DEBUG) Log.v(TAG, "restoreAllState: avail #" + i);
mAvailIndices.add(i);
}
}
//省略...
}
这个方法主要有两个地方是我们比较关系的
1 fs.mInstance = f; 相当于提前把保留下来的fragment直接发给FragmentState内部变量mInstance
2 Fragment f = fs.instantiate(mActivity, mParent); FragmentState转换Fragment
我们再一次看instantiate这个方法,有两个恢复分支
public Fragment instantiate(Activity activity, Fragment parent) {
//1 由于之前如果有保留的fragment集合的fragment,则mInstance已经被赋值过了,不会为空,直接返回mInstance。
if (mInstance != null) {
return mInstance;
}
if (mArguments != null) {
mArguments.setClassLoader(activity.getClassLoader());
}
//2 如果上面不是保留的framgnet 会重新创建新的fragment。
mInstance = Fragment.instantiate(activity, mClassName, mArguments);
//后面在做赋值操作,这里直接省略代码...
}
恢复的两个分支也是基于mRetainInstance是否为真, 如果真则直接返回实例,如果是假则需要重新创建Fragment。