Questions de partage de vie



Chaque développeur a posé des questions sur le cycle de vie de l'activité: qu'est-ce qu'un service de liaison, comment enregistrer l'état de l'interface lorsque l'écran est pivoté et en quoi le fragment diffère de l'activité.
Chez FunCorp, nous avons accumulé une liste de questions sur des sujets similaires, mais avec certaines nuances. Je veux en partager certains avec vous.


1. Tout le monde sait que si vous ouvrez la deuxième activité au-dessus de la première et faites pivoter l'écran, la chaîne d'appels du cycle de vie ressemblera à ceci:


Activité d'ouverture

FirstActivity: onPause
SecondActivity: onCreate
SecondActivity: onStart
SecondActivity: onResume
FirstActivity: onSaveInstanceState
FirstActivity: onStop


Tourner

SecondActivity: onPause
SecondActivity: onSaveInstanceState
SecondActivity: onStop
SecondActivity: onCreate
SecondActivity: onStart
SecondActivity: onRestoreInstanceState
SecondActivity: onResume


Retour en arrière

SecondActivity: onPause
FirstActivity: onCreate
FirstActivity: onStart
FirstActivity: onRestoreInstanceState
SecondActivity: onStop


Et que se passera-t-il si la deuxième activité est transparente?


Solution


Dans le cas d'une activité top transparente, en termes de logique, tout est légèrement différent. Précisément parce qu'il est transparent, après la rotation, il faut restaurer le contenu et l'activité qui se trouve directement en dessous. Par conséquent, l'ordre des appels sera légèrement différent:


Activité découverte

FirstActivity: onPause
SecondActivity: onCreate
SecondActivity: onStart
SecondActivity: onResume


Tourner

SecondActivity: onPause
SecondActivity: onSaveInstanceState
SecondActivity: onStop
SecondActivity: onCreate
SecondActivity: onStart
SecondActivity: onRestoreInstanceState
SecondActivity: onResume
FirstActivity: onSaveInstanceState
FirstActivity: onStop
FirstActivity: onCreate
FirstActivity: onStart
FirstActivity: onRestoreInstanceState
FirstActivity: onResume
FirstActivity: onPause


2. Aucune application ne peut se passer de l'ajout dynamique d'une vue, mais il faut parfois déplacer la même vue entre différents écrans. Le même objet peut-il être ajouté simultanément à deux activités différentes? Que se passe-t-il si je le crée avec le contexte de l'application et que je souhaite l'ajouter à différentes activités en même temps?


Pourquoi est-ce nécessaire?
Il existe des bibliothèques «peu agréables» qui contiennent une logique métier importante dans les vues personnalisées, et recréer ces vues au sein de chaque nouvelle activité est une mauvaise décision, car Je veux avoir un seul ensemble de données.



Solution


Rien n'empêche de créer une vue avec le contexte de l'application. Il appliquera simplement des styles par défaut qui ne sont liés à aucune activité. Vous pouvez également déplacer cette vue entre différentes activités sans problème, mais vous devez vous assurer qu'elle est ajoutée à un seul parent


private void addViewInner(View child, int index, LayoutParams params, boolean preventRequestLayout) { ... if (child.getParent() != null) { throw new IllegalStateException("The specified child already has a parent. " + "You must call removeView() on the child's parent first."); } ... } 

Vous pouvez, par exemple, vous abonner à ActivityLifecycleCallbacks, supprimer onStop (removeView) de l'activité en cours, ajouter onStart à la prochaine ouverture (addView).


3. Le fragment peut être ajouté par ajout et remplacement. Et quelle est la différence entre ces deux options en termes d'ordre d'appeler les méthodes du cycle de vie? Quels sont les avantages de chacun d'eux?


Solution


Même si vous ajoutez un fragment via replace, cela ne signifie pas qu'il est complètement remplacé. Cela signifie qu'à ce stade du conteneur, sa vue sera remplacée, par conséquent, onDestroyView sera appelé pour le fragment actuel et onCreateView sera appelé à nouveau lors du retour.



Cela change à peu près les règles du jeu. Vous devez détacher tous les contrôleurs et classes associés à l'interface utilisateur dans onDestroyView. Il est nécessaire de séparer clairement la réception des données nécessaires au fragment et le remplissage de la vue (listes, etc.), car le remplissage et la rupture de la vue auront lieu beaucoup plus souvent que la réception de données (lecture de certaines données de la base de données).


Il existe également des nuances avec la récupération d'état: par exemple, onSaveInstanceState vient parfois après onDestroyView. En outre, il convient de garder à l'esprit que si null est entré dans onViewStateRestored, cela signifie que vous n'avez pas besoin de restaurer quoi que ce soit, mais pas de réinitialiser à l'état par défaut.


Si nous parlons de la commodité entre l'ajout et le remplacement, alors le remplacement est plus économique en mémoire si vous avez une navigation profonde (nous avons une profondeur de navigation utilisateur de l'un des KPI du produit). Il est également beaucoup plus pratique de remplacer la barre d'outils par replace, car dans onCreateView vous pouvez la réinjecter. Parmi les avantages de l'ajout: moins de problèmes avec le cycle de vie, lorsque vous revenez, je ne recrée pas la vue et je n'ai pas besoin de remplir quoi que ce soit.


4. Parfois, vous devez toujours travailler directement avec les services et même avec les services de liaison. L'activité interagit avec l'un de ces services (une seule activité). Il se connecte au service et y transfère des données. Lorsque vous tournez l'écran, notre activité est détruite, et nous sommes obligés de rebondir de ce service. Mais s'il n'y a pas de connexion, le service est détruit, et après avoir tourné, bind sera un service complètement différent. Comment vous assurer que lorsque vous activez le service reste en ligne?


Solution


Si vous connaissez une belle solution, écrivez dans les commentaires. Seulement quelque chose de similaire me vient à l'esprit:


  @Override protected void onDestroy() { super.onDestroy(); ThreadsUtils.postOnUiThread(new Runnable() { @Override public void run() { unbindService(mConnection); } }); } 

5. Récemment, nous avons refait la navigation à l'intérieur de notre application sur Single Activity (en utilisant l'une des bibliothèques disponibles). Auparavant, chaque écran d'application était une activité distincte, maintenant la navigation fonctionne sur des fragments. Le problème du retour à l'activité au milieu de la pile a été résolu par des drapeaux d'intention. Comment puis-je retourner au fragment au milieu de la pile?


Solution


Oui, FragmentManager ne fournit pas de solutions prêtes à l'emploi. Cicerone fait quelque chose de similaire en lui-même:


  protected void backTo(BackTo command) { String key = command.getScreenKey(); if (key == null) { backToRoot(); } else { int index = localStackCopy.indexOf(key); int size = localStackCopy.size(); if (index != -1) { for (int i = 1; i < size - index; i++) { localStackCopy.pop(); } fragmentManager.popBackStack(key, 0); } else { backToUnexisting(command.getScreenKey()); } } } 

6. En outre, nous nous sommes récemment débarrassés d'un composant aussi inefficace et complexe que ViewPager, car la logique d'interaction avec lui est très compliquée et le comportement des fragments est imprévisible dans certains cas. Dans certains fragments, nous avons utilisé des fragments internes. Que se passe-t-il lors de l'utilisation de fragments dans des éléments RecycleView?


Solution


En général, il n'y aura rien de mal. Le fragment sera ajouté et affiché sans aucun problème. La seule chose à laquelle nous sommes confrontés est des incohérences avec son cycle de vie. L'implémentation sur ViewPager gère le cycle de vie des fragments via setUserVisibleHint, et RecycleView fait tout le front sans penser à la visibilité et à l'accessibilité réelles des fragments.


7. Pour la même raison que nous sommes passés de ViewPager, nous avons rencontré le problème de la restauration de l'état. Dans le cas des fragments, cela a été mis en œuvre par le framework: aux bons endroits, nous avons simplement redéfini onSaveInstanceState et enregistré toutes les données nécessaires dans le Bundle. Lorsque vous recréez le ViewPager, tous les fragments ont été restaurés par le FragmentManager et ont renvoyé leur état. Que faire avec RecycleView et son ViewHolder?


Solution


«Vous devez tout écrire dans la base de données et y lire à chaque fois», dites-vous. Ou la logique d'enregistrement de l'état doit être extérieure et la liste n'est qu'un affichage. Dans un monde idéal, c'est. Mais dans notre cas, chaque élément de la liste est un écran complexe avec sa propre logique. Par conséquent, j'ai dû inventer mon vélo dans le style "faisons la même logique que dans le ViewPager et fragment":


Adaptateur
 public class RecycleViewGalleryAdapter extends RecyclerView.Adapter<GalleryItemViewHolder> implements GalleryAdapter { private static final String RV_STATE_KEY = "RV_STATE"; @Nullable private Bundle mSavedState; @Override public void onBindViewHolder(GalleryItemViewHolder holder, int position) { if (holder.isAttached()) { holder.detach(); } holder.attach(createArgs(position, getItemViewType(position))); restoreItemState(holder); } @Override public void saveState(Bundle bundle) { Bundle adapterState = new Bundle(); saveItemsState(adapterState); bundle.putBundle(RV_STATE_KEY, adapterState); } @Override public void restoreState(@Nullable Bundle bundle) { if (bundle == null) { return; } mSavedState = bundle.getBundle(RV_STATE_KEY); } private void restoreItemState(GalleryItemViewHolder holder) { if (mSavedState == null) { holder.restoreState(null); return; } String stateKey = String.valueOf(holder.getGalleryItemId()); Bundle state = mSavedState.getBundle(stateKey); if (state == null) { holder.restoreState(null); mSavedState = null; return; } holder.restoreState(state); mSavedState.remove(stateKey); } private void saveItemsState(Bundle outState) { GalleryItemHolder holder = getCurrentGalleryViewItem(); saveItemState(outState, (GalleryItemViewHolder) holder); } private void saveItemState(Bundle bundle, GalleryItemViewHolder holder) { Bundle itemState = new Bundle(); holder.saveState(itemState); bundle.putBundle(String.valueOf(holder.getGalleryItemId()), itemState); } } 

À Fragment.onSaveInstanceState, nous lisons l'état des supports dont nous avons besoin et les mettons dans le Bundle. Lors de la recréation des supports, nous obtenons le bundle enregistré et sur onBindViewHolder nous passons les états trouvés à l'intérieur des supports:


8. De quoi cela nous menace-t-il?


  @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity); ViewGroup root = findViewById(R.id.default_id); ViewGroup view1 = new LinearLayout(this); view1.setId(R.id.default_id); root.addView(view1); ViewGroup view2 = new FrameLayout(this); view2.setId(R.id.default_id); view1.addView(view2); ViewGroup view3 = new RelativeLayout(this); view3.setId(R.id.default_id); view2.addView(view3); } 

Solution


En fait, il n'y a rien de mal à cela. Dans la même RecycleView, les listes d'éléments avec le même identifiant sont stockées. Cependant, il y a encore une petite nuance:


  @Override protected <T extends View> T findViewTraversal(@IdRes int id) { if (id == mID) { return (T) this; } final View[] where = mChildren; final int len = mChildrenCount; for (int i = 0; i < len; i++) { View v = where[i]; if ((v.mPrivateFlags & PFLAG_IS_ROOT_NAMESPACE) == 0) { v = v.findViewById(id); if (v != null) { return (T) v; } } } return null; } 

Cela devrait être soigneusement si nous avons des éléments avec le même identifiant dans la hiérarchie, car c'est toujours le premier élément trouvé qui est toujours renvoyé, et à différents niveaux de l'appel findViewById, il peut s'agir d'objets différents.


9. Vous tombez de TooLargeTransaction lorsque vous faites pivoter l'écran (oui, ici notre ViewPager est toujours indirectement à blâmer). Comment trouver le coupable?


Solution


Tout est assez simple: bloquez ActivityLifecycleCallbacks sur l'application, interceptez tout onActivitySaveInstanceState et analysez tout ce qui se trouve à l'intérieur du bundle. Là, vous pouvez obtenir le statut de toutes les vues et de tous les fragments à l'intérieur de cette activité.


Voici un exemple de la façon dont nous obtenons l'état des fragments du bundle:


 /** * Tries to find saved [FragmentState] in bundle using 'android:support:fragments' key. */ fun Bundle.getFragmentsStateList(): List<FragmentBundle>? { try { val fragmentManagerState: FragmentManagerState? = getParcelable("android:support:fragments") val active = fragmentManagerState?.mActive ?: return emptyList() return active.filter { it.mSavedFragmentState != null }.map { fragmentState -> FragmentBundle(fragmentState.mClassName, fragmentState.mSavedFragmentState) } } catch (throwable: Throwable) { Assert.fail(throwable) return null } } fun init() { application.registerActivityLifecycleCallbacks(object : SimpleActivityLifecycleCallback() { override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle?) { super.onActivitySaveInstanceState(activity, outState) outState?.let { ThreadsUtils.runOnMainThread { trackActivitySaveState(activity, outState) } } } }) } @MainThread private fun trackActivitySaveState(activity: Activity, outState: Bundle) { val sizeInBytes = outState.getSizeInBytes() val fragmentsInfos = outState.getFragmentsStateList() ?.map { mapFragmentsSaveInstanceSaveInfo(it) } ... } 

Ensuite, nous calculons simplement la taille du Bundle et l'enregistrons:


  fun Bundle.getSizeInBytes(): Int { val parcel = Parcel.obtain() return try { parcel.writeValue(this) parcel.dataSize() } finally { parcel.recycle() } } 

10. Supposons que nous ayons une activité et un ensemble de dépendances sur elle. Dans certaines conditions, nous devons recréer un ensemble de ces dépendances (par exemple, en cliquant sur une certaine expérience avec une autre interface utilisateur). Comment mettons-nous cela en œuvre?


Solution


Bien sûr, vous pouvez bricoler avec des drapeaux et en faire une sorte de redémarrage «béquille» de l'activité grâce au lancement de l'intention. Mais en fait, tout est très simple - l'activité a une méthode de recréation.


Très probablement, la plupart de ces connaissances ne vous seront pas utiles, car vous ne venez pas à chacun d'eux d'une bonne vie. Cependant, certains d'entre eux montrent bien comment une personne sait raisonner et proposer des solutions. Nous utilisons des questions similaires dans les entretiens. Si vous avez des tâches intéressantes que l'on vous a demandé de résoudre lors des entretiens, ou si vous les définissez vous-même, écrivez-les dans les commentaires - ce sera intéressant à discuter!

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


All Articles