Wenn ein Fragment ersetzt und in den Backstack gelegt (oder entfernt) wird, bleibt es im Speicher?

72

Entspricht das Verhalten der Funktionsweise von Aktivitäten? Zum Beispiel funktioniert es bei Aktivitäten folgendermaßen:

Aktivität A startet Aktivität B , während B auf dem Bildschirm angezeigt wird, kann das System A aus dem Speicher entfernen, wenn dies vom System benötigt wird. Wenn Sie auf ZURÜCK drücken, wird A im Speicher neu erstellt, als ob es überhaupt nicht verlassen worden wäre.

Ich habe nach einer klaren Erklärung gesucht, was mit Fragmenten in Bezug auf das Gedächtnis passiert, und nichts gefunden. Funktioniert es genauso? Zum Beispiel:

Aktivität C enthält Fragment F im Layout. Dann wird F irgendwann durch Fragment G ersetzt , aber F wird in seinem hinteren Stapel gehalten.

Will F Aufenthalt im Speicher , bis der C getötet wird oder kann sie vom System entfernt werden , je nach Bedarf?

Ich frage mich wirklich, ob ich das Risiko habe, dass mir der Speicher ausgeht, wenn ich einen Stapel komplizierter Fragmente in einer einzelnen Aktivität habe.

CottonBallPaws
quelle
6
Beachten Sie, dass Ihre erste Annahme falsch ist: Die Aktivität "A" wird niemals aus dem Speicher entfernt. Siehe commonsware.com/blog/2011/10/03/…
Thierry-Dimitri Roy
1
@ Thierry-DimitriRoy, unglaublich ... Ich fühle mich wie ich eine Lüge gelebt habe ... Ich glaube es fast nicht ...
CottonBallPaws
Das System beendet keine einzelne Aktivität oder kein Fragment. Es kann nur den gesamten Prozess abbrechen, um Speicher zurückzugewinnen, wenn der Speicherplatz knapp wird.
stdout

Antworten:

103

Schauen Sie sich das an: BackStackRecord.Op.fragment

So werden Fragmente im Backstack gespeichert. Notieren Sie sich die Live - Referenz, weder WeakReferencenoch SoftReferencewerden dort verwendet.

Nun dies: FragmentManagerImpl.mBackStack

Dort speichert der Manager den Backstack. Einfach ArrayList, auch keine WRs oder SRs.

Und zum Schluss: Activity.mFragments

Das ist der Verweis auf den Fragmentmanager.

GC kann nur Objekte sammeln, die keine Live-Referenzen haben (von keinem Thread aus erreichbar). Das heißt, bis Ihre Aktivität zerstört ist (und somit die FragmentManagerReferenz weg ist), kann GC keine der Fragmente im hinteren Stapel sammeln .

Beachten Sie, dass bei Zerstörung der Aktivität und Beibehaltung des Status (z. B. wenn Sie das Gerät in den Querformatmodus versetzen) keine tatsächlichen FragmentObjekte im Stapel beibehalten werden, sondern nur deren Status - Fragment.FragmentState- Objekte, dh tatsächliche Fragmente im hinteren Stapel -Erstellt jedes Mal, wenn eine Aktivität mit beibehaltenem Status neu erstellt wird.

Hoffe das hilft.

PS Kurz gesagt: Ja, Sie können nicht genügend Speicher haben, indem Sie Fragmentsdem Backstack hinzufügen und zu viele Ansichten hinzufügen, um die Hierarchie anzuzeigen.

UPD In Ihrem Beispiel bleibt F im Speicher, bis C getötet wird. Wenn C getötet und dann mit einer anderen Konfiguration wiederbelebt wird, wird F auch in einem anderen Objekt zerstört und wiedergeboren. Der Speicherbedarf von F bleibt also bestehen, bis C den Status verliert oder der Rückstapel gelöscht wird.

Ivan Bartsov
quelle
30
Beachten Sie jedoch, dass beim Ablegen eines Fragments auf dem Backstack dessen onDestroyView () aufgerufen wird. Wenn Sie zu diesem Zeitpunkt alle wichtigen Zuordnungen bereinigen, die vom Fragment gehalten werden, sollten Sie keine Speicherprobleme haben.
Hackbod
1
Ja, fester Punkt. Fragmentobjekte nehmen immer noch eine gewisse Menge an Mem ein, und genau genommen kann man den Speicher immer noch mit einer großen Menge an Fragmenten überfluten, selbst wenn die Bereinigungslogik vorhanden ist, aber Sie haben definitiv Recht - wenn es richtig bereinigt wird, unter realen Bedingungen ( dh wenn die Anzahl der Fragmente sinnvoll ist, sollten Backstack-Fragmente keinen erheblichen Speicheraufwand verursachen.
Ivan Bartsov
1
@hackbod Was meinst du damit major allocations?
Theblang
@mattblang Wenn ich darf, meinte Dianne höchstwahrscheinlich alles, was speicherintensiv ist und nicht von der Ansichtshierarchie des Fragments verwaltet wird. Das bemerkenswerteste Beispiel ist Bitmaps. Wenn Ihr Fragment ein paar Grundelemente oder Durchschnittsgrößen enthält String, ist das wahrscheinlich in Ordnung. Entsorgen Sie jedoch mehrere Instanzen, die Sie über die Prozessspeicherobergrenze bringen würden.
Ivan Bartsov
3
@ Traninho Gute Vorstellung. Wenn eine FragmentInstanz in den Backstack gestellt wird, wird sie nicht zerstört. Daher onDestroyView()sollten alle Ansichtsreferenzen, über die sie verfügt , immer auf Null gesetzt werden. Andernfalls wird verhindert, dass die toten Ansichten GCed werden. Nicht-Backstack-Fragmente (siehe setRetainInstance(true)) werden auch dann nicht zerstört, wenn sie Activityneu erstellt werden. Wenn Sie also Ansichtsreferenzen beibehalten , können Sie ein Ganzes verlieren Activity. Normalerweise geschieht dies jedoch nicht, da in 99% der Fälle die Ansichtsreferenzen aktualisiert werden, in onCreateView()denen die Actvity-Neuerstellung aufgerufen wird. Danach verweisen die Referenzen trotzdem auf neue Ansichten.
Ivan Bartsov
7

Es tut mir leid, dass ich Ihnen keine offizielle Informationsquelle zur Verfügung stellen konnte, aber ich war auch neugierig zu sehen, was passieren würde, und habe beschlossen, es zu testen. Und nach meinen Tests laufen Sie Gefahr, dass Ihnen der Speicher ausgeht.

Ich musste eine unglaubliche Menge an Fragmenten (mehr als einhundert) in eine for-Schleife OutOfMemoryErroreinfügen, damit dies geschah, aber es geschah. Beim Überprüfen meiner Protokolle konnte ich feststellen, dass die onCreate()und onCreateView()-Methoden häufig aufgerufen onSaveInstance()wurden onPause()und onDestroyüberhaupt nicht aufgerufen wurden.

Als Referenz habe ich die Fragmente wie folgt zum Backstack hinzugefügt:

getSupportFragmentManager().beginTransaction().add(R.id.scene_fragment_container, mSceneFragment).addToBackStack("FOOBAR").commit();

Und die Fragmente, die ich hinzufügte, waren etwas einfach: ein ImageView, ein EditText, ein Paar TextViews, ein SeekBarund ein ListView.

Aber es sei denn, Sie haben eine große Datenmenge im Speicher, sollte dies kein Problem sein.

Später habe ich versucht, nur 50 zum Backstack hinzuzufügen, die App zu beenden und neu zu starten. Und wie ich gehofft / erraten habe, wurden alle Fragmente wiederhergestellt (und die Methoden onSaveInstance()und onPause()aufgerufen), sodass meine Lebenszyklusimplementierung nicht das Problem war, das den OutOfMemoryErrorBrand verursachte.

Beowulf Björnson
quelle
4

Von developer.android.com/guide/topics/fundamentals/fragments.html

Ein Fragment muss immer in eine Aktivität eingebettet sein, und der Lebenszyklus des Fragments wird direkt vom Lebenszyklus der Hostaktivität beeinflusst. Wenn zum Beispiel die Aktivität angehalten wird, sind alle Fragmente darin, und wenn die Aktivität zerstört wird, sind auch alle Fragmente darin. Während eine Aktivität ausgeführt wird (sie befindet sich im wieder aufgenommenen Lebenszykluszustand), können Sie jedes Fragment unabhängig voneinander bearbeiten, z. B. hinzufügen oder entfernen. Wenn Sie eine solche Fragmenttransaktion ausführen, können Sie sie auch einem von der Aktivität verwalteten Backstack hinzufügen. Jeder Backstack-Eintrag in der Aktivität ist eine Aufzeichnung der aufgetretenen Fragmenttransaktion. Mit dem Backstack kann der Benutzer eine Fragmenttransaktion umkehren (rückwärts navigieren), indem er die Taste BACK drückt.

Bill Gary
quelle
Danke Bill. Das sagt jedoch immer noch nicht auf die eine oder andere Weise aus, ob die Fragmente im
Backstack
@littleFluffyKitty warum beantwortet das nicht deine Frage? Offensichtlich sind sie im Gedächtnis, aber wie eine normale Aktivität angehalten, werden sie erst bereinigt, wenn Sie zurückkehren oder Ihre Aktivität zerstört wird.
Warpzit
1

Ja, Ihnen kann der Speicherplatz ausgehen und zu viele Fragmente in einer einzelnen Aktivität erstellen. Die Fragmente werden nur zerstört, wenn die enthaltende Aktivität ist.

Sid
quelle