旋转后重新创建 ViewModel;如果直接用 dagger2 注入

ViewModel is recreated after rotation; if injected directly with dagger2

可能与 this

重复

我正在探索 android 使用 dagger2 进行注入 api。因此,在我的示例应用程序中,我直接在 activity 中注入了 ViewModel;看看下面的代码片段。

class SampleApp : Application(), HasActivityInjector {

    @Inject
    lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector<Activity>

    override fun activityInjector(): AndroidInjector<Activity> =
            dispatchingAndroidInjector

    override fun onCreate() {
        super.onCreate()

        DaggerApplicationComponent.builder()
                .application(this)
                .build()
                .inject(this)
    }
}

@Component(modules = [
    AndroidInjectionModule::class,
    ActivityBindingModule::class,
    AppModule::class
    /** Other modules **/
])
@Singleton
interface ApplicationComponent {

    @Component.Builder
    interface Builder {

        @BindsInstance
        fun application(application: Application): Builder

        fun build(): ApplicationComponent
    }

    fun inject(sampleApp: SampleApp)
}

@Module
public abstract class ActivityBindingModule {

    @ContributesAndroidInjector(modules = MainModule.class)
    public abstract MainActivity contributeMainActivityInjector();
}

class MainActivity : AppCompatActivity() {
    @Inject
    lateinit var mainViewModel: mainViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        AndroidInjection.inject(this)

        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_dashboard)
    }
}

@Module
public class MainModule {
    @Provides
    public static MainViewModelProviderFactory provideMainViewModelProviderFactory(/** some dependencies **/) {
        return new MainViewModelProviderFactory(/** some dependencies **/);
    }

    @Provides
    public static MainViewModel provideMainViewModel(MainActivity activity, MainViewModelProviderFactory factory) {
        return ViewModelProviders.of(activity, factory).get(MainViewModel.class);
    }
}

如您所见,我已将 MainViewModel 直接注入 activity。现在,如果我旋转 activity 注入的实例是不同的。

但是,如果我在 MainActivity 中注入 MainViewModelProviderFactory 并执行

ViewModelProviders.of(activity, factory).get(MainViewModel.class) 它 returns 与之前相同的实例。

我没有发现我的实现有什么问题。

如有指点,将不胜感激。

所以在查看了 ViewModelProviderViewModelProvidersFragmentActivity 的来源之后,是的 dagger2 documentation 我得到了答案..

如果我错了,请随时纠正我..

We must not inject ViewModel directly, we should inject factories instead.

由于这一行 AndroidInjection.inject(this).

,我遇到了这个问题

根据匕首作者

It is crucial to call AndroidInjection.inject() before super.onCreate() in an Activity

让我们在非常高的级别上看看这里出了什么问题..

Activity 将在使用 onRetainNonConfigurationInstance 旋转时保留它的 ViewModel 并将在 onCreate()

中恢复它

因为我们在调用 super.onCreate() 之前注入,我们不会得到保留的 MainViewModel 对象,而是新对象。

如果您需要详细信息,请继续阅读。


当 dagger 尝试注入 MainViewModel 时,它会调用 MainModuleprovideMainViewModel() 方法,该方法会调用以下表达式(请记住 super.onCreate() 尚未调用)

ViewModelProviders.of(activity, factory).get(MainViewModel.class)

ViewModelProviders.of 将 return 一个 ViewModelProvider,其中包含 activity 和 ViewModelProviderFactory[=54= 的 ViewModelStore 的引用]

public static ViewModelProvider of(@NonNull FragmentActivity activity,
        @Nullable Factory factory) {
    .
    .

    return new ViewModelProvider(ViewModelStores.of(activity), factory);
}

ViewModelStore.of(activity) 最终会调用 activity 的 getViewModelStore(),因为 activity 在这种情况下是 AppCompatActivity,它实现了 ViewModelStoreOwner

AppCompatActivity 如果它为 null 并持有对它的引用,则创建新的 ViewModelStoreViewModelStoreMap<String, ViewModel> 的包装器,带有附加方法 clear()

@NonNull
public ViewModelStore getViewModelStore() {
    if (this.getApplication() == null) {
        throw new IllegalStateException("Your activity is not yet attached to the Application instance. You can't request ViewModel before onCreate call.");
    } else {
        if (this.mViewModelStore == null) {
            this.mViewModelStore = new ViewModelStore();
        }

        return this.mViewModelStore;
    }
}

每当设备旋转时 activity 使用 onRetainNonConfigurationInstance 保留其非配置实例状态并在 onCreate 中恢复它。 (例如 mViewModelStore)

ViewModelProvider.get 将尝试从 activity 的 ViewModelStore

中获取 ViewModel
public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
    ViewModel viewModel = mViewModelStore.get(key);

    if (modelClass.isInstance(viewModel)) {
        //noinspection unchecked
        return (T) viewModel;
    } else {
        //noinspection StatementWithEmptyBody
        if (viewModel != null) {
            // TODO: log a warning.
        }
    }

    viewModel = mFactory.create(modelClass);
    mViewModelStore.put(key, viewModel);
    //noinspection unchecked
    return (T) viewModel;
}

在这个特定的例子中;因为我们还没有调用 super.onCreate() 方法,实现将要求 factory 创建它并更新相应的 ViewModelStore.

因此我们最终得到了两个不同的 MainViewModel 对象。