Conserver le ViewModel à l'intérieur et à l'extérieur

image

À un moment donné, j'ai remarqué des discussions périodiques sur le fonctionnement réel de ViewModel à partir des composants architecturaux de Google. Réalisant que je ne comprenais pas moi-même, j'ai grimpé sur Internet et j'ai été surpris de constater qu'il y avait une quantité incroyable d'articles identiques sur la façon de cuisiner ViewModel, de se faire des amis avec LiveData, de glisser des dépendances via Dagger, de copuler avec RxJava et d'autres titres de divers degrés d'utilité, cependant, il n'y a presque rien sur ce qui se passe à l'intérieur. Je vais donc essayer de combler le vide moi-même.

Attention


TL; DR si vous vous sentez désolé pour le temps - descendez à la sortie, vous perdrez peu.

Donc, la première chose à laquelle vous pouvez faire attention est qu'il existe 2 packages différents de composants architecturaux avec ViewModel, à savoir:

1) Ancien android.arch.lifecycle
2) Nouveau androidx.lifecycle

Spoiler : Il n'y a pas beaucoup de différence entre eux.

Tout le travail se cache derrière le défi:

ViewModelProviders.of(activity).get(MyViewModel::class.java) 

Commençons par la méthode of

  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 vérifie simplement null, et AndroidViewModelFactory n'est qu'un singleton thread-safe qui contient Application. Ils ne présentent donc pas d'intérêt particulier, le plus intéressant étant la méthode ViewModelStores.of :

  public static ViewModelStore of(@NonNull FragmentActivity activity) { if (activity instanceof ViewModelStoreOwner) { return ((ViewModelStoreOwner) activity).getViewModelStore(); } return holderFragmentFor(activity).getViewModelStore(); } 

À première vue, cela semble plutôt étrange - pourquoi une FragmentActivity devrait-elle vérifier la présence de l'interface ViewModelStoreOwner si elle l'implémente déjà ? - Ce n'était pas toujours le cas - jusqu'à la fin de février 2018, lorsque la version 27.1.0 de la bibliothèque de support a été publiée , FragmentActivity n'avait jamais implémenté ViewModelStoreOwner. En même temps, ViewModel a fonctionné pour lui-même.

Commençons donc par l'ancien cas - la méthode holderFragmentFor a été lancée :

  public static HolderFragment holderFragmentFor(FragmentActivity activity) { return sHolderFragmentManager.holderFragmentFor(activity); } 

Ensuite, obtenez ou créez un nouveau fragment de support :

  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; } 

Eh bien, HolderFragment lui - même , bien sûr, conservé

  public HolderFragment() { setRetainInstance(true); } 

En fait, l'objet ViewModelStore y est stocké, qui à son tour contient un ensemble 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(); } } 

Revenons au cas où la version de la bibliothèque de support est 27.1.0 et supérieure. FragmentActivity implémente déjà l'interface ViewModelStoreOwner, c'est-à-dire qu'il implémente la seule méthode 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; } } 

Ici, je vais simplifier un peu - NonConfigurationInstances est un objet qui ne devrait pas dépendre de la configuration (évidemment du nom), qui se trouve dans l' activité et balaie l'intérieur de ActivityClientRecord à ActivityThread pendant la récréation entre onStop et onDestroy

En général, cela semble assez drôle - au lieu d'un piratage de la vie avec un transfert de ViewModel à l'intérieur du fragment de conservation , les développeurs ont fait un geste délicat - ils ont utilisé exactement le même mécanisme, mais se sont débarrassés de la nécessité de créer un fragment supplémentaire à chaque fois.

L'activité a toujours eu une méthode onRetainNonConfigurationInstance intéressante. Dans la classe d'activité, il n'a pratiquement rien fait. En général:

  public Object onRetainNonConfigurationInstance() { return null; } 

La description dans la documentation est prometteuse:
Appelé par le système, dans le cadre de la destruction d'une activité en raison d'un changement de configuration, lorsqu'il est connu qu'une nouvelle instance sera immédiatement créée pour la nouvelle configuration. Vous pouvez renvoyer n'importe quel objet que vous aimez ici, y compris l'instance d'activité elle-même, qui peut ensuite être récupérée en appelant getLastNonConfigurationInstance () dans la nouvelle instance d'activité.

image

C'est-à-dire ce qu'il ne faut pas y mettre - il sera analysé dans getLastNonConfigurationInstance () après avoir recréé l'activité. Les développeurs de composants architecturaux en ont profité. Parmi les inconvénients - jusqu'à 4 androïdes ne fonctionnent pas, il faudra le faire à l'ancienne en conservant le fragment.

La méthode clear () de ViewModel a été appelée très simplement - dans la méthode onDestroy FragmentActivity.

  protected void onDestroy() { super.onDestroy(); if (this.mViewModelStore != null && !this.isChangingConfigurations()) { this.mViewModelStore.clear(); } this.mFragments.dispatchDestroy(); } 

En fait, avec Androidx presque tout est identique, la seule différence est que la méthode getViewModelStore () n'est plus dans FragmentActivity, mais dans ComponentActivity , dont FragmentActivity est hérité dans AndroidX. Seul l'appel à la méthode clear () a changé; il a été supprimé de onDestroy à un rappel indépendant, qui est créé dans le constructeur ComponentActivity:

  getLifecycle().addObserver(new GenericLifecycleObserver() { @Override public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) { if (event == Lifecycle.Event.ON_DESTROY) { if (!isChangingConfigurations()) { getViewModelStore().clear(); } } } }); 

Pour le protocole - lors de la création de l'article, les éléments suivants ont été utilisés:

Bibliothèque de support 27.0.0, 28.0.0
androidx.lifecycle: lifecycle-viewmodel: 2.0.0
androidx.lifecycle: lifecycle-extensions: 2.0.0
android.arch.lifecycle: extensions: 1.1.1
android.arch.lifecycle: viewmodel: 1.1.1

Conclusions:


- ViewModel a vraiment survécu à la recréation d'activité dans le fragment de conservation jusqu'à la bibliothèque de support 27.1.0 qui est apparue en février 2018
- À partir de la bibliothèque de support version 27.1.0 , ainsi que dans AndroidX ViewModel, je suis allé attendre pour recréer l'activité dans FragmentActivity.NonConfigurationInstances ( ComponentActivity.NonConfigurationInstances for AndroidX), en fait le même mécanisme à travers lequel conserver les fragments fonctionne, mais la création d'un fragment supplémentaire n'est pas nécessaire , tous les ViewModel sont envoyés "à côté de" avec des fragments de conservation.
- Le mécanisme de ViewModel est presque le même dans AndroidX et la bibliothèque de support
- Si vous devez soudainement (oui, je ne peux pas imaginer pourquoi) faire glisser des données qui devraient vivre pendant la durée de l'activité, mais en même temps prendre en compte la récréation - vous pouvez utiliser le onRetainNonConfigurationInstance () / getLastNonConfigurationInstance ()
- Quelle est l'ancienne décision, à quoi ressemble le nouveau quelque chose entre un hack documenté et des béquilles

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


All Articles