ViewModel в Activity под капотом: как она выживает при пересоздании
Что на самом деле происходит с ViewModel, когда Activity уничтожается и создаётся заново? Разбираем всю цепочку: ViewModelStore, NonConfigurationInstances, ActivityThread и ActivityClientRecord. Подробно показываем, где и как хранится состояние, и как Android магически восстанавливает всё при конфигурационных изменениях.
Введение
В статье не рассматривается работа с ViewModel, предполагается, что эта тема уже знакома. Основное внимание уделяется тому, как ViewModel переживает изменение конфигурации. Но для начала — небольшое введение в ViewModel.
ViewModel - компонент архитектурного паттерна MVVM, который был предоставлен Google как примитив позволяющий пережить изменение конфигураций. Изменение конфигураций в свою очередь - это состояние, заставляющая activity/fragment пересоздаваться, это именно то состояние которое может пережить ViewModel. Популярные конфигурации которые приводят к пересозданию Activity:
- Изменение ориентаций экрана(screenOrientation): portrait/landscape
- Изменение направления экрана(layoutDirection): rtl/ltr
- Изменение языка приложения(locale)
- Изменение размера шрифтов/соотношение экрана
Есть конечно способ сообщать системе о том что пересоздавать Activity при изменении конфигураций не нужно. Флаг android:configChanges используется в AndroidManifest.xml в теге <activity/>, чтобы указать, какие изменения конфигурации система не должна пересоздавать Activity, а передавать управление методу Activity.onConfigurationChanged().
<activity
android:name="MainActivity"
android:configChanges="layoutDirection|touchscreen|density|orientation|keyboard|locale|keyboardHidden|navigation|screenLayout|mcc|mnc|fontScale|uiMode|screenSize|smallestScreenSize"
/>Однако сейчас речь не об этом. Наша цель — разобраться, каким образом ViewModel умудряется переживать все изменения конфигурации и сохранять своё состояние.
Объявление ViewModel
С появлением делегатов в Kotlin разработчики получили возможность значительно упростить создание и использование компонентов. Теперь объявление ViewModel с использованием делегатов выглядит следующим образом:
class MainActivity : ComponentActivity() {
private val viewModel by viewModel<MyViewModel>()
}Без делегатов создание объекта ViewModel, используя явный вызов ViewModelProvider выглядит следующий образом:
class MainActivity : ComponentActivity() {
private lateinit var viewModel: MyViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// В старых версиях ViewModelProvider был частью lifecycle-viewmodel
viewModel = ViewModelProvider(this).get(MyViewModel::class.java)
// После адаптации ViewModel под KMP и переноса ViewModelProvider в lifecycle-viewmodel-android
// можно и рекомендуется через перегруженный фабричный метод create:
viewModel = ViewModelProvider.create(owner = this).get(MyViewModel::class)
// Альтернативный способ создания ViewModel (эквивалентен предыдущему)
viewModel = ViewModelProvider.create(store = this.viewModelStore).get(MyViewModel::class)
}
}Метод ViewModelProvider.create имеет параметры со значениями по умолчанию, поэтому на уровне байткода компилятор создаст несколько перегруженных версий метода (overloads). Это позволяет вызывать его с разным количеством аргументов: только с store, с store и factory, либо со всеми параметрами, включая extras.
Jetpack ViewModel теперь поддерживает Kotlin Multiplatform (KMP), что позволяет использовать его не только на Android, но и на iOS, Desktop и Web. Это стало возможным благодаря разделению на два модуля:
lifecycle-viewmodel(expected): KMP-модуль без привязки к Android. lifecycle-viewmodel-android(actual): модуль для работы с ViewModelStoreOwner и ViewModelProvider на Android.
Начиная с версии 2.8.0-alpha03, артефакты lifecycle-* теперь официально поддерживают Kotlin Multiplatform! Это означает, что классы, такие как ViewModel, ViewModelStore, ViewModelStoreOwner и ViewModelProvider, теперь можно использовать в общем коде.
Далее в статье мы рассмотрим именно версию viewmodel:2.8.0+, если в версий на которой вы находитесь сейчас немного отличаются исходники, то не переживайте, c добавлением поддержки kmp немного поменяли внутренюю структуру , но реализация и внутренняя логика такая же что и до поддержки kmp
ViewModelStoreOwner ?
Как мы видим выше, мы вручную не создаём объект ViewModel, а только передаём тип его класса в ViewModelProvider, который самостоятельно занимается созданием экземпляра.
Обратите внимание, что мы также передаём в метод ViewModelProvider.create параметр owner = this. Если заглянуть в исходники метода create, можно заметить, что требуется тип owner: ViewModelStoreOwner:
public actual companion object {
@JvmStatic
@Suppress("MissingJvmstatic")
public actual fun create(
owner: ViewModelStoreOwner, // <- нас интересует этот тип
factory: Factory,
extras: CreationExtras,
): ViewModelProvider = ViewModelProvider(owner.viewModelStore, factory, extras)
}Если интересно, почему метод create() можно вызывать без передачи значений для параметров factory и extras (хоть они и обязательны):
ViewModelProvider.create(owner = this)Это связано с тем, что код использует KMP (Kotlin Multiplatform). В expect-объявлении для create() уже заданы значения по умолчанию для factory и extras, поэтому передавать их явно необязательно.
public expect class ViewModelProvider {
....
public companion object {
public fun create(
owner: ViewModelStoreOwner,
factory: Factory = ViewModelProviders.getDefaultFactory(owner),
extras: CreationExtras = ViewModelProviders.getDefaultCreationExtras(owner),
): ViewModelProvider
}
....
}Подробнее можно посмотреть в исходниках: ViewModelProvider.kt
Углубляемся в ViewModelStore / Owner
Получается что при вызове метода ViewModelProvider.create() для параметра owner мы передаем this (само активити), и как можно догадаться, это означает, что activity реализует(наследуется) от интерфейса ViewModelStoreOwner. Давайте взглянем на исходники этого интерфейса: ViewModelStoreOwner:
public interface [[[ViewModelStoreOwner |https://github.com/androidx/androidx/blob/androidx-main/lifecycle/lifecycle-viewmodel/src/commonMain/kotlin/androidx/lifecycle/ViewModelProvider.kt]]] {
/**
* The owned [ViewModelStore]
*/
public val viewModelStore: ViewModelStore
}ViewModelStoreOwner — это интерфейс с единственным полем, которое представляет собой ViewModelStore (хранитель view models). От ViewModelStoreOwner наследуются такие компоненты как: ComponentActivity, Fragment, * *NavBackStackEntry**.
Официальная документация гласит:
A scope that owns ViewModelStore. A responsibility of an implementation of this interface is to retain owned ViewModelStore during the configuration changes and call ViewModelStore. clear, when this scope is going to be destroyed.
Обязанности ViewModelStoreOwner:
- Хранение ViewModelStore во время изменения конфигураций.
- Очистка ViewModelStore при уничтожении ComponentActivity/Fragment — в состоянии
onDestroy(). Удаляются все ViewModel-и которые ViewModelStore хранить в себе.
Мы определили, что ViewModelStoreOwner — это всего лишь интерфейс, не содержащий собственной логики. Его реализуют такие компоненты, как:
ComponentActivity(и его наследники:FragmentActivity, AppCompatActivity)Fragment(и его производные:DialogFragment,BottomSheetDialogFragment,AppCompatDialogFragment).NavBackStackEntry- Класс из библиотеки Jetpack Navigation (он же androidx navigation)
Далее нас уже интересует сам ViewModelStore:
ViewModelStore — это класс, который внутри себя делегирует управление коллекцией Map (LinkedHashMap) для хранения ViewModel по ключу:
private val map = mutableMapOf<String, ViewModel>()По умолчанию в качестве ключа используется полное имя класса (включая его пакет). Этот ключ генерируется следующим образом в исходниках утилитного класса ViewModelProviders (не путать с ViewModelProvider):
private const val VIEW_MODEL_PROVIDER_DEFAULT_KEY: String = "androidx.lifecycle.ViewModelProvider.DefaultKey"
internal fun <T : ViewModel> getDefaultKey(modelClass: KClass<T>): String {
return "$VIEW_MODEL_PROVIDER_DEFAULT_KEY:$modelClass.canonicalName"
}Таким образом, для MyViewModel ключ будет выглядеть так: androidx.lifecycle.ViewModelProvider.DefaultKey:com.example.MyViewModel.
Поскольку ViewModelStore основан на Map, он делегирует все основные операции, такие как put, get, keys и clear, внутреннему Map (LinkedHashMap).
Соответственно, так как внутренняя реализация ViewModelStore полагается на Map, он также делегирует свои методы put, get, key, clear внутреннему Map(LinkedHashMap). Особого внимания заслуживает метод clear():
public open class ViewModelStore {
private val map = mutableMapOf<String, ViewModel>()
...
/**
* Clears internal storage and notifies `ViewModel`s that they are no longer used.
*/
public fun clear() {
for (vm in map.values) {
vm.clear()
}
map.clear()
}
}Давайте разберёмся, что здесь происходит. Когда наш ViewModelStoreOwner (в лице ComponentActivity или Fragment) окончательно умирает (смерть не связана с пересозданием из-за изменения конфигураций), он вызывает метод clear() у ViewModelStore.
В методе clear() цикл for проходит по всем значениям (view models), которые хранятся внутри внутреннего HashMap, и вызывает у каждой ViewModel внутренний метод clear(). Этот метод, в свою очередь, инициирует вызов метода onCleared() у нашей ViewModel.
onCleared() — это метод, который мы можем переопределить в своей ViewModel, и он вызывается только в момент окончательного уничтожения ViewModel, когда активити или фрагмент также окончательно завершают свою работу.
public actual abstract class ViewModel {
...
protected actual open fun onCleared() {} // <- метод onCleared, который можно переопределить
@MainThread
internal actual fun clear() {
impl?.clear()
onCleared() // <- вызов метода onCleared
}
}Таким образом, метод clear() гарантирует, что все ресурсы и фоновые задачи, связанные с ViewModel, будут корректно освобождены перед уничтожением. Соответственно, сам метод viewModelStore.clear() вызывается ViewModelStoreOwner (в лице ComponentActivity или Fragment).
Давайте в качестве примера выберем ComponentActivity, чтобы понять, как работает очистка.
Ниже приведён фрагмент кода из ComponentActivity, который отслеживает её уничтожение и вызывает viewModelStore.clear():
@Suppress("LeakingThis")
lifecycle.addObserver(
LifecycleEventObserver { _, event ->
if (event == Lifecycle.Event.ON_DESTROY) { // <- состояние ON_DESTROY является триггером
// Clear out the available context
contextAwareHelper.clearAvailableContext()
// And clear the ViewModelStore
if (!isChangingConfigurations) { // <- проверка на то можно ли очищать ViewModelStore
viewModelStore.clear() // <- очистка ViewModelStore
}
reportFullyDrawnExecutor.activityDestroyed()
}
}
)В данном коде происходит добавление наблюдателя на жизненный цикл активности с использованием LifecycleEventObserver. Когда активность достигает состояния ON_DESTROY, запускается проверка, не происходит ли изменение конфигурации ( isChangingConfigurations). Если активность действительно умирает окончательно (и не пересоздаётся), вызывается метод viewModelStore.clear(), который очищает все связанные с активностью ViewModel.
Мы видим, что проверка состояния ON_DESTROY в сочетании с условием if (!isChangingConfigurations) позволяет убедиться в том, что причиной уничтожения не является изменение конфигурации. Только в этом случае очищается ViewModelStore и удаляются все экземпляры ViewModel, связанные с данной активностью.
В этой статье мы подробно разбираем внутренние методы класса
ComponentActivity, начиная с версии **androidx.activity:activity:1.9.0-alpha01 **, когда он был переписан на Kotlin.Если у вас установлена более старая версия библиотеки, и вы видите реализацию на Java — не переживайте. Логика и основные методы остались прежними, поэтому все представленные концепции и объяснения будут актуальны.
ComponentActivity.onDestroy() →getViewModelStore().clear() →MyViewModel.onCleared()Теперь мы разобрались с процессом очистки и уничтожения ViewModel. Перейдём к следующему этапу — рассмотрим подробнее, как происходит создание объекта ViewModel, когда мы передаём её в ViewModelProvider:
ViewModelProvider.create(owner = this).get(MyViewModel::class)Да, можно уточнить, что ViewModelProvider.create — это функция с значениями по умолчанию. Например:
ViewModelProvider.create(owner = this).get(MyViewModel::class)Ранее мы разобрали один из перегруженных методов ViewModelProvider.create (функция с аргументами по умолчанию). Это фабричный метод, который принимает минимум ViewModelStore или ViewModelStoreOwner, создаёт объектViewModelProvider и на этом завершает свою работу.
Теперь нас интересует следующий ключевой метод — get, который принимает класс ViewModel в качестве параметра. ViewModelProvider делегирует свою работу классу ViewModelProviderImpl:
public actual open class ViewModelProvider private constructor(
private val impl: ViewModelProviderImpl,
) {
...
@MainThread
public actual operator fun <T : ViewModel> get(modelClass: KClass<T>): T =
impl.getViewModel(modelClass) // <- вызов метода getViewModel, принадлежащий ViewModelProviderImpl
}Разработчики Google вынесли общую логику создания ViewModel в отдельный объект ViewModelProviderImpl. Это позволило избежать дублирования кода на разных платформах в KMP. Причина в том, что expect-классы в Kotlin Multiplatform не могут содержать реализации методов по умолчанию. Если бы они могли, реализация находилась бы прямо внутри expect-версии ViewModelProvider, без необходимости выносить её в отдельный объект. Однако, из-за этого ограничения, была создана ViewModelProviderImpl, которая содержит общую логику создания ViewModel для всех платформ.
Оригинальный комментарий:
Kotlin Multiplatform does not support expect class with default implementation yet, so we extracted the common logic used by all platforms to this internal class.
Исходники метода getViewModel() в ViewModelProviderImpl.kt:
internal fun <T : ViewModel> getViewModel(
modelClass: KClass<T>,
key: String = ViewModelProviders.getDefaultKey(modelClass),
): T {
val viewModel = store[key] // 1. Достается viewmodel из ViewModelStore, если он существует
if (modelClass.isInstance(viewModel)) {
if (factory is ViewModelProvider.OnRequeryFactory) {
factory.onRequery(viewModel!!)
}
return viewModel as T
}
val extras = MutableCreationExtras(extras)
extras[ViewModelProviders.ViewModelKey] = key
// 2. Создается viewmodel и кладется в ViewModelStore
return createViewModel(factory, modelClass, extras).also { vm -> store.put(key, vm) }
}При вызове ViewModelProvider.create() под капотом вызывается метод getViewModel(), который выполняет следующие шаги:
- Проверяет наличие объекта
ViewModelвViewModelStoreпо заданному ключу. Если объект уже существует, он возвращается. - Если объект не найден, создаётся новый экземпляр
ViewModel, который затем кладётся вViewModelStoreдля последующего использования.
Где ViewModelStore сохраняется?
Теперь, когда мы знаем полный процесс создания ViewModel и её размещения в ViewModelStore, возникает логичный вопрос: если все ViewModel-и хранятся внутри ViewModelStore, а сам ViewModelStore находится в ComponentActivity или Fragment, которые реализуют интерфейс ViewModelStoreOwner, то где и как хранится сам объект ViewModelStore?
Для того чтобы найти ответ на вопрос о хранении ViewModelStore, давайте посмотрим, как ComponentActivity реализует интерфейс ViewModelStoreOwner:
override val viewModelStore: ViewModelStore
get() {
checkNotNull(application) {
("Your activity is not yet attached to the " +
"Application instance. You can't request ViewModel before onCreate call.")
}
ensureViewModelStore()
return _viewModelStore!!
}Мы видим, что вызывается метод ensureViewModelStore, а затем возвращается поле _viewModelStore.
// Lazily recreated from NonConfigurationInstances by val viewModelStore
private var _viewModelStore: ViewModelStore? = nullПоле _viewModelStore не имеет значения по умолчанию, поэтому перед возвратом оно инициализируется внутри метода ensureViewModelStore:
private fun ensureViewModelStore() {
if (_viewModelStore == null) {
// Извлекается ComponentActivity#NonConfigurationInstances из метода Activity#getLastNonConfigurationInstance()
val nc = lastNonConfigurationInstance as NonConfigurationInstances?
if (nc != null) {
// Восстанавливается ViewModelStore из NonConfigurationInstances
_viewModelStore = nc.viewModelStore
}
if (_viewModelStore == null) {
// Создается ViewModelStore если нет сохраненного внутри объекта NonConfigurationInstances
_viewModelStore = ViewModelStore()
}
}
}Тут-то и начинается самое интересное. Если поле _viewModelStore равно null, сначала выполняется попытка получить его из метода getLastNonConfigurationInstance(), который возвращает объект класса NonConfigurationInstances.
Если ViewModelStore отсутствует и там, это может означать одно из двух:
- Активность создаётся впервые и у неё ещё нет сохранённого
ViewModelStore. - Система уничтожила процесс приложения (например, из-за нехватки памяти), а затем пользователь снова запустил приложение, из-за чего
ViewModelStoreне сохранился.
В любом из этих случаев создаётся новый экземпляр ViewModelStore.
Самая неочевидная часть — это вызов метода getLastNonConfigurationInstance(). Этот метод принадлежит классуActivity, а класс NonConfigurationInstances, у которого даже само название выглядит интригующе, объявлен вComponentActivity:
internal class NonConfigurationInstances {
var custom: Any? = null
var viewModelStore: ViewModelStore? = null
}Таким образом, объект NonConfigurationInstances используется для хранения ViewModelStore при изменении конфигурации активности. Это позволяет сохранить состояние ViewModel и восстановить его после пересоздания активности.
Переменная custom по умолчанию имеет значение null и фактически не используется, поскольку ViewModelStore более гибко выполняет всю работу по сохранению состояний для переживания изменений конфигураций. Тем не менее, переменную custom можно задействовать, переопределив такие функции, как onRetainCustomNonConfigurationInstance и getLastCustomNonConfigurationInstance. До появления ViewModel многие разработчики активно использовали(в 2012) именно её для сохранения данных при пересоздании активности когда менялась конфигурация.
Переменная viewModelStore имеет тип ViewModelStore и хранит ссылку на наш объект ViewModelStore. Значение в эту переменную NonConfigurationInstances#viewModelStore присваивается при вызове методаonRetainNonConfigurationInstance, а извлекается при вызове getLastNonConfigurationInstance (с этим методом мы уже столкнулись выше в методе ensureViewModelStore).
Разобравшись с классом NonConfigurationInstances, давайте выясним, где создаётся объект этого класса и каким образом в поле viewModelStore присваивается значение.
Для этого обратимся к методам onRetainNonConfigurationInstance и getLastNonConfigurationInstance, которые присутствуют в Activity и ComponentActivity.
Исходники метода в ComponentActivity выглядят следующим образом:
@Suppress("deprecation")
final override fun onRetainNonConfigurationInstance(): Any? {
// Maintain backward compatibility.
val custom = onRetainCustomNonConfigurationInstance()
var viewModelStore = _viewModelStore
if (viewModelStore == null) {
// No one called getViewModelStore(), so see if there was an existing
// ViewModelStore from our last NonConfigurationInstance
val nc = lastNonConfigurationInstance as NonConfigurationInstances?
if (nc != null) {
viewModelStore = nc.viewModelStore
}
}
if (viewModelStore == null && custom == null) {
return null
}
val nci = NonConfigurationInstances()
nci.custom = custom
nci.viewModelStore = viewModelStore
return nci
}Метод onRetainNonConfigurationInstance() возвращает объект NonConfigurationInstances, содержащий ссылку на ранее созданный ViewModelStore.
Таким образом, при уничтожении активности (например, при повороте экрана) вызывается этот метод, и ViewModelStore сохраняется в экземпляре NonConfigurationInstances.
Когда активность пересоздаётся, объект NonConfigurationInstances восстанавливается через вызов метода getLastNonConfigurationInstance(), и из него извлекается сохранённый ViewModelStore.
В методе onRetainNonConfigurationInstance реализована логика получения уже существующего ViewModelStore и объекта Custom (если он есть). После получения этих объектов они кладутся в экземпляр класса NonConfigurationInstances, который затем возвращается из метода.
Метод onRetainNonConfigurationInstance создаёт объект класса NonConfigurationInstances, помещает внутрь viewModelStore и кастомный объект, а затем возвращает его. Возникает вопрос: кто именно вызывает этот метод?
Вызывающий метод внутри самого класса Activity(самый базовый Activity от которого наследуются все остальные):
NonConfigurationInstances retainNonConfigurationInstances() {
Object activity = onRetainNonConfigurationInstance(); // <- вызов onRetainNonConfigurationInstance()
//...code
NonConfigurationInstances nci = new NonConfigurationInstances();
nci.activity = activity; // <- присвоение извлеченного объекта из onRetainNonConfigurationInstance()
nci.children = children;
nci.fragments = fragments;
nci.loaders = loaders;
if (mVoiceInteractor != null) {
mVoiceInteractor.retainInstance();
nci.voiceInteractor = mVoiceInteractor;
}
return nci;
}Как видно, сам класс Activity вызывает метод onRetainNonConfigurationInstance с которым мы ранее познакомились и сохраняет результат в полеactivity класса NonConfigurationInstances. При этом мы снова сталкиваемся с классом NonConfigurationInstances, но на этот раз он объявлен в самой Activity и имеет дополнительные поля:
static final class NonConfigurationInstances {
Object activity; // <- Здесь и будет храниться ComponentActivity.NonConfigurationInstances
HashMap<String, Object> children;
FragmentManagerNonConfig fragments;
ArrayMap<String, LoaderManager> loaders;
VoiceInteractor voiceInteractor;
}Чтобы устранить путаницу:
- Объект
ViewModelStoreхранится внутриComponentActivity#NonConfigurationInstances. - Сам объект
ComponentActivity#NonConfigurationInstancesхранится вActivity#NonConfigurationInstance. - Это достигается через метод
retainNonConfigurationInstances()классаActivity.
Но кто же вызывает метод retainNonConfigurationInstances() и где хранится конечный объект Activity#NonConfigurationInstance, который содержит ViewModelStore?
Ответ на этот вопрос кроется в классе ActivityThread, который отвечает за управление жизненным циклом активностей и их взаимодействие с системой. Именно этот класс обрабатывает создание, уничтожение и повторное создание активности, а также отвечает за сохранение и восстановление данных при изменениях конфигурации.
Метод из ActivityThread, который непосредственно вызывает Activity.retainNonConfigurationInstances(), называется ActivityThread.performDestroyActivity().
Рассмотрим его исходники в классе ActivityThread, далее исходники:
void performDestroyActivity(ActivityClientRecord r, boolean finishing,
boolean getNonConfigInstance, String reason) {
//...
if (getNonConfigInstance) {
try {
// Вызов Activity.retainNonConfigurationInstances()
// и сохранение в r.lastNonConfigurationInstances
r.lastNonConfigurationInstances = r.activity.retainNonConfigurationInstances();
} catch (Exception e) {
if (!mInstrumentation.onException(r.activity, e)) {
throw new RuntimeException("Unable to retain activity "
+ r.intent.getComponent().toShortString() + ": " + e.toString(), e);
}
}
}
//...
}Чтобы найти исходники ActivityThread, достаточно в Android Studio воспользоваться поиском по имени класса: ActivityThread.
Или зайти в исходники Android по одной из ссылок:
После вызова метода retainNonConfigurationInstances() результат сохраняется в поле lastNonConfigurationInstances объекта ActivityClientRecord:
r.lastNonConfigurationInstances = r.activity.retainNonConfigurationInstances();Класс ActivityClientRecord представляет собой запись активности и используется для хранения всей информации, связанной с реальным экземпляром активности.
Это своего рода структура данных для ведения учета активности в процессе выполнения приложения.
Основные поля класса ActivityClientRecord:
lastNonConfigurationInstances— объектActivity#NonConfigurationInstance, в котором хранитсяComponentActivity#NonConfigurationInstancesв котором хранитсяViewModelStore.state— объектBundle, содержащий сохраненное состояние активности. Да, да, это тот самый Bundle который мы получаем в методеonCreate,onRestoreInstanceStateиonSaveInstanceStateintent— объектIntent, представляющий намерение запуска активности.window— объектWindow, связанный с активностью.activity— сам объектActivity.parent— родительская активность (если есть).createdConfig— объектConfiguration, содержащий настройки, примененные при создании активности.overrideConfig— объектConfiguration, содержащий текущие настройки активности.
В рамках данной статьи нас интересует только поле lastNonConfigurationInstances, так как именно оно связано с хранением и восстановлением ViewModelStore.
Теперь давайте разберемся, как вызывается метод performDestroyActivity() в рамках системного вызова.
Последовательность вызовов:
ActivityTransactionItem.execute()ActivityRelaunchItem.execute()ActivityThread.handleRelaunchActivity()ActivityThread.handleRelaunchActivityInner()ActivityThread.handleDestroyActivity()ActivityThread.performDestroyActivity()
Важно понимать, что на более высоком уровне в этой цепочке стоят такие классы, как ClientTransactionItem, ClientTransaction и ClientLifecycleManager, а еще выше — сама система, которая управляет взаимодействием устройства с сенсорами и другими компонентами. Однако, углубляться дальше в эту цепочку мы не будем, так как всего через пару слоев окажемся на уровне межпроцессного взаимодействия (IPC) и работы системы с процессами.
На вершине вызовов находится метод ActivityTransactionItem.execute(), который запускает цепочку: сначала вызывает getActivityClientRecord(), а затем тот вызывает ClientTransactionHandler.getActivityClient().
public abstract class ActivityTransactionItem extends ClientTransactionItem {
@Override
public final void execute(ClientTransactionHandler client, IBinder token,
PendingTransactionActions pendingActions) {
final ActivityClientRecord r = getActivityClientRecord(client, token); // <- Вызов getActivityClientRecord
execute(client, r, pendingActions);
}
@NonNull
ActivityClientRecord getActivityClientRecord(
@NonNull ClientTransactionHandler client, IBinder token) {
final ActivityClientRecord r = client.getActivityClient(token); // <- получение клиент от ClientTransactionHandler(ActivityThread)
...
return r;
}
}ClientTransactionHandler — это абстрактный класс, и одна из его реализаций — класс ActivityThread, с которым мы уже успели познакомиться.
public final class ActivityThread extends ClientTransactionHandler
implements ActivityThreadInternal {
...
@Override
public ActivityClientRecord getActivityClient(IBinder token) {
return mActivities.get(token); // <- Возвращает из Map ActivityClientRecord по ключу
}
...
}От ActivityTransactionItem - наследуется класс ActivityRelaunchItem, который и запускает у ActivityThread метод handleRelaunchActivity:
public class ActivityRelaunchItem extends ActivityTransactionItem {
@Override
public void execute(@NonNull ClientTransactionHandler client, @NonNull ActivityClientRecord r,
@NonNull PendingTransactionActions pendingActions) {
...
client.handleRelaunchActivity(mActivityClientRecord, pendingActions);
...
}
}Все запущенные активности внутри нашего приложения хранятся в коллекций Map в объекте класса ActivityThread:
/**
* Maps from activity token to local record of running activities in this process.
* ....
*/
@UnsupportedAppUsage
final ArrayMap<IBinder, ActivityClientRecord> mActivities = new ArrayMap<>();Таким образом, мы наконец выяснили, что наша ViewModel фактически хранится в объекте ActivityThread, который является синглтоном. Благодаря этому ViewModel не уничтожается при изменении конфигурации.
Важно: Экземпляр ActivityThread является синглтоном и существует на протяжении всего жизненного цикла процесса приложения. В методе handleBindApplication() внутри ActivityThread создается объект Application, который также живет до завершения процесса. Это означает, что ActivityThread и Application связаны общим жизненным циклом, за исключением того, что ActivityThread появляется раньше — еще до создания Application — и управляет его инициализацией.
Восстановление ViewModelStore
Исходя из того, что мы обнаружили ранее, цепочка хранения ViewModel выглядит следующим образом:
ViewModelхранится внутриViewModelStore.ViewModelStoreхранится вComponentActivity#NonConfigurationInstances.ComponentActivity#NonConfigurationInstancesхранится вActivity#NonConfigurationInstance.Activity#NonConfigurationInstanceхранится вActivityClientRecord.ActivityClientRecordхранится вActivityThread.
При повторном создании Activity вызывается его метод attach(), одним из параметров которого является Activity#NonConfigurationInstances. Этот объект извлекается из связанного с Activity объекта ActivityClientRecord.
Когда у Activity меняется конфигурация, система сразу же перезапускает её, чтобы применить новые параметры. В этот момент ActivityThread.java мгновенно извлекает ViewModelStore, который хранится в ComponentActivity#NonConfigurationInstances. Этот объект, в свою очередь, находится внутри Activity#NonConfigurationInstances.
Далее Activity#NonConfigurationInstances сохраняется в ActivityClientRecord, связанном с пересоздаваемой Activity. Внутри ActivityClientRecord есть специальное поле lastNonConfigurationInstances, куда и помещается этот объект. Сам ActivityClientRecord хранится в Map-коллекции внутри ActivityThread.java, который является синглтоном в рамках процесса приложения и способен переживать изменения конфигурации.
После этого ActivityThread пересоздаёт Activity, применяя новые параметры конфигурации. При создании он передаёт в неё все сохранённые данные, включая NonConfigurationInstances, который, в конечном итоге, содержит ViewModelStore. А ViewModelStore, в свою очередь, хранит нашу ViewModel
Диаграмма вызовов при сохранении и восстановлении ViewModelStore
Диаграмма ниже иллюстрирует цепочку вызовов. Ради упрощения некоторые детали опущены, а избыточные абстракции убраны:
Итоги
В этой статье мы не касались работы ViewModel как таковой — фокус был исключительно на том, почему она не умирает при пересоздании Activity, и за счёт чего это вообще возможно.
Мы проследили всю цепочку: ViewModel → ViewModelStore → ComponentActivity#NonConfigurationInstances → Activity#NonConfigurationInstances → ActivityClientRecord → ActivityThread. Именно в этой глубокой вложенности и заключается ответ: ViewModel выживает, потому что сохраняется не в Activity напрямую, а в объекте, который система сама передаёт новой Activity при конфигурационных изменениях.
Сам ViewModelStore создаётся либо с нуля, либо восстанавливается через getLastNonConfigurationInstance(). Он очищается только в onDestroy(), если isChangingConfigurations == false, — то есть если Activity действительно умирает, а не пересоздаётся.
Под капотом всё это обеспечивается ActivityThread, который сохраняет NonConfigurationInstances в ActivityClientRecord, а потом передаёт в метод attach() при создании новой Activity. ActivityThread — синглтон, живущий столько же, сколько и процесс, и именно он является опорной точкой, через которую проходит вся цепочка восстановления.
ViewModel выживает не потому, что её кто-то “сохраняет” — а потому, что никто её не убивает. Пока жив ActivityThread, жив и ViewModelStore.
Позже мы снова вернемся к ActivityThread и ActivityClientRecord, это будет в рамках следующих статьей.
Обсуждение
Пока нет комментариев. Будьте первым, кто поделится своими мыслями!