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

时间:2018-04-24 10:28:28

标签: android android-architecture-components android-viewmodel

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

  1. 活动已启动且已创建ViewModel实例
  2. 活动投放到后台
  3. 旋转设备屏幕
  4. 活动被放回到前台
  5. 调用ViewModel的onCleared方法并创建新对象
  6. 在这种情况下,我的ViewModel实例是否正在被销毁,这是Android的正常行为吗?如果是这样,是否有任何建议的解决方案来保持其状态?我可以想到的一种方法是在调用onCleared时保存它,但是,只要活动实际完成,它也会保持状态。另一种方法可能是使用onRestoreInstanceState,但它会在每个屏幕旋转时触发(不仅仅是应用程序在后台)。
    处理此类情况的银弹?

4 个答案:

答案 0 :(得分:6)

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

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

但是,如果您自己现在不想更新到上述版本,则可以这样解决(我知道这是一个糟糕的解决方案,但我没有看到其他好的方法

在您的活动中,覆盖 onDestroy方法,并在调用super.onDestroy 之前将所有必填字段保存到本地变量中。现在调用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)
}

答案 1 :(得分:0)

AFAIK,ViewModel的唯一目的是在其所有者经历不同的生命周期事件时生存并保留数据(即“保存状态”)。所以你不必自己“拯救国家”。

我们可以从中看出这是“不正常的行为”。 onCleared()仅在活动结束后调用(并且不会再次重新创建)。

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

在您的活动中,您应该拥有以下内容:

// 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...
});

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

答案 2 :(得分:0)

将支持库/ compileSDK / targetSDK更改为28。

我对多窗口也有类似的问题。切换到拆分屏幕时,将重新创建我的viewModel。支持库28解决了我的问题。 (我的生命周期版本是1.1.1)

答案 3 :(得分:0)

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

深入研究后,我通过向“活动”中添加以下方法解决了类似的问题:

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

然后我在片段中做到了:

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

我已经有了一些用于菜单目的的抽象超类,因此我将方法隐藏在那里,因此不必在每次活动中都重复它。这就是为什么他们受到保护。我相信,如果您将它们放在需要它们的每个活动或片段中,它们可能是私有的。

为了尽可能清晰,我将在活动中调用方法在onCreate()中分配视图模型,看起来像这样

private MyViewModel myViewModel;

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

或分段

private MyViewModel myViewModel;

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

到目前为止,它似乎工作正常。如果有人有更好的方法,请告诉我们!