美文网首页
Fragment必须提供无参构造函数

Fragment必须提供无参构造函数

作者: 卢大管家 | 来源:发表于2021-09-24 19:17 被阅读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;
    }
    

    相关文章

      网友评论

          本文标题:Fragment必须提供无参构造函数

          本文链接:https://www.haomeiwen.com/subject/pugzgltx.html