Android 在屏幕旋转时重新创建 ViewModel

Android ViewModel recreated on screen rotation

我发现了一个架构组件 ViewModel 未保留的情况 - 简而言之,它如下所示:

  1. Activity 已启动并创建了 ViewModel 个实例
  2. Activity 放在后台
  3. 设备屏幕旋转
  4. Activity 回到前台
  5. 调用 ViewModel 的 onCleared 方法并创建新对象

在这种情况下,我的 ViewModel 实例被销毁是 Android 的正常行为吗?如果是这样,是否有任何推荐的保持其状态的解决方案?

我能想到的一种方法是在调用 onCleared 后保存它,但是,只要 activity 实际完成,它也会保留状态。另一种方法是使用 onRestoreInstanceState 但它会在每次屏幕旋转时触发(不仅是在应用程序处于后台时)。

有处理这种情况的灵丹妙药吗?

AFAIK,ViewModel 的唯一目的是在其所有者经历不同的生命周期事件时生存并保留数据(即 "save the state")。所以你不必 "save the state" 自己。

由此可知是"not normal behavior"。 onCleared() 仅在 activity 完成后调用(并且不会再次重新创建)。

您是使用 ViewModelProvider 创建 ViewModel,还是使用构造函数创建实例?

在你的 activity 中,你应该有这样的东西:

// in onCreate() - for example - of your activity
model = ViewModelProviders.of(this).get(MyViewModel.class);
// then use it anywhere in the activity like so
model.someAsyncMethod().observe(this, arg -> {
    // do sth...
});

这样做,你应该会得到预期的效果。

是的@tomwyr,这是来自 android 框架的错误。 Bug details

修复在 28.0.0-alpha3 和 AndroidX 1.0.0-alpha3 中可用

但是如果你不想自己更新到以上版本,那么你可以这样解决(我知道这是一个不好的解决方案,但我没有看到任何其他好的方法)

在您的 activity 中覆盖 onDestroy 方法 并在调用 super.onDestroy[=29= 之前将所有必填字段保存到局部变量 ].现在调用 super.onDestroy 然后再次初始化您的 ViewModel 并将所需的字段分配回您的 ViewModel

的新实例

关于isFinishing

以下代码使用 Kotlin

override fun onDestroy() {
     val oldViewModel = obtainViewModel()

     if (!isFinishing) { //isFinishing will be false in case of orientation change

          val requiredFieldValue = oldViewModel.getRequiredFieldValue()

          super.onDestroy

         val newViewModel = obtainViewModel()

         if (newViewModel != oldViewModel) { //View Model has been destroyed
              newViewModel.setRequiredFieldValue(requiredFieldValue)
          }
      } else {
         super.onDestroy
      }
 }

private fun obtainViewModel(): SampleViewModel {
      return ViewModelProviders.of(this).get(SampleViewModel::class.java)
}

将支撑 library/compileSDK/targetSDK 更改为 28。

我对 multi-window 也有类似的问题。切换到分屏时,会重新创建我的 viewModel。支持库 28 解决了我的问题。 (我的生命周期版本是1.1.1)

对于像我这样可能无法从以前的答案中得到帮助的其他人,问题可能是您没有使用工厂正确设置 ViewModelProvider。

在四处挖掘之后,我通过将以下方法添加到我的活动中解决了我的类似问题:

protected final <T extends ViewModel> T obtainViewModel(@NonNull AppCompatActivity activity, @NonNull Class<T> modelClass) {
    ViewModelProvider.AndroidViewModelFactory factory = ViewModelProvider.AndroidViewModelFactory.getInstance(activity.getApplication());
    return new ViewModelProvider(activity, factory).get(modelClass);
}

然后我在我的 Fragments 中这样做了:

protected final <T extends ViewModel> T obtainFragmentViewModel(@NonNull FragmentActivity fragment, @NonNull Class<T> modelClass) {
    ViewModelProvider.AndroidViewModelFactory factory = ViewModelProvider.AndroidViewModelFactory.getInstance(fragment.getApplication());
    return new ViewModelProvider(fragment, factory).get(modelClass);
}

我已经有一些用于菜单用途的抽象超级 类,所以我将方法隐藏在那里,这样我就不必在每个 activity 中重复它。这就是他们受到保护的原因。我相信如果你把它们放在你需要它们的每个 activity 或片段中,它们可能是私有的。

为了尽可能清楚,我会调用方法在 activity 的 onCreate() 中分配我的视图模型,它看起来像这样

private MyViewModel myViewModel;

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    myViewModel = obtainViewModel(this, MyViewModel.class);
}

或片段

private MyViewModel myViewModel;

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    if (getActivity() != null) {
        myViewModel = obtainFragmentViewModel(getActivity(), MyViewModel.class);
    }
}