Очистка Android ViewModel вручную? - PullRequest
0 голосов
/ 06 декабря 2018

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

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


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

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

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

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

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


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

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

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


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

Ответы [ 6 ]

0 голосов
/ 09 июля 2019

Как уже указывалось, невозможно очистить отдельную 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's assume
            //that your ViewModel doesn't take any arguments
            return modelClass.newInstance()
        }
    }

    val viewModelStore = this@MainActivity.getIndividualViewModelStore<VIEWMODEL>()
    return ViewModelProvider(this.getIndividualViewModelStore<VIEWMODEL>(), factory).get(VIEWMODEL::class.java)
}

    val viewModelStore = this@MainActivity.getIndividualViewModelStore<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, который связанк жизненному циклу Деятельности:

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

Позже, когда пришло время избавиться от общей 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 = this@MainActivity.javaClass.simpleName
                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 ${this@MainActivity.javaClass.simpleName}:"
                )
                for ((index, viewModelClass) in individualModelStores.keys.withIndex()) {
                    Log.w(
                            tag,
                            "${index + 1}) $viewModelClass\n"
                    )
                }
            }
        }
    }
}
0 голосов
/ 09 апреля 2019

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

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

Резюме:

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

Мне кажется, у меня есть лучшее решение.

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

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

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

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

Обновление:

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

0 голосов
/ 11 декабря 2018

Если вы проверите код здесь , вы обнаружите, что вы можете получить 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();
}

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

0 голосов
/ 06 декабря 2018

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

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

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

0 голосов
/ 06 декабря 2018

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

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

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

...