Android ViewModel воссоздан при повороте экрана

Я нашел случай, когда компоненты архитектуры ViewModel не сохраняются - вкратце это выглядит так:

  1. Активность запущена и создан экземпляр ViewModel
  2. Активность ставится на задний план
  3. Экран устройства повернут
  4. Активность возвращается на передний план
  5. Вызывается метод ViewModel onCleared и создается новый объект

Является ли нормальным поведением Android то, что мой экземпляр ViewModel в этом случае уничтожается? Если да, есть ли рекомендуемое решение для сохранения его состояния?

Один из способов, который я могу придумать, - сохранить его после вызова onCleared, однако он также будет сохранять состояние всякий раз, когда действие фактически завершается. Другим способом может быть использование onRestoreInstanceState, но он срабатывает при каждом повороте экрана (не только если приложение находится в фоновом режиме).

Любая серебряная пуля для такого случая?


person tomwyr    schedule 24.04.2018    source источник
comment
Похоже, это проблема платформы Android: issuetracker.google.com/issues/73644080. Об аналогичной проблеме сообщалось в этом вопросе SO: of-activit" title="android viewmodel воссоздан, когда активность его хоста не была в верхней части activit">stackoverflow.com/questions/48200085/.   -  person tomwyr    schedule 24.04.2018


Ответы (4)


Да, @tomwyr, это была ошибка фреймворка Android. Сведения об ошибке

Исправление доступно в версиях 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)
}
person Surendar Dharavath    schedule 23.07.2018
comment
Я нашел другой способ решить эту проблему (внес некоторые изменения в код, чтобы ошибка не влияла на мое приложение), но ваше решение кажется вполне приемлемым в качестве обходного пути. - person tomwyr; 23.07.2018

Насколько мне известно, единственная цель 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...
});

Делая это, вы должны получить ожидаемый эффект.

person Ace    schedule 24.04.2018
comment
Да, я создаю его точно так же, как в вашем примере, то есть ссылаясь на него через ViewModelProviders. Кроме того, я добавил комментарий к моему вопросу, если вам интересно. - person tomwyr; 24.04.2018
comment
Ясно.. Извините, это было AFAIK. Если это известная проблема, то я надеюсь, что они исправят ее как можно скорее. Если нет, то, надеюсь, кто-то более опытный может просветить нас. - person Ace; 24.04.2018
comment
Я думаю, что цель другая. Он сохраняет данные даже в тех случаях, когда жизненный цикл активности заканчивается, как в типичном случае ротации. - person Blcknx; 27.04.2018
comment
@Blcknx, следовательно, (и не воссоздается) в моем ответе - person Ace; 27.04.2018
comment
Та же проблема и у меня, до уровня API 26 он работает нормально, но я столкнулся с этой проблемой, когда обновил свой уровень API до 27. - person Surendar Dharavath; 17.07.2018

Для других, которым могут не помочь предыдущие ответы, такие как я, проблема может заключаться в том, что вы неправильно настроили свой 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);
}

И затем я сделал это в своих фрагментах:

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

У меня уже было несколько абстрактных суперклассов для целей меню, поэтому я спрятал методы там, чтобы мне не приходилось повторять их в каждом действии. Вот почему они защищены. Я считаю, что они могут быть частными, если вы поместите их в каждое действие или фрагмент, в котором они вам нужны.

Чтобы быть как можно более ясным, я бы затем вызвал методы для назначения моей модели представления в 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);
    }
}
person Vanheden    schedule 14.06.2020

Измените библиотеку поддержки/compileSDK/targetSDK на 28.

У меня была аналогичная проблема с многооконностью. При переключении на разделенный экран моя модель представления воссоздается. Библиотека поддержки 28 исправила мою проблему. (Моя версия жизненного цикла 1.1.1)

person Feng Shi    schedule 13.12.2018