日前在项目代码里遇到偷懒使用重写Fragment带参构造方法来传参的做法,顿生好奇,继承android.support.v4.app.Fragment而又不写无参构造方法不是会出现lint错误编译不通过的咩?仔细追究,原来是这货被加了@SuppressLint("ValidFragment")从而屏蔽了错误。(个人非常不建议使用SuppressLint来屏蔽错误,尽管编译阶段通过了,运行时错误却越加恐怖!)
废话不多说,我们回归正题来看为什么Fragment必须要使用无参的构造方法。为什么?因为不这么干的话咱的app就会崩崩崩呗(咖喱给给~)。
首先,让我崩给你看~
将app定位至包含此非法(暂且叫它非法)的Fragment的页面,此时将app切至后台,等待app被系统回收,为了加快回收,你可以去玩半个小时比较耗内存的游戏等等,显而易见,这种等待十分愚蠢且不可预知,所以我们可以用借助开发工具立即触发app的被回收,这里我使用Android Studio来做。点击下图中箭头标注的小叉,你的app就被干掉了。(一定要是debug版本的才会在Android Monitor中出现哦)
接下来我们从历史任务中打开我们的app,哦哦
这下我们终于如愿以偿地看到的崩溃的异常堆栈信息:
Caused by: android.support.v4.app.Fragment$InstantiationException: Unable to instantiate fragment com.tuanche.app.fragment.ChooseCarStyleOnlineRetailersFragment: make sure class name exists, is public, and has an empty constructor that is public at android.support.v4.app.Fragment.instantiate(Fragment.java:413) at android.support.v4.app.FragmentState.instantiate(Fragment.java:97) at android.support.v4.app.FragmentManagerImpl.restoreAllState(FragmentManager.java:1801) at android.support.v4.app.FragmentActivity.onCreate(FragmentActivity.java:213) at com.zhqnghq.app.activity.BaseFragmentActivity.onCreate(BaseFragmentActivity.java:36) at com.zhanghq.app.activity.SecondActivity.onCreate(SecondActivity.java:97)
忽略最后两行,我们从倒数第三行开始看,打开FragmentActivity的onCreate方法,我们可以看到这样的代码:
if(savedInstanceState != null) {Parcelable p = savedInstanceState.getParcelable("android:support:fragments");this.mFragments.restoreAllState(p, nc != null?nc.fragments:null); }
因为是被kill后恢复,savedInstanceState不等于空,于是我们进入了if代码块,再往里走看this.mFragments.restoreAllState做了些什么我们关心的,再次挑出关键部分来看:
for(i = 0; i < fms.mActive.length; ++i) {FragmentState var8 = fms.mActive[i];if(var8 != null) {Fragment var9 = var8.instantiate(this.mActivity, this.mParent);if(DEBUG) {Log.v("FragmentManager", "restoreAllState: active #" + i + ": " + var9);}this.mActive.add(var9);var8.mInstance = null;} else {this.mActive.add((Object)null);if(this.mAvailIndices == null) {this.mAvailIndices = new ArrayList();}if(DEBUG) {Log.v("FragmentManager", "restoreAllState: avail #" + i);}this.mAvailIndices.add(Integer.valueOf(i));} }
Fragment var9 = var8.instantiate(this.mActivity, this.mParent);这句代码是我们关注的重点,去看看它做了些什么:
if(this.mInstance != null) {return this.mInstance; } else {if(this.mArguments != null) {this.mArguments.setClassLoader(activity.getClassLoader());}this.mInstance = Fragment.instantiate(activity, this.mClassName, this.mArguments);if(this.mSavedFragmentState != null) {this.mSavedFragmentState.setClassLoader(activity.getClassLoader());this.mInstance.mSavedFragmentState = this.mSavedFragmentState;} ...
我滴个神啊,终于看到几行可以稍微看懂的代码了,啦啦啊~Fragment.instantiate()是在从字面意思就可以看出是要生成一个新的Fragment,不信我们可以点进去看:
public static Fragment instantiate(Context context, String fname, Bundle args) {try {Class e = (Class)sClassMap.get(fname);if(e == null) {e = context.getClassLoader().loadClass(fname);sClassMap.put(fname, e);}Fragment f = (Fragment)e.newInstance();if(args != null) {args.setClassLoader(f.getClass().getClassLoader());f.mArguments = args;}return f;} catch (ClassNotFoundException var5) {throw new Fragment.InstantiationException("Unable to instantiate fragment " + fname + ": make sure class name exists, is public, and has an" + " empty constructor that is public", var5);} catch (java.lang.InstantiationException var6) {throw new Fragment.InstantiationException("Unable to instantiate fragment " + fname + ": make sure class name exists, is public, and has an" + " empty constructor that is public", var6);} catch (IllegalAccessException var7) {throw new Fragment.InstantiationException("Unable to instantiate fragment " + fname + ": make sure class name exists, is public, and has an" + " empty constructor that is public", var7);} }
忽略冗长的catch代码,可以看到,instantiate方法显示想法设法构造了一个Class对象,然后调用Class::newInstance()方法构造了一个全新的Fragment对象,最后还原了Fragment的Argument,至此Fragment的恢复大功告成。我们回过头看来Class::newInstance()的方法注释(良好的注释很重要啊~):
/** * Returns a new instance of the class represented by this { @code Class}, * created by invoking the default (that is, zero-argument) constructor. If * there is no such constructor, or if the creation fails (either because of * a lack of available memory or because an exception is thrown by the * constructor), an { @code InstantiationException} is thrown. If the default * constructor exists but is not accessible from the context where this * method is invoked, an { @code IllegalAccessException} is thrown. * * @throws IllegalAccessException * if the default constructor is not visible. * @throws InstantiationException * if the instance can not be created. */
看完之后豁然开朗啊有木有,原来这货会去调用当前Class的无参构造方法去构造实例,并且还在后面非常牛逼的警告了,如果没有无参构造方法或者构造失败或者这个默认构造方法访问不到,我就生气就抛异常!再回过头去看最开始贴出的异常信息,是不是和这里的异常message一模一样呢,哈哈。