最近在开发中遇到一个crash,仔细研究了一下,记录一下:
先说结论:使用Fragment时,要声明一个无参的构造函数,否则在状态恢复时会出现crash
因为当Fragment因为某种原因重新创建时,会调用到onCreate方法传入之前保存的状态,在instantiate方法中通过反射无参构造函数创建一个Fragment,并且为Arguments初始化为原来保存的值,而此时如果没有无参构造函数就会抛出异常,造成程序崩溃。
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.demo/com.demo.activity.DemoActivity}: androidx.fragment.app.Fragment$InstantiationException: Unable to instantiate fragment com.demo.fragment.DemoFragment: could not find Fragment constructor
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2944)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3079)
at android.app.ActivityThread.handleRelaunchActivityInner(ActivityThread.java:4815)
at android.app.ActivityThread.handleRelaunchActivity(ActivityThread.java:4724)
at android.app.servertransaction.ActivityRelaunchItem.execute(ActivityRelaunchItem.java:69)
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1836)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:193)
at android.app.ActivityThread.main(ActivityThread.java:6702)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:911)
Caused by: androidx.fragment.app.Fragment$InstantiationException: Unable to instantiate fragment com.demo.fragment.DemoFragment: could not find Fragment constructor
at androidx.fragment.app.Fragment.instantiate(Fragment.java:563)
at androidx.fragment.app.FragmentContainer.instantiate(FragmentContainer.java:57)
at androidx.fragment.app.FragmentManager$3.instantiate(FragmentManager.java:390)
at androidx.fragment.app.FragmentStateManager.<init>(FragmentStateManager.java:74)
at androidx.fragment.app.FragmentManager.restoreSaveState(FragmentManager.java:2454)
at androidx.fragment.app.FragmentController.restoreSaveState(FragmentController.java:196)
at androidx.fragment.app.FragmentActivity.onCreate(FragmentActivity.java:287)
at androidx.appcompat.app.AppCompatActivity.onCreate(AppCompatActivity.java:115)
at com.demo.activity.BaseActivity.onCreate(BaseActivity.java:110)
at com.demo.activity.DemoActivity.onCreate(DemoActivity.java:81)
at android.app.Activity.performCreate(Activity.java:7136)
at android.app.Activity.performCreate(Activity.java:7127)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1271)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2924)
... 13 more
Caused by: java.lang.NoSuchMethodException: <init> []
at java.lang.Class.getConstructor0(Class.java:2327)
at java.lang.Class.getConstructor(Class.java:1725)
at androidx.fragment.app.Fragment.instantiate(Fragment.java:548)
... 26 more
根据堆栈提示,找到了出问题的地方Fragmet类:
fragment.png可以看到,问题原因是没有找到fragment的构造函数,具体是在Fragment f = clazz.getConstructor().newInstance(); 调用无参构造函数时发出了错误。
什么时候会调用 instantiate 方法呢
在activity创建时,由FragmentActivity onCeate 透传序列化的方法state:FRAGMENTS_TAG = "android:support:fragments" onCreate.png
会调用 FragmentController.restoreSaveState 方法,由注释可知,该方法是为了恢复所有被保存的fragment的状态
接下来调用 FragmentManager.restoreSaveState,此方法内activity onCreate传入的序列化对象强转为 FragmentManagerState FragmentManager.restoreSaveState
这里特别说一下,为什么可以直接强转给FragmentManagerState对象,原因在下面的方法,保存fragment实时状态的:Parcelable p = mFragments.saveAllState(); FragmentActivity.png
Parcelable saveAllState() {
// Make sure all pending operations have now been executed to get
// our state update-to-date.
forcePostponedTransactions();
endAnimatingAwayFragments();
execPendingActions();
// 省略部分代码......
// First collect all active fragments.
int size = mActive.size();
// 省略部分代码......
// Build list of currently added fragments.
size = mAdded.size();
// 省略部分代码......
// Now save back stack.
// 省略部分代码......
FragmentManagerState fms = new FragmentManagerState();
fms.mActive = active;
fms.mAdded = added;
fms.mBackStack = backStack;
if (mPrimaryNav != null) {
fms.mPrimaryNavActiveWho = mPrimaryNav.mWho;
}
fms.mNextFragmentIndex = mNextFragmentIndex;
return fms;
}
可以看到,saveAllState() 返回的对象其实就是FragmentManagerState,这里返回Parcelable而不是FragmentManagerState对象主要是方便数据的持久化处理。
因此在恢复状态时 FragmentManager.restoreSaveState方法可以直接将Parcelable对象强转为FragmentManagerState对象。
FragmentManager中构建了默认的FragmentFractory,Factory中重写了instantiate方法,调用了FragmentHostCallback的instantiate方法。该方法最终调用了Fragment类的中静态方法。
即文章开始提到的 Fragment.instantiate 方法,在instantiate中尝试用无参构造函数创建fragment实例时由于找不到无参的构造函数而报错
此外需要注意,创建fragment涉及到相关参数保存的操作,官方建调用fragment.setArguments(args)方法,系统会再恢复状态时同步恢复这些参数,从而避免业务数据的丢失。
/**
* Create a new instance of a Fragment with the given class name. This is
* the same as calling its empty constructor, setting the {@link ClassLoader} on the
* supplied arguments, then calling {@link #setArguments(Bundle)}.
*
* ....省略部分
*
*/
/**
* Supply the construction arguments for this fragment.
* The arguments supplied here will be retained across fragment destroy and
* creation.
* <p>This method cannot be called if the fragment is added to a FragmentManager and
* if {@link #isStateSaved()} would return true.</p>
*/
public void setArguments(@Nullable Bundle args) {
if (mFragmentManager != null && isStateSaved()) {
throw new IllegalStateException("Fragment already added and state has been saved");
}
mArguments = args;
}
网友评论