Conservar dentro y fuera de ViewModel

imagen

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.lifecycle
2) Nuevo androidx.lifecycle

Spoiler : 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 onDestroy

En 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.

imagen

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

Source: https://habr.com/ru/post/439926/


All Articles