En algún momento, noté charlas periódicas sobre cómo
ViewModel realmente funciona a partir de los componentes arquitectónicos de Google. Al darme cuenta de que yo mismo no entendía, subí a Internet y me sorprendió descubrir que hay una increíble cantidad de artículos idénticos sobre cómo cocinar ViewModel, hacer amigos con LiveData, deslizar dependencias a través de Dagger, copular con RxJava y otros títulos de diversos grados de utilidad, Sin embargo, no hay casi nada sobre lo que está sucediendo dentro.
Así que intentaré llenar el vacío yo mismo.
Precaución
TL; DR si siente lástima por el tiempo: baje hasta la salida, perderá poco.
Entonces, lo primero a lo que puede prestar atención es que hay 2 paquetes diferentes de componentes arquitectónicos con ViewModel, a saber:
1) Viejo
android.arch.lifecycle2) Nuevo
androidx.lifecycleSpoiler : No hay mucha diferencia entre ellos.
Todo el trabajo yace detrás del desafío:
ViewModelProviders.of(activity).get(MyViewModel::class.java)
Comencemos con el método de
public static ViewModelProvider of(@NonNull FragmentActivity activity) { return of(activity, null); } public static ViewModelProvider of(@NonNull FragmentActivity activity, @Nullable Factory factory) { Application application = checkApplication(activity); if (factory == null) { factory = ViewModelProvider.AndroidViewModelFactory.getInstance(application); } return new ViewModelProvider(ViewModelStores.of(activity), factory); }
checkApplication solo busca null, y
AndroidViewModelFactory es solo un singleton seguro para subprocesos que contiene Application. Por lo tanto, no son de especial interés, lo más interesante está en el método
ViewModelStores.of :
public static ViewModelStore of(@NonNull FragmentActivity activity) { if (activity instanceof ViewModelStoreOwner) { return ((ViewModelStoreOwner) activity).getViewModelStore(); } return holderFragmentFor(activity).getViewModelStore(); }
A primera vista, parece bastante extraño: ¿por qué un
FragmentActivity debe verificar la presencia de la interfaz
ViewModelStoreOwner si
ya la implementa ? - Este no fue siempre el caso: hasta el lejano febrero de 2018, cuando
se lanzó la versión
27.1.0 de la biblioteca de soporte , FragmentActivity nunca había implementado ViewModelStoreOwner. Al mismo tiempo, ViewModel funcionó por sí mismo.
Entonces, comencemos con el caso anterior: se
lanzó el método
holderFragmentFor :
public static HolderFragment holderFragmentFor(FragmentActivity activity) { return sHolderFragmentManager.holderFragmentFor(activity); }
A continuación, solo obtenga o cree un nuevo fragmento de
soporte :
HolderFragment holderFragmentFor(FragmentActivity activity) { FragmentManager fm = activity.getSupportFragmentManager(); HolderFragment holder = findHolderFragment(fm); if (holder != null) { return holder; } holder = mNotCommittedActivityHolders.get(activity); if (holder != null) { return holder; } if (!mActivityCallbacksIsAdded) { mActivityCallbacksIsAdded = true; activity.getApplication().registerActivityLifecycleCallbacks(mActivityCallbacks); } holder = createHolderFragment(fm); mNotCommittedActivityHolders.put(activity, holder); return holder; }
Bueno, el propio
HolderFragment, por supuesto,
retenido public HolderFragment() { setRetainInstance(true); }
En realidad, el objeto
ViewModelStore se almacena en él, que a su vez contiene un paquete de
ViewModel :
public class ViewModelStore { private final HashMap<String, ViewModel> mMap = new HashMap<>(); final void put(String key, ViewModel viewModel) { ViewModel oldViewModel = mMap.put(key, viewModel); if (oldViewModel != null) { oldViewModel.onCleared(); } } final ViewModel get(String key) { return mMap.get(key); } public final void clear() { for (ViewModel vm : mMap.values()) { vm.onCleared(); } mMap.clear(); } }
Volvamos al caso cuando la versión de la biblioteca de soporte es 27.1.0 y superior. FragmentActivity ya implementa la interfaz ViewModelStoreOwner, es decir, implementa el único método
getViewModelStore :
public ViewModelStore getViewModelStore() { if (this.getApplication() == null) { throw new IllegalStateException("Your activity is not yet attached to the Application instance. You can't request ViewModel before onCreate call."); } else { if (this.mViewModelStore == null) { FragmentActivity.NonConfigurationInstances nc = (FragmentActivity.NonConfigurationInstances)this.getLastNonConfigurationInstance(); if (nc != null) { this.mViewModelStore = nc.viewModelStore; } if (this.mViewModelStore == null) { this.mViewModelStore = new ViewModelStore(); } } return this.mViewModelStore; } }
Aquí simplificaré un poco:
NonConfigurationInstances es un objeto que no debería depender de la configuración (obviamente del nombre), que se encuentra en
Activity y barre dentro de
ActivityClientRecord a través de
ActivityThread durante la recreación entre
onStop y
onDestroyEn general, parece bastante divertido: en lugar de un truco de vida con una transferencia ViewModel dentro del fragmento de
retención , los desarrolladores hicieron un movimiento complicado: utilizaron exactamente el mismo mecanismo, pero se deshicieron de la necesidad de crear un fragmento adicional cada vez.
La actividad siempre ha tenido un método
onRetainNonConfigurationInstance interesante. En la clase de Actividad, esencialmente no hizo nada. En general:
public Object onRetainNonConfigurationInstance() { return null; }
La descripción en la documentación es prometedora:
Llamado por el sistema, como parte de la destrucción de una actividad debido a un cambio de configuración, cuando se sabe que se creará inmediatamente una nueva instancia para la nueva configuración. Puede devolver cualquier objeto que desee aquí, incluida la instancia de actividad en sí, que luego se puede recuperar llamando a getLastNonConfigurationInstance () en la nueva instancia de actividad.

Es decir, qué no poner allí: se rastreará en
getLastNonConfigurationInstance () después de recrear la Actividad. Los desarrolladores de componentes arquitectónicos se aprovecharon de esto. De los inconvenientes: hasta 4 androides no funcionan, tendrá que hacerse de la manera tradicional a través del fragmento de retención.
El método
clear () de ViewModel se llamó de manera muy simple: en el método
onDestroy FragmentActivity.
protected void onDestroy() { super.onDestroy(); if (this.mViewModelStore != null && !this.isChangingConfigurations()) { this.mViewModelStore.clear(); } this.mFragments.dispatchDestroy(); }
De hecho, con Androidx casi todo es igual, la única diferencia es que el método getViewModelStore () ya no está en FragmentActivity, sino en
ComponentActivity , de la cual FragmentActivity se hereda en AndroidX. Solo ha cambiado la llamada al método clear (); se ha eliminado de onDestroy a una devolución de llamada independiente, que se crea en el constructor ComponentActivity:
getLifecycle().addObserver(new GenericLifecycleObserver() { @Override public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) { if (event == Lifecycle.Event.ON_DESTROY) { if (!isChangingConfigurations()) { getViewModelStore().clear(); } } } });
Para el protocolo: durante la creación del artículo se utilizó lo siguiente:
Biblioteca de soporte 27.0.0, 28.0.0
androidx.lifecycle: lifecycle-viewmodel: 2.0.0
androidx.lifecycle: extensiones de ciclo de vida: 2.0.0
android.arch.lifecycle: extensiones: 1.1.1
android.arch.lifecycle: viewmodel: 1.1.1
Conclusiones:
- ViewModel realmente sobrevivió a la recreación de la actividad en el fragmento de
retención hasta la biblioteca de soporte 27.1.0 que apareció en febrero de 2018
- Desde la versión
27.1.0 de la biblioteca de soporte en adelante, así como en
AndroidX ViewModel, fui a esperar para recrear la actividad en
FragmentActivity.NonConfigurationInstances (
ComponentActivity.NonConfigurationInstances para AndroidX), de hecho, el mismo mecanismo a través del cual los fragmentos funcionan, pero no es necesario crear un fragmento adicional , todos los ViewModel se envían "junto a" con fragmentos de retención.
- El mecanismo de ViewModel es casi el
mismo en AndroidX y en la biblioteca de soporte
- Si de repente necesita (sí, no puedo imaginar por qué) arrastrar datos que deberían vivir mientras vive la Actividad, pero
tenga en cuenta la recreación; puede usar
onRetainNonConfigurationInstance () /
getLastNonConfigurationInstance ()- Cuál es la decisión anterior, cómo se ve la nueva entre un truco documentado y muletas