Reter dentro e fora do ViewModel

imagem

Em algum momento, notei conversas periódicas sobre como o ViewModel realmente funciona a partir dos componentes arquiteturais do Google. Percebendo que eu mesmo não entendi, entrei na Internet e fiquei surpreso ao descobrir que há uma quantidade incrível de artigos idênticos sobre como cozinhar o ViewModel, fazer amizade com o LiveData, passar dependências pelo Dagger, copular com o RxJava e outros títulos de vários graus de utilidade, no entanto, não há quase nada sobre o que está acontecendo lá dentro. Então, eu vou tentar preencher a lacuna.

Cuidado


TL; DR, se você sentir pena do tempo - role para a saída, perderá pouco.

Portanto, a primeira coisa que você deve prestar atenção é que existem 2 pacotes diferentes de componentes de arquitetura com o ViewModel, a saber:

1) Antigo android.arch.lifecycle
2) Novo androidx.lifecycle

Spoiler : Não há muita diferença entre eles.

Todo o trabalho está por trás do desafio:

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

Vamos começar com o método

  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 apenas verifica nulo, e AndroidViewModelFactory é apenas um singleton seguro para threads que contém Application. Portanto, eles não são de interesse especial, o mais interessante está no método ViewModelStores.of :

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

À primeira vista, parece bastante estranho - por que um FragmentActivity deve verificar a presença da interface ViewModelStoreOwner se já a implementa ? - Esse nem sempre foi o caso - até o distante fevereiro de 2018, quando a biblioteca de suporte versão 27.1.0 foi lançada , o FragmentActivity nunca havia implementado o ViewModelStoreOwner. Ao mesmo tempo, o ViewModel trabalhou por si mesmo.

Então, vamos começar com o caso antigo - o método holderFragmentFor foi lançado :

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

Em seguida, obtenha ou crie um novo fragmento de titular :

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

Bem, o HolderFragment em si , é claro, retido

  public HolderFragment() { setRetainInstance(true); } 

Na verdade, o objeto ViewModelStore é armazenado nele, que, por sua vez, contém um pacote 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(); } } 

Vamos voltar ao caso em que a versão da biblioteca de suporte é 27.1.0 e superior. FragmentActivity já implementa a interface ViewModelStoreOwner, ou seja, implementa o ú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; } } 

Aqui vou simplificar um pouco - NonConfigurationInstances é um objeto que não deve depender da configuração (obviamente do nome), que fica na Activity e varre dentro do ActivityClientRecord por ActivityThread durante a recreação entre onStop e onDestroy

Em geral, parece bem engraçado - em vez de um golpe na vida com uma transferência do ViewModel dentro do fragmento de retenção , os desenvolvedores fizeram uma jogada complicada - eles usaram exatamente o mesmo mecanismo, mas se livraram da necessidade de criar um fragmento extra a cada vez.

A atividade sempre teve um método onRetainNonConfigurationInstance interessante. Na classe Activity, ele basicamente não fez nada. Em geral:

  public Object onRetainNonConfigurationInstance() { return null; } 

A descrição na documentação é promissora:
Chamado pelo sistema, como parte da destruição de uma atividade devido a uma alteração na configuração, quando se sabe que uma nova instância será criada imediatamente para a nova configuração. Você pode retornar qualquer objeto que desejar aqui, incluindo a própria instância de atividade, que pode ser recuperada posteriormente chamando getLastNonConfigurationInstance () na nova instância de atividade.

imagem

Ou seja, o que não colocar lá - ele será rastreado em getLastNonConfigurationInstance () depois de recriar a Atividade. Os desenvolvedores de componentes arquitetônicos se aproveitaram disso. Dos pontos negativos - até 4 andróides não funcionam, terá que ser feito da maneira antiga através de reter fragmento.

O método clear () do ViewModel foi chamado de maneira muito simples - no método onDestroy FragmentActivity.

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

De fato, com o Androidx, quase tudo é igual, a única diferença é que o método getViewModelStore () não está mais em FragmentActivity, mas em ComponentActivity , do qual FragmentActivity é herdado no AndroidX. Somente a chamada para o método clear () foi alterada; foi removida de onDestroy para um retorno de chamada independente, criado no construtor 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 o protocolo - durante a criação do artigo, foram utilizados os seguintes:

Biblioteca de suporte 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

Conclusões:


- O ViewModel realmente sobreviveu à recriação de atividade no fragmento de retenção até a biblioteca de suporte 27.1.0 que apareceu em fevereiro de 2018
- A partir da biblioteca de suporte versão 27.1.0 em diante, bem como no AndroidX ViewModel, esperei para recriar a atividade em FragmentActivity.NonConfigurationInstances ( ComponentActivity.NonConfigurationInstances for AndroidX), na verdade o mesmo mecanismo pelo qual retém os fragmentos funciona, mas a criação de um fragmento extra não é necessária , todos os ViewModel são enviados "ao lado de" com fragmentos de retenção.
- O mecanismo do ViewModel é quase o mesmo na biblioteca AndroidX e Support
- Se você precisar repentinamente (sim, não consigo imaginar o porquê) arrastar dados que devem permanecer durante a atividade, mas levar em consideração a recreação - use os métodos onRetainNonConfigurationInstance () / getLastNonConfigurationInstance ()
- Qual é a decisão antiga, qual a aparência do novo algo entre um hack e muletas documentadas

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


All Articles