升级AndroidX Lifecycle 2.2.0后,如何通过Fragment访问Activity的AndroidViewModel?
问题背景
你在重构Android应用时采用了Room、ViewModel、LiveData等架构组件,其中CanteensViewModel(继承自AndroidViewModel)需要被多个Fragment共享。原本在Activity和Fragment中通过ViewModelProvider获取实例的方式,在升级到androidx.lifecycle 2.2.0版本后失效,抛出了InstantiationException,提示你的ViewModel没有无参构造函数。
错误详情
报错的完整栈追踪如下:
2020-02-28 14:30:16.098 25279-25279/com.pasta.mensadd E/AndroidRuntime: FATAL EXCEPTION: main Process: com.pasta.mensadd, PID: 25279 java.lang.RuntimeException: Unable to start activity ComponentInfo{com.pasta.mensadd/com.pasta.mensadd.ui.MainActivity}: java.lang.RuntimeException: Cannot create an instance of class com.pasta.mensadd.ui.viewmodel.CanteensViewModel at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2795) at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2873) at android.app.ActivityThread.-wrap11(Unknown Source:0) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1602) at android.os.Handler.dispatchMessage(Handler.java:106) at android.os.Looper.loop(Looper.java:164) at android.app.ActivityThread.main(ActivityThread.java:6543) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807) Caused by: java.lang.RuntimeException: Cannot create an instance of class com.pasta.mensadd.ui.viewmodel.CanteensViewModel at androidx.lifecycle.ViewModelProvider$NewInstanceFactory.create(ViewModelProvider.java:221) at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.java:187) at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.java:150) at com.pasta.mensadd.ui.MainActivity.onCreate(MainActivity.java:70) at android.app.Activity.performCreate(Activity.java:7023) at android.app.Activity.performCreate(Activity.java:7014) at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1215) at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2748) at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2873) at android.app.ActivityThread.-wrap11(Unknown Source:0) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1602) at android.os.Handler.dispatchMessage(Handler.java:106) at android.os.Looper.loop(Looper.java:164) at android.app.ActivityThread.main(ActivityThread.java:6543) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807) Caused by: java.lang.InstantiationException: java.lang.Class<com.pasta.mensadd.ui.viewmodel.CanteensViewModel> has no zero argument constructor at java.lang.Class.newInstance(Native Method) at androidx.lifecycle.ViewModelProvider$NewInstanceFactory.create(ViewModelProvider.java:219) at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.java:187) at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.java:150) at com.pasta.mensadd.ui.MainActivity.onCreate(MainActivity.java:70) at android.app.Activity.performCreate(Activity.java:7023) at android.app.Activity.performCreate(Activity.java:7014) at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1215) at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2748) at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2873) at android.app.ActivityThread.-wrap11(Unknown Source:0) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1602) at android.os.Handler.dispatchMessage(Handler.java:106) at android.os.Looper.loop(Looper.java:164) at android.app.ActivityThread.main(ActivityThread.java:6543) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807)
核心原因
androidx.lifecycle 2.2.0版本后,ViewModelProvider的默认工厂改为了NewInstanceFactory,这个工厂只会尝试用无参构造函数创建ViewModel实例。而你的CanteensViewModel继承自AndroidViewModel,它需要接收Application参数完成初始化,直接使用默认工厂自然会抛出无参构造不存在的异常。
正确解决方案
你自己摸索出的方法是完全可行的,具体步骤如下:
在Activity中初始化ViewModel时指定
AndroidViewModelFactory
在MainActivity的onCreate方法中,使用带工厂参数的ViewModelProvider构造函数,确保用正确的工厂创建ViewModel并绑定到Activity生命周期:new ViewModelProvider(this, ViewModelProvider.AndroidViewModelFactory.getInstance(this.getApplication())) .get(CanteensViewModel.class);在Fragment中通过Activity作为所有者获取ViewModel
在Fragment中,以requireActivity()作为ViewModelProvider的所有者参数,就能获取到Activity中创建的同一个ViewModel实例:new ViewModelProvider(requireActivity()).get(CanteensViewModel.class);
注意事项
- 不要在每个Fragment中单独调用
ViewModelProvider.AndroidViewModelFactory.getInstance(...).create(...)创建ViewModel,这样每个Fragment都会得到独立的实例,完全失去了共享ViewModel的意义。 - 确保你的
CanteensViewModel构造函数正确接收Application参数并传递给父类:public class CanteensViewModel extends AndroidViewModel { public CanteensViewModel(Application application) { super(application); // 初始化仓库等业务逻辑 } }
内容的提问来源于stack exchange,提问作者julianctni

