(4.3.1.18)Fragment重叠问题引发的思考:不保留活动下,关于Fragment 状态的保存和恢复的坑
2017-12-28 11:29
661 查看
一前言
二原因探寻
三自动恢复诱发
1 Fragment重叠问题
参考文献
从上面的关键信息可以看出,异常的原因就是因为使用的fragment没有public的empty constructor。事实,也确实如此,我的那个fragment有一个带参数的constructor但是i没有Public的empty constructor。
那么问题来了,为什么Fragment必须要empty constructor?
也就是说:当系统因为内存紧张杀死非前台进程(并非真正的杀死),然后我们将被系统杀掉的非前台app带回前台,如果这个时候有UI是呈现在Fragment中,那么会因为restore造成fragment需要通过反射实例对象,从而将之前save的状态还原,而这个反射实例对象就是fragment需要Public的empty constructor的关键所在
在FragmentActivity.onSaveInstanceState会自动存储fragments
在FragmentActivity.onCreate中回取出FRAGMENTS_TAG的存储的fragments
调用FragmentManagerImpl.restoreAllState
调用FragmentState.instantiate
调用Fragment.instantiate
假设现在处在第一个Tab 图片列表(NormalListFragment),然后点击某个Item进入详情页,由于不保留活动,Fragment所在的Activity会销毁掉。然后,我们从详情页返回到图片列表,Activity会重建,Fragment会重新绑定, 整个过程Activity和Fragment的生命周期方法调用Log如下:
从上面的Log可以发现,重新创建Activity时,NormalListFragment每个周期方法都走了两遍。这意味着同时创建了两个NormalListFragment实例,这个两个NormalListFragment一个是我代码里面主动创建的,另外一个则是上次Activity异常销毁时保存的,因为恢复的这个Fragment没有拿到引用,所以无法去做操作的(隐藏显示),这意味着我切换到其他tab时,这个Fragment会一直显示,这正是Fragment重叠问题的根源所在。
具体的原因也是销毁重建导致的
具体得解决方案参看:
- Fragment重叠问题引发的思考
- 彻底解决Fragment重叠的问题
我踩到的关于Fragment 状态的保存和恢复的坑
浅析Fragment为什么需要Public的empty constructor
彻底解决Fragment重叠的问题
二原因探寻
三自动恢复诱发
1 Fragment重叠问题
参考文献
一、前言
最近,在做一个项目。当app启动后,然后使其进入后台进程(按home键),接着使用其它app(用其它app的目的是为了让系统内存不足,然后让系统将我们的app杀死)。当我们的app被系统杀死后,这时候通过任务管理点击我们的app进入应用。这时候问题出现了,app崩溃了,为了不暴露项目,一些项目包名或者类名的信息就省略了,下面就是异常的关键信息:12-28 11:17:53.175 11121-11121/com.xxx.xxx E/ANDROID_LAB: Error[java.lang.RuntimeException: Unable to start activity ComponentInfo{com.xxx.xxx/com.xxx.xxx.callrank.activity.CallRankMainActivity}: android.support.v4.app.Fragment$InstantiationException: Unable to instantiate fragment com.xxx.xxx.callrank.fragment.CallRankListFragment: make sure class name exists, is public, and has an empty constructor that is public at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2505) at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2577) at android.app.ActivityThread.access$1000(ActivityThread.java:164) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1462) at android.os.Handler.dispatchMessage(Handler.java:102) at android.os.Looper.loop(Looper.java:159) at android.app.ActivityThread.main(ActivityThread.java:5540) at java.lang.reflect.Method.invoke(Native Method) at java.lang.reflect.Method.invoke(Method.java:372) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:964) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:759) Caused by: android.support.v4.app.Fragment$InstantiationException: Unable to instantiate fragment com.xxx.xxx.callrank.fragment.CallRankListFragment: make sure class name exists, is public, and has an empty constructor that is public at android.support.v4.app.Fragment.instantiate(Fragment.java:434) at android.support.v4.app.FragmentState.instantiate(Fragment.java:102) at android.support.v4.app.FragmentManagerImpl.restoreAllState(FragmentManager.java:1907) at android.support.v4.app.FragmentController.restoreAllState(FragmentController.java:135) at android.support.v4.app.FragmentActivity.onCreate(FragmentActivity.java:251) at com.xxx.xxx.base.LoadingSaveActivity.onCreate(LoadingSaveActivity.java:29) at com.xxx.xxx.base.ImmersiveActivity.onCreate(ImmersiveActivity.java:31) at com.xxx.xxx.base.BaseLaunchActivity.onCreate(BaseLaunchActivity.java:63) at com.xxx.xxx.base.BaseFragmentActivity.onCreate(BaseFragmentActivity.java:125) at com.xxx.xxx.base.BaseImageCacheActivity.onCreate(BaseImageCacheActivity.java:24) at com.xxx.xxx.common.activity.BaseActivity.onCreate(BaseActivity.java:96) at android.app.Activity.performCreate(Activity.java:6107) at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1106) at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2458) at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2577) at android.app.ActivityThread.access$1000(ActivityThread.java:164) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1462) at android.os.Handler.dispatchMessage(Handler.java:102) at android.os.Looper.loop(Looper.java:159) at android.app.ActivityThread.main(ActivityThread.java:5540) at java.lang.reflect.Method.invoke(Native Method) at java.lang.reflect.Method.invoke(Method.java:372) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:964) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:759) Caused by: java.lang.InstantiationException: class com.xxx.xxx.callrank.fragment.CallRankListFragment has no zero argument constructor at java.lang.Class.newInstance(Class.java:1597) at android.support.v4.app.Fragment.instantiate(Fragment.java:423) at android.support.v4.app.FragmentState.instantiate(Fragment.java:102) at android.support.v4.app.FragmentManagerImpl.restoreAllState(FragmentManager.java:1907) at android.support.v4.app.FragmentController.restoreAllState(FragmentController.java:135) at android.support.v4.app.FragmentActivity.onCreate(FragmentActivity.java:251) at com.xxx.xxx.base.LoadingSaveActivity.onCreate(LoadingSaveActivity.java:29) at com.xxx.xxx.base.ImmersiveActivity.onCreate(ImmersiveActivity.java:31) at com.xxx.xxx.base.BaseLaunchActivity.onCreate(BaseLaunchActivity.java:63) at com.xxx.xxx.base.BaseFragmentActivity.onCreate(BaseFragmentActivity.java:125) at com.xxx.xxx.base.BaseImageCacheActivity.onCreate(BaseImageCacheActivity.java:24) at com.xxx.xxx.common.activity.BaseActivity.onCreate(BaseActivity.java:96) at android.app.Activity.performCreate(Activity.java:6107) at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1106) at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2458) at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2577) at android.app.ActivityThread.access$1000(ActivityThread.java:164) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1462) at android.os.Handler.dispatchMessage(Handler.java:102) at android.os.Looper.loop(Looper.java:159) at android.app.ActivityThread.main(ActivityThread.java:5540) at java.lang.reflect.Method.invoke(Native Method) at java.lang.reflect.Method.invoke(Method.java:372) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:964) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:759) Caused by: java.lang.NoSuchMethodException: <init> [] at java.lang.Class.getConstructor(Class.java:531) at java.lang.Class.getDeclaredConstructor(Class.java:510) at java.lang.Class.newInstance(Class.java:1595) at android.support.v4.app.Fragment.instantiate(Fragment.java:423) at android.support.v4.app.FragmentState.instantiate(Fragment.java:102) at android.support.v4.app.FragmentManagerImpl.restoreAllState(FragmentManager.java:1907) at android.support.v4.app.FragmentController.restoreAllState(FragmentController.java:135) at android.support.v4.app.FragmentActivity.onCreate(FragmentActivity.java:251) at com.xxx.xxx.base.LoadingSaveActivity.onCreate(LoadingSaveActivity.java:29) at com.xxx.xxx.base.ImmersiveActivity.onCreate(ImmersiveActivity.java:31) at com.xxx.xxx.base.BaseLaunchActivity.onCreate(BaseLaunchActivity.java:63) at com.xxx.xxx.base.BaseFragmentActivity.onCreate(BaseFragmentActivity.java:125) at com.xxx.xxx.base.BaseImageCacheActivity.onCreate(BaseImageCacheActivity.java:24) at com.xxx.xxx.common.activity.BaseActivity.onCreate(BaseActivity.java:96) at android.app.Activity.performCreate(Activity.java:6107) at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1106) at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2458) at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2577) at android.app.ActivityThread.access$1000(ActivityThread.java:164) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1462) at android.os.Handler.dispatchMessage(Handler.java:102) at android.os.Looper.loop(Looper.java:159) at android.app.ActivityThread.main(ActivityThread.java:5540) at java.lang.reflect.Method.invoke(Native Method) at java.lang.reflect.Method.invoke(Method.java:372) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:964) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:759) ]
从上面的关键信息可以看出,异常的原因就是因为使用的fragment没有public的empty constructor。事实,也确实如此,我的那个fragment有一个带参数的constructor但是i没有Public的empty constructor。
那么问题来了,为什么Fragment必须要empty constructor?
二、原因探寻
FragmentActivity在onSaveInstance中会把fragments自动保存,并在重建的onCreate中利用反射,调用其空构造函数重建也就是说:当系统因为内存紧张杀死非前台进程(并非真正的杀死),然后我们将被系统杀掉的非前台app带回前台,如果这个时候有UI是呈现在Fragment中,那么会因为restore造成fragment需要通过反射实例对象,从而将之前save的状态还原,而这个反射实例对象就是fragment需要Public的empty constructor的关键所在
at android.support.v4.app.Fragment.instantiate(Fragment.java:434) at android.support.v4.app.FragmentState.instantiate(Fragment.java:102) at android.support.v4.app.FragmentManagerImpl.restoreAllState(FragmentManager.java:1907) at android.support.v4.app.FragmentController.restoreAllState(FragmentController.java:135) at android.support.v4.app.FragmentActivity.onCreate(FragmentActivity.java:251)
在FragmentActivity.onSaveInstanceState会自动存储fragments
/** * Save all appropriate fragment state. */ @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); Parcelable p = mFragments.saveAllState(); if (p != null) { outState.putParcelable(FRAGMENTS_TAG, p); } }
在FragmentActivity.onCreate中回取出FRAGMENTS_TAG的存储的fragments
@Override protected void onCreate(Bundle savedInstanceState) { //... if (savedInstanceState != null) { Parcelable p = savedInstanceState.getParcelable(FRAGMENTS_TAG); mFragments.restoreAllState(p, nc != null ? nc.fragments : null); } mFragments.dispatchCreate(); }
调用FragmentManagerImpl.restoreAllState
调用FragmentState.instantiate
调用Fragment.instantiate
public static Fragment instantiate(Context context, String fname, Bundle args) { try { Class<?> clazz = sClassMap.get(fname); if (clazz == null) { // Class not found in the cache, see if it's real, and try to add it clazz = context.getClassLoader().loadClass(fname); sClassMap.put(fname, clazz); } Fragment f = (Fragment)clazz.newInstance(); if (args != null) { args.setClassLoader(f.getClass().getClassLoader()); f.mArguments = args; } return f; } catch (ClassNotFoundException e) { throw new InstantiationException("Unable to instantiate fragment " + fname + ": make sure class name exists, is public, and has an" + " empty constructor that is public", e); } catch (java.lang.InstantiationException e) { throw new InstantiationException("Unable to instantiate fragment " + fname + ": make sure class name exists, is public, and has an" + " empty constructor that is public", e); } catch (IllegalAccessException e) { throw new InstantiationException("Unable to instantiate fragment " + fname + ": make sure class name exists, is public, and has an" + " empty constructor that is public", e); } }
三、自动恢复诱发
3.1 Fragment重叠问题
由于Fragment重叠问题是发生在某种特定的情况下,所以在常规环境下很难复现,所以需要在Android 手机开发者选项中把不保留活动这个选项打开,这样每次进入新的Activity,旧的Activity就会马上销毁。假设现在处在第一个Tab 图片列表(NormalListFragment),然后点击某个Item进入详情页,由于不保留活动,Fragment所在的Activity会销毁掉。然后,我们从详情页返回到图片列表,Activity会重建,Fragment会重新绑定, 整个过程Activity和Fragment的生命周期方法调用Log如下:
06-18 21:35:36.479 3004-3004/com.jx.androiddemos I/FragmentLearn: NormalListFragment onPause 06-18 21:35:36.479 3004-3004/com.jx.androiddemos I/FragmentLearn: FragmentMainActivity onPause 06-18 21:35:36.893 3004-3004/com.jx.androiddemos I/FragmentLearn: FragmentMainActivity onSaveInstanceState Bundle[{android:viewHierarchyState=Bundle[{android:views={16908290=android.view.AbsSavedState$1@6962f3e, 2131624006=android.view.AbsSavedState$1@6962f3e, 2131624007=android.view.AbsSavedState$1@6962f3e, 2131624008=android.support.v7.widget.Toolbar$SavedState@bda98cc, 2131624009=android.view.AbsSavedState$1@6962f3e, 2131624052=android.view.AbsSavedState$1@6962f3e, 2131624053=android.view.AbsSavedState$1@6962f3e, 2131624054=android.view.AbsSavedState$1@6962f3e, 2131624055=android.view.AbsSavedState$1@6962f3e, 2131624056=android.view.AbsSavedState$1@6962f3e, 2131624057=android.view.AbsSavedState$1@6962f3e, 2131624058=android.view.AbsSavedState$1@6962f3e, 2131624059=android.view.AbsSavedState$1@6962f3e, 2131624060=android.view.AbsSavedState$1@6962f3e, 2131624061=android.view.AbsSavedState$1@6962f3e, 2131624062=android.view.AbsSavedState$1@6962f3e, 2131624063=android.view.AbsSavedState$1@6962f3e, 2131624064=android.view.AbsSavedState$1@6962f3e}, android:focusedViewId=2131624102}], android:support:fragments=android.support.v4.app.FragmentManagerState@696715}] 06-18 21:35:36.893 3004-3004/com.jx.androiddemos I/FragmentLearn: NormalListFragment onStop 06-18 21:35:36.893 3004-3004/com.jx.androiddemos I/FragmentLearn: FragmentMainActivity onStop 06-18 21:35:36.910 3004-3004/com.jx.androiddemos I/FragmentLearn: NormalListFragment onDestroyView 06-18 21:35:36.914 3004-3004/com.jx.androiddemos I/FragmentLearn: NormalListFragment onDestroy 06-18 21:35:36.914 3004-3004/com.jx.androiddemos I/FragmentLearn: NormalListFragment onDetach 06-18 21:35:36.914 3004-3004/com.jx.androiddemos I/FragmentLearn: FragmentMainActivity onDestroy 06-18 21:35:39.527 3004-3004/com.jx.androiddemos I/FragmentLearn: NormalListFragment onAttach 06-18 21:35:39.527 3004-3004/com.jx.androiddemos I/FragmentLearn: NormalListFragment onCreate 06-18 21:35:39.527 3004-3004/com.jx.androiddemos I/FragmentLearn: FragmentMainActivity onCreate Bundle[{android:viewHierarchyState=Bundle[mParcelledData.dataSize=1512], android:support:fragments=android.support.v4.app.FragmentManagerState@e7e75b8}] 06-18 21:35:39.666 3004-3004/com.jx.androiddemos I/FragmentLearn: NormalListFragment onCreateView Bundle[{android:view_state={2131624102=AbsListView.SavedState{402bf7e selectedId=-9223372036854775808 firstId=-1 viewTop=0 position=0 height=1557 filter=null checkState=null}}}] 06-18 21:35:39.672 3004-3004/com.jx.androiddemos I/FragmentLearn: NormalListFragment onActivityCreated Bundle[{android:view_state={2131624102=AbsListView.SavedState{402bf7e selectedId=-9223372036854775808 firstId=-1 viewTop=0 position=0 height=1557 filter=null checkState=null}}}] 06-18 21:35:39.672 3004-3004/com.jx.androiddemos I/FragmentLearn: NormalListFragment onAttach 06-18 21:35:39.672 3004-3004/com.jx.androiddemos I/FragmentLearn: NormalListFragment onCreate 06-18 21:35:39.673 3004-3004/com.jx.androiddemos I/FragmentLearn: NormalListFragment onCreateView null 06-18 21:35:39.675 3004-3004/com.jx.androiddemos I/FragmentLearn: NormalListFragment onActivityCreated null 06-18 21:35:39.676 3004-3004/com.jx.androiddemos I/FragmentLearn: NormalListFragment onStart 06-18 21:35:39.676 3004-3004/com.jx.androiddemos I/FragmentLearn: NormalListFragment onStart 06-18 21:35:39.676 3004-3004/com.jx.androiddemos I/FragmentLearn: FragmentMainActivity onStart 06-18 21:35:39.679 3004-3004/com.jx.androiddemos I/FragmentLearn: FragmentMainActivity onRestoreInstanceState Bundle[{android:viewHierarchyState=Bundle[{android:views={16908290=android.view.AbsSavedState$1@6962f3e, 2131624006=android.view.AbsSavedState$1@6962f3e, 2131624007=android.view.AbsSavedState$1@6962f3e, 2131624008=android.support.v7.widget.Toolbar$SavedState@80d4056, 2131624009=android.view.AbsSavedState$1@6962f3e, 2131624052=android.view.AbsSavedState$1@6962f3e, 2131624053=android.view.AbsSavedState$1@6962f3e, 2131624054=android.view.AbsSavedState$1@6962f3e, 2131624055=android.view.AbsSavedState$1@6962f3e, 2131624056=android.view.AbsSavedState$1@6962f3e, 2131624057=android.view.AbsSavedState$1@6962f3e, 2131624058=android.view.AbsSavedState$1@6962f3e, 2131624059=android.view.AbsSavedState$1@6962f3e, 2131624060=android.view.AbsSavedState$1@6962f3e, 2131624061=android.view.AbsSavedState$1@6962f3e, 2131624062=android.view.AbsSavedState$1@6962f3e, 2131624063=android.view.AbsSavedState$1@6962f3e, 2131624064=android.view.AbsSavedState$1@6962f3e}, android:focusedViewId=2131624102}], android:support:fragments=android.support.v4.app.FragmentManagerState@e7e75b8}] 06-18 21:35:39.680 3004-3004/com.jx.androiddemos I/FragmentLearn: FragmentMainActivity onResume 06-18 21:35:39.680 3004-3004/com.jx.androiddemos I/FragmentLearn: NormalListFragment onResume 06-18 21:35:39.680 3004-3004/com.jx.androiddemos I/FragmentLearn: NormalListFragment onResume
从上面的Log可以发现,重新创建Activity时,NormalListFragment每个周期方法都走了两遍。这意味着同时创建了两个NormalListFragment实例,这个两个NormalListFragment一个是我代码里面主动创建的,另外一个则是上次Activity异常销毁时保存的,因为恢复的这个Fragment没有拿到引用,所以无法去做操作的(隐藏显示),这意味着我切换到其他tab时,这个Fragment会一直显示,这正是Fragment重叠问题的根源所在。
具体的原因也是销毁重建导致的
具体得解决方案参看:
- Fragment重叠问题引发的思考
- 彻底解决Fragment重叠的问题
参考文献
Fragment重叠问题引发的思考我踩到的关于Fragment 状态的保存和恢复的坑
浅析Fragment为什么需要Public的empty constructor
彻底解决Fragment重叠的问题
相关文章推荐
- Fragment重叠问题引发的思考
- 关于Viewpager中的Fragment 滑动时保存状态的问题
- Android:关于Viewpager中的Fragment 滑动时保存状态的问题
- 关于FragmentStatePagerAdapter状态保存引发的IllegalStateException
- 我踩到的关于Fragment 状态的保存和恢复的坑
- 关于开启不保留活动后引发RestoreInstanceState容易崩溃的问题小记
- Fragment重叠问题引发的思考
- Viewpager中的Fragment 滑动时保存状态的问题
- 由一个问题引发的思考——关于数据库的外键约束
- 关于Activity加载Fragment切换Show和Hide重叠问题
- Fragment的状态保存和恢复
- 关于Fragment 使用重叠问题。
- Android中管理多个Fragment的最佳实践,完美解决保存状态与重影问题
- 关于GridView翻页checkBox状态保存的问题
- Android关于Fragment长期置于后台返回重叠的问题
- [译]Android Activity 和 Fragment 状态保存与恢复的最佳实践
- Android中保存和恢复Fragment状态的最好方法
- Activity、Fragment保存和恢复状态的最佳实现
- 保存/恢复 Activity 和 Fragment 状态的最佳实
- Viewpager中的Fragment 滑动时保存状态的问题