Fragen zur Lebensfreigabe



Jeder Entwickler stellte Fragen zum Aktivitätslebenszyklus: Was ist ein Bindedienst, wie wird der Status der Benutzeroberfläche gespeichert, wenn der Bildschirm gedreht wird, und wie unterscheidet sich Fragment von Aktivität?
Wir bei FunCorp haben eine Liste mit Fragen zu ähnlichen Themen zusammengestellt, jedoch mit bestimmten Nuancen. Ich möchte einige davon mit Ihnen teilen.


1. Jeder weiß, dass die Lebenszyklus-Anrufkette folgendermaßen aussieht, wenn Sie die zweite Aktivität über der ersten öffnen und den Bildschirm drehen:


Eröffnungsaktivität

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


Drehen Sie sich

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


Kehre zurück

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


Und was passiert, wenn die zweite Aktivität transparent ist?


Lösung


Bei transparenter Top-Aktivität ist logisch alles etwas anders. Gerade weil es transparent ist, müssen nach der Rotation der Inhalt und die Aktivität, die sich direkt darunter befindet, wiederhergestellt werden. Daher unterscheidet sich die Reihenfolge der Anrufe geringfügig:


Erkennungsaktivität

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


Drehen Sie sich

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. Keine Anwendung kann auf das dynamische Hinzufügen einer Ansicht verzichten, aber manchmal müssen Sie dieselbe Ansicht zwischen verschiedenen Bildschirmen verschieben. Kann dasselbe Objekt gleichzeitig zu zwei verschiedenen Aktivitäten hinzugefügt werden? Was passiert, wenn ich es mit dem Anwendungskontext erstelle und es gleichzeitig verschiedenen Aktivitäten hinzufügen möchte?


Warum wird das benötigt?
Es gibt "nicht sehr angenehme" Bibliotheken, die wichtige Geschäftslogik in benutzerdefinierten Ansichten enthalten, und das Neuerstellen dieser Ansichten in jeder neuen Aktivität ist eine schlechte Entscheidung, da Ich möchte einen Datensatz haben.



Lösung


Nichts verhindert das Erstellen einer Ansicht mit dem Anwendungskontext. Es werden einfach Standardstile angewendet, die sich nicht auf eine Aktivität beziehen. Sie können diese Ansicht auch problemlos zwischen verschiedenen Aktivitäten verschieben, müssen jedoch sicherstellen, dass sie nur einem übergeordneten Element hinzugefügt wird


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."); } ... } 

Sie können beispielsweise ActivityLifecycleCallbacks abonnieren, onStop (removeView) aus der aktuellen Aktivität löschen und onStart zum nächsten Öffnen (addView) hinzufügen.


3. Das Fragment kann durch Hinzufügen und Ersetzen hinzugefügt werden. Und was ist der Unterschied zwischen diesen beiden Optionen in Bezug auf die Reihenfolge des Aufrufs von Lebenszyklusmethoden? Was sind die Vorteile von jedem von ihnen?


Lösung


Selbst wenn Sie ein Fragment durch Ersetzen hinzufügen, bedeutet dies nicht, dass es vollständig ersetzt wird. Dies bedeutet, dass an dieser Stelle im Container die Ansicht ersetzt wird. Daher wird onDestroyView für das aktuelle Fragment aufgerufen und onCreateView wird bei der Rückkehr erneut aufgerufen.



Dies ändert ziemlich genau die Spielregeln. Sie müssen alle Controller und Klassen, die der Benutzeroberfläche zugeordnet sind, in onDestroyView trennen. Es ist notwendig, den Empfang der für das Fragment erforderlichen Daten und das Ausfüllen der Ansicht (Listen usw.) klar zu trennen, da das Ausfüllen und Unterbrechen der Ansicht viel häufiger erfolgt als das Empfangen von Daten (Lesen einiger Daten aus der Datenbank).


Es gibt auch Nuancen bei der Statuswiederherstellung: Beispielsweise kommt onSaveInstanceState manchmal nach onDestroyView. Darüber hinaus sollte berücksichtigt werden, dass wenn null in onViewStateRestored eingeht, dies bedeutet, dass Sie nichts wiederherstellen müssen, aber nicht auf den Standardstatus zurückgesetzt werden müssen.


Wenn wir über die Bequemlichkeit zwischen Hinzufügen und Ersetzen sprechen, ist das Ersetzen im Speicher wirtschaftlicher, wenn Sie über eine tiefe Navigation verfügen (wir haben eine Benutzer-Navigationstiefe für einen der Produkt-KPIs). Es ist auch viel bequemer, die Symbolleiste durch Ersetzen zu ersetzen, da Sie sie in onCreateView erneut fixieren können. Von den Vorteilen von add: weniger Probleme mit dem Lebenszyklus, wenn Sie zurückkehren, erstelle ich die Ansicht nicht neu und muss nichts neu füllen.


4. Manchmal müssen Sie immer noch direkt mit Diensten und sogar mit Bindungsdiensten arbeiten. Die Aktivität interagiert mit einem dieser Dienste (nur eine Aktivität). Es stellt eine Verbindung zum Dienst her und überträgt Daten an diesen. Wenn Sie den Bildschirm drehen, wird unsere Aktivität zerstört und wir sind verpflichtet, von diesem Dienst abzuprallen. Wenn jedoch keine Verbindung besteht, wird der Dienst zerstört und nach dem Abbinden wird bind an einen völlig anderen Dienst gesendet. Wie stellen Sie sicher, dass der Dienst beim Einschalten weiterhin aktiv ist?


Lösung


Wenn Sie eine schöne Lösung kennen, schreiben Sie in die Kommentare. Es fällt nur etwas Ähnliches ein:


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

5. Kürzlich haben wir die Navigation in unserer Anwendung für einzelne Aktivitäten (unter Verwendung einer der verfügbaren Bibliotheken) überarbeitet. Früher war jeder Anwendungsbildschirm eine separate Aktivität, jetzt arbeitet die Navigation an Fragmenten. Das Problem der Rückkehr zur Aktivität in der Mitte des Stapels wurde durch Absichtsflags gelöst. Wie kann ich zu dem Fragment in der Mitte des Stapels zurückkehren?


Lösung


Ja, FragmentManager bietet keine sofort einsatzbereiten Lösungen. Cicerone macht etwas Ähnliches in sich selbst:


  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. Außerdem haben wir kürzlich eine so ineffiziente und komplexe Komponente wie ViewPager beseitigt, da die Logik der Interaktion sehr kompliziert ist und das Verhalten von Fragmenten in bestimmten Fällen nicht vorhersehbar ist. In einigen Fragmenten haben wir innere Fragmente verwendet. Was passiert, wenn Fragmente in RecycleView-Elementen verwendet werden?


Lösung


Im Allgemeinen wird nichts falsch sein. Das Fragment wird problemlos hinzugefügt und angezeigt. Das einzige, mit dem wir konfrontiert sind, sind Inkonsistenzen mit dem Lebenszyklus. Die Implementierung auf dem ViewPager verwaltet den Lebenszyklus der Fragmente über setUserVisibleHint, und die RecycleView erledigt alles auf der Stirn, ohne über die tatsächliche Sichtbarkeit und Verfügbarkeit der Fragmente nachzudenken.


7. Aus dem gleichen Grund, aus dem wir von ViewPager gewechselt sind, ist das Problem der Wiederherstellung des Status aufgetreten. Bei Fragmenten wurde dies vom Framework implementiert: An den richtigen Stellen haben wir einfach onSaveInstanceState neu definiert und alle notwendigen Daten im Bundle gespeichert. Wenn Sie den ViewPager neu erstellen, wurden alle Fragmente vom FragmentManager wiederhergestellt und haben ihren Status zurückgegeben. Was tun mit RecycleView und seinem ViewHolder?


Lösung


„Man muss jedes Mal alles in die Datenbank schreiben und daraus lesen“, sagen Sie. Oder die Logik zum Speichern des Status sollte außerhalb liegen und die Liste ist nur eine Anzeige. In einer idealen Welt ist es. In unserem Fall ist jedes Element der Liste ein komplexer Bildschirm mit einer eigenen Logik. Daher musste ich mein Fahrrad im Stil „Lasst uns die gleiche Logik wie im ViewPager und im Fragment ausführen“ erfinden:


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

Bei Fragment.onSaveInstanceState lesen wir den Status der benötigten Inhaber und fügen sie in das Bundle ein. Beim erneuten Erstellen der Halter erhalten wir das gespeicherte Bundle und auf dem onBindViewHolder übergeben wir die gefundenen Zustände innerhalb der Halter:


8. Womit bedroht uns das?


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

Lösung


In der Tat ist daran nichts auszusetzen. In derselben RecycleView werden Listen von Elementen mit derselben ID gespeichert. Es gibt jedoch noch eine kleine 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; } 

Es sollte vorsichtig sein, wenn wir Elemente mit derselben ID in der Hierarchie haben, weil Es ist immer das erste gefundene Element, das immer zurückgegeben wird, und auf verschiedenen Ebenen des Aufrufs findViewById können es verschiedene Objekte sein.


9. Sie fallen von TooLargeTransaction ab, wenn Sie den Bildschirm drehen (ja, hier ist unser ViewPager immer noch indirekt schuld). Wie finde ich den Täter?


Lösung


Es ist ganz einfach: Hängen Sie ActivityLifecycleCallbacks an die Anwendung, fangen Sie alle onActivitySaveInstanceState ab und analysieren Sie alles, was im Bundle enthalten ist. Dort können Sie den Status aller Ansichten und Fragmente innerhalb dieser Aktivität abrufen.


Unten sehen Sie ein Beispiel, wie wir den Zustand von Fragmenten aus dem Bundle erhalten:


 /** * 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) } ... } 

Als nächstes berechnen wir einfach die Größe des Bundles und protokollieren es:


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

10. Angenommen, wir haben eine Aktivität und eine Reihe von Abhängigkeiten davon. Unter bestimmten Bedingungen müssen wir eine Reihe dieser Abhängigkeiten neu erstellen (z. B. durch Klicken auf ein bestimmtes Experiment mit einer anderen Benutzeroberfläche). Wie setzen wir das um?


Lösung


Natürlich können Sie mit Flaggen basteln und es durch den Start der Absicht zu einer Art „Krücke“ machen. Tatsächlich ist jedoch alles sehr einfach - Aktivität hat eine Wiederherstellungsmethode.


Höchstwahrscheinlich wird das meiste dieses Wissens für Sie nicht nützlich sein, da Sie nicht aus einem guten Leben zu jedem von ihnen kommen. Einige von ihnen zeigen jedoch gut, wie eine Person argumentieren und Lösungen vorschlagen kann. Wir verwenden ähnliche Fragen in Interviews. Wenn Sie interessante Aufgaben haben, die Sie in den Interviews lösen sollten, oder wenn Sie sie selbst festlegen, schreiben Sie sie in die Kommentare - es wird interessant sein, darüber zu diskutieren!

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


All Articles