Fragment必须提供无参构造函数

最近在开发中遇到一个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的构造函数,具体是在Fragment f = clazz.getConstructor().newInstance(); 调用无参构造函数时发出了错误。

什么时候会调用 instantiate 方法呢?

在activity创建时,由FragmentActivity onCeate 透传序列化的方法state:FRAGMENTS_TAG = "android:support:fragments"

 会调用 FragmentController.restoreSaveState 方法,由注释可知,该方法是为了恢复所有被保存的fragment的状态

接下来调用 FragmentManager.restoreSaveState,此方法内activity onCreate传入的序列化对象强转为 FragmentManagerState

 这里特别说一下,为什么可以直接强转给FragmentManagerState对象,原因在下面的方法,保存fragment实时状态的:Parcelable p = mFragments.saveAllState();

 


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;
}

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
如果MainViewModel有一个有参构造方法,你需要实现一个ViewModelProvider.Factory的接口,以便正确地创建实例。具体步骤如下: 1. 创建一个实现ViewModelProvider.Factory接口的类,例如: ```kotlin class MainViewModelFactory(private val application: Application, private val param: String) : ViewModelProvider.Factory { override fun <T : ViewModel?> create(modelClass: Class<T>): T { if (modelClass.isAssignableFrom(MainViewModel::class.java)) { return MainViewModel(application, param) as T } throw IllegalArgumentException("Unknown ViewModel class") } } ``` 在上述代码中,我们创建了一个MainViewModelFactory类,它有一个构造函数,传入了Application和一个字符串类型的参数。在create方法中,我们检查传入的modelClass是否是MainViewModel类的子类,如果是,则返回通过有参构造函数创建的MainViewModel实例,否则抛出IllegalArgumentException异常。 2. 在Activity或Fragment中使用MainViewModelFactory创建MainViewModel实例,例如: ```kotlin val factory = MainViewModelFactory(application, "Hello World") val viewModel = ViewModelProviders.of(this, factory).get(MainViewModel::class.java) ``` 在上述代码中,我们先创建了一个MainViewModelFactory实例,传入了Application和一个字符串类型的参数。然后,我们使用ViewModelProviders提供的of方法并传入factory参数,获取MainViewModel实例。 这样,你就成功地使用有参构造函数创建了MainViewModel实例。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值