Ручная очистка Android ViewModel?

Со ссылкой на класс android.arch.lifecycle.ViewModel.

ViewModel относится к жизненному циклу компонента пользовательского интерфейса, к которому он относится, поэтому в приложении Fragment -based это будет жизненный цикл фрагмента. Это хорошая вещь.


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

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

Это обсуждается в официальной документации ViewModel:

ViewModels также можно использовать в качестве уровня связи между различными фрагментами действия. Каждый фрагмент может получить ViewModel, используя тот же ключ через свою активность. Это позволяет осуществлять связь между фрагментами в разобщенном виде, так что им никогда не нужно напрямую общаться с другим фрагментом.

Другими словами, для обмена информацией между фрагментами, представляющими разные экраны, модель ViewModel должна быть ограничена жизненным циклом Activity (и в соответствии с документацией для Android это также может использоваться в других общих экземплярах).


Теперь в новом шаблоне навигации Jetpack рекомендуется использовать архитектуру "Один вид деятельности/Много фрагментов". Это означает, что действие длится все время использования приложения.

т.е. любые совместно используемые экземпляры ViewModel, ограниченные жизненным циклом Activity, никогда не будут очищены - память остается в постоянном использовании.

С целью сохранения памяти и использования всего необходимого в любой момент времени было бы неплохо иметь возможность очищать общие экземпляры ViewModel когда они больше не нужны.


Как можно вручную очистить ViewModel от его ViewModelStore или фрагмента держателя?

Ответ 1

Если вы проверите код здесь, вы обнаружите, что вы можете получить ViewModelStore из ViewModelStoreOwner и Fragment, FragmentActivity, например, реализует этот интерфейс.

Оттуда вы можете просто позвонить viewModelStore.clear(), который, как сказано в документации:

 /**
 *  Clears internal storage and notifies ViewModels that they are no longer used.
 */
public final void clear() {
    for (ViewModel vm : mMap.values()) {
        vm.clear();
    }
    mMap.clear();
}

N.B.: Это очистит все доступные ViewModel для конкретного LifeCycleOwner, это не позволит вам очистить одну конкретную ViewModel.

Ответ 2

Если вы не хотите, чтобы ViewModel был привязан к жизненному циклу Activity, вы можете охватить его до жизненного цикла родительского фрагмента. Поэтому, если вы хотите поделиться экземпляром ViewModel с несколькими фрагментами на экране, вы можете разбить фрагменты таким образом, чтобы все они имели общий родительский фрагмент. Таким образом, когда вы создаете экземпляр ViewModel вы можете просто сделать это:

CommonViewModel viewModel = ViewModelProviders.of(getParentFragment()).class(CommonViewModel.class);

Надеюсь, это поможет!

Ответ 3

Я думаю, что у меня есть лучшее решение.

Как утверждает @Nagy Robi, вы можете очистить ViewModel, viewModelStore.clear(). Проблема в том, что он очистит ВСЕ модель представления, ограниченную этим ViewModelStore. Другими словами, вы не сможете контролировать, какую ViewModel следует очистить.

Но в соответствии с @mikehc здесь. Вместо этого мы могли бы создать наш собственный ViewModelStore. Это позволит нам детально контролировать, в какой области видимости должен существовать ViewModel.

Примечание: я не видел, чтобы кто-то делал такой подход, но я надеюсь, что он правильный. Это будет действительно хороший способ управления областями в приложении с одним действием.

Пожалуйста, дайте несколько отзывов об этом подходе. Все будет оценено.

Обновить:

Начиная с Навигационного компонента v2.1.0-alpha02, ViewModel теперь может быть ограничен потоком. Недостатком является то, что вы должны реализовать Navigation Component для вашего проекта, а также у вас нет granualar контроля в области вашего ViewModel. Но это, кажется, лучше.

Ответ 4

Я просто пишу библиотеку для решения этой проблемы: scoped-vm, не стесняйтесь проверить, и я буду очень признателен за любые отзывы. Под капотом он использует упомянутый подход @Archie - он поддерживает отдельный ViewModelStore для каждой области. Но он идет на один шаг дальше и очищает сам ViewModelStore, как только последний фрагмент, который запросил viewmodel из этой области, разрушается.

Я должен сказать, что в настоящее время все управление моделью представления (и эта библиотека в частности) затронуто серьезной ошибкой с backstack, надеюсь, это будет исправлено.

Резюме:

  • Если вам важно, чтобы ViewModel.onCleared() не вызывался, лучший способ (на данный момент) - очистить его самостоятельно. Из-за этой ошибки у вас нет гарантии, что viewmodel fragment когда-либо будет очищен.
  • Если вы просто беспокоитесь о просочившейся ViewModel - не беспокойтесь, они будут собирать мусор, как и любые другие объекты, на которые нет ссылок. Не стесняйтесь использовать мою библиотеку для детальной оценки, если это соответствует вашим потребностям.

Ответ 5

Как было указано, невозможно очистить отдельную ViewModel от ViewModelStore с помощью API компонентов архитектуры. Одним из возможных решений этой проблемы является наличие хранилищ для каждой модели ViewModel, которые можно безопасно очистить при необходимости:

class MainActivity : AppCompatActivity() {

val individualModelStores = HashMap<KClass<out ViewModel>, ViewModelStore>()

inline fun <reified VIEWMODEL : ViewModel> getSharedViewModel(): VIEWMODEL {
    val factory = object : ViewModelProvider.Factory {
        override fun <T : ViewModel?> create(modelClass: Class<T>): T {
            //Put your existing ViewModel instantiation code here,
            //e.g., dependency injection or a factory you're using
            //For the simplicity of example let assume
            //that your ViewModel doesn't take any arguments
            return modelClass.newInstance()
        }
    }

    val viewModelStore = [email protected]<VIEWMODEL>()
    return ViewModelProvider(this.getIndividualViewModelStore<VIEWMODEL>(), factory).get(VIEWMODEL::class.java)
}

    val viewModelStore = [email protected]<VIEWMODEL>()
    return ViewModelProvider(this.getIndividualViewModelStore<VIEWMODEL>(), factory).get(VIEWMODEL::class.java)
}

inline fun <reified VIEWMODEL : ViewModel> getIndividualViewModelStore(): ViewModelStore {
    val viewModelKey = VIEWMODEL::class
    var viewModelStore = individualModelStores[viewModelKey]
    return if (viewModelStore != null) {
        viewModelStore
    } else {
        viewModelStore = ViewModelStore()
        individualModelStores[viewModelKey] = viewModelStore
        return viewModelStore
    }
}

inline fun <reified VIEWMODEL : ViewModel> clearIndividualViewModelStore() {
    val viewModelKey = VIEWMODEL::class
    individualModelStores[viewModelKey]?.clear()
    individualModelStores.remove(viewModelKey)
}

}

Используйте getSharedViewModel() для получения экземпляра ViewModel, который связан с жизненным циклом Activity:

val yourViewModel : YourViewModel = (requireActivity() as MainActivity).getSharedViewModel(/*There could be some arguments in case of a more complex ViewModelProvider.Factory implementation*/)

Позже, когда clearIndividualViewModelStore<>() время избавиться от общего ViewModel, используйте clearIndividualViewModelStore<>():

(requireActivity() as MainActivity).clearIndividualViewModelStore<YourViewModel>()

В некоторых случаях вы захотите очистить ViewModel как можно скорее, если он больше не нужен (например, если он содержит некоторые конфиденциальные данные пользователя, такие как имя пользователя или пароль). Вот способ записи состояния individualModelStores при каждом переключении фрагмента, чтобы помочь вам отслеживать общие модели представления:

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    if (BuildConfig.DEBUG) {
        navController.addOnDestinationChangedListener { _, _, _ ->
            if (individualModelStores.isNotEmpty()) {
                val tag = [email protected]
                Log.w(
                        tag,
                        "Don't forget to clear the shared ViewModelStores if they are not needed anymore."
                )
                Log.w(
                        tag,
                        "Currently there are ${individualModelStores.keys.size} ViewModelStores bound to ${[email protected]}:"
                )
                for ((index, viewModelClass) in individualModelStores.keys.withIndex()) {
                    Log.w(
                            tag,
                            "${index + 1}) $viewModelClass\n"
                    )
                }
            }
        }
    }
}

Ответ 6

Как правило, вы не очищаете ViewModel вручную, потому что он обрабатывается автоматически. Если вам кажется, что нужно вручную очистить ViewModel, вы, вероятно, слишком много делаете в этом ViewModel...

Нет ничего плохого в использовании нескольких режимов просмотра. Сначала можно было бы охватить активность, а другую можно было бы охватить фрагментом.

Попытайтесь использовать Viewmodel с областью действия Activity только для вещей, которые необходимо разделить. И поместите как можно больше вещей в область просмотра с фрагментами. Модифицированная область просмотра фрагмента будет очищена, когда фрагмент будет уничтожен. Уменьшение общего объема памяти.