一、前言
最近,在做一个项目。当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重叠的问题