(4.3.1.18)Fragment重叠问题引发的思考:不保留活动下,关于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重叠的问题

参考文献

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值