Caused by: android.support.v4.app.Fragment$InstantiationException: Unable to instantiate fragment com.ckjr.context.VoteActivity$VoteFragment: make sure class name exists, is public, and has an empty constructor that is public
然而并没有太大软用,在手机上运行一段时间后,内存不够,应用被回收掉。再次进入依然需要重新创建,crash。
其实原因很简单。当切换到其他应用时,会调用FragmentActivity的onSaveInstanceState方法,再次进入,Activity重新创建,onCreate方法中会拿到savedInstanceState并re-instantiate。
我们先进入onSaveInstanceState():
/**
* 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);
}
}
Parcelable saveAllState() {
.......
FragmentManagerState fms = new FragmentManagerState();
fms.mActive = active;
fms.mAdded = added;
fms.mBackStack = backStack;
return fms;
}
非常简单,将Fragment的状态保存到outState,而outState就是onCreate方法中的savedInstanceState。fms.Added,是一个数组,每当调用addFragment或attachFragment就会往里添加;fms.Active,在addFragment()中调用makeActive()将Fragment往其中添加;fms.mBackStack,Fragment栈,用来存储BackStackRecord的一个列表,而BackStackRecord则是用于记录Fragment的一些操作。注意,所有Fragment状态保存在Bundle中,key为FRAGMENTS_TAG。接下来我们看看onCreate中的super.onCreate(savedInstanceState)中做了些什么。
@Override
protected void onCreate(Bundle savedInstanceState) {
mFragments.attachActivity(this, mContainer, null);
// Old versions of the platform didn't do this!
if (getLayoutInflater().getFactory() == null) {
getLayoutInflater().setFactory(this);
}
super.onCreate(savedInstanceState);
NonConfigurationInstances nc = (NonConfigurationInstances)
getLastNonConfigurationInstance();
if (nc != null) {
mAllLoaderManagers = nc.loaders;
}
if (savedInstanceState != null) {
Parcelable p = savedInstanceState.getParcelable(FRAGMENTS_TAG);
mFragments.restoreAllState(p, nc != null ? nc.fragments : null);
}
mFragments.dispatchCreate();
}
拿到savedInstanceState,如果不为空,从中取出key FRAGMENTS_TAG的值,然后执行恢复操作。我们进入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;
.........
for (int i=0; i<fms.mActive.length; i++) {
FragmentState fs = fms.mActive[i];
if (fs != null) {
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);
}
}
.........
}
不用多说读者也能猜到这里大概做些什么,将saveAllState()中保存的 fms. Added、fms.Active和fms.mBackStack一一恢复。我们聚焦到这行代码上
Fragment f = fs.instantiate(mActivity, mParent);
就是在这里,进行了对Fragment的re-instantiate。点进去
public Fragment instantiate(FragmentActivity activity, Fragment parent) {
if (mInstance != null) {
return mInstance;
}
if (mArguments != null) {
mArguments.setClassLoader(activity.getClassLoader());
}
mInstance = Fragment.instantiate(activity, mClassName, mArguments);
........
return mInstance;
}
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);
}
}
看到我们熟悉的报错信息,想必这里就是问题的终点了。没错,就是Fragment f = (Fragment)clazz.newInstance()引起的。
报错原因:反射创建失败!
接下来,我们模拟一个小实验来得出最终的解决方案!
创建这么一个类,FragmentA模拟单独的一个fragment class;FragmentB模拟内部类Fragment。
public class FragmentA {
public FragmentA(String str) {
}
public class FragmentB {
}
}
先来验证B,在Test中执行如下代码:
很明显,直接报异常。这样调用class.newInstance反射构造FragmentB肯定失败。因此,我们修改FragmentB为
public static class FragmentB {
}
再此运行,结果Success,通过。
接下来验证FragmentA,依然异常
我们给其添加一个空的构造方法。
public class FragmentA {
public FragmentA() {
}
public FragmentA(String str) {
}
}
再次运行,Success。
由此我们得出结论,class.newInstance反射失败有2个原因:
1、Fragment无空构造方法;
2、Fragment是内部类,没有加static。
----------------------------------------------------------分割线------------------------------------------------------
最后提供一个比较暴力,且不建议这么使用的一个解决方法。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(null);
}
第一次写Blog,有不对的地方欢迎评论指正。