Fragment必须提供无参构造函数

2021-09-24  本文已影响0人  卢大管家

最近在开发中遇到一个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;
}
上一篇下一篇

猜你喜欢

热点阅读