2012-11-16 4 views
27

Ich habe gelesen, dass .setOnRetainInstance(true) auf Fragmenten Einstellung UI präsentieren zu Speicherlecks führen.Retained Fragmente mit UI und Speicherlecks

Könnte jemand bitte erklären, warum und wie dies geschehen würde? Ich habe nirgends eine detaillierte Erklärung gefunden.

+0

einfach das Thema zu dokumentieren, sind hier ähnliche Themen: http://stackoverflow.com/q/11182180/693752 – Snicolas

+0

http: // Stackoverflow. com/q/11160412/693752 – Snicolas

Antwort

75

In einem mit UI speichern Sie oft einige View s als Instanzstatus, um den Zugriff zu beschleunigen. Zum Beispiel ein Link zu Ihrem EditText so müssen Sie nicht findViewById es die ganze Zeit.

Das Problem ist, dass ein View einen Verweis auf den Activity Kontext behält. Wenn Sie nun eine View beibehalten, behalten Sie auch einen Verweis auf diesen Kontext.

Das ist kein Problem, wenn der Kontext nach wie vor gültig ist, aber der typische Fall beibehalten wird, um die Aktivität neu zu starten. Sehr oft für eine Bildschirmdrehung zum Beispiel. Activity Recreation wird einen neuen Kontext erstellen und alte Kontexte sollen als Garbage Collected dienen. Aber es kann jetzt nicht Müll gesammelt werden, da Ihr Fragment immer noch einen Verweis auf den alten hat.

Das folgende Beispiel zeigt, wie es nicht zu tun

public class LeakyFragment extends Fragment { 

    private View mLeak; // retained 

    @Override 
    public void onCreate(Bundle savedInstanceState) { 
     super.onCreate(savedInstanceState); 
     setRetainInstance(true); 
    } 

    @Override 
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 
     mLeak = inflater.inflate(R.layout.whatever, container, false); 
     return mLeak; 
    } 

    @Override 
    public void onDestroyView() { 
     super.onDestroyView(); 
     // not cleaning up. 
    } 
} 

Um loswerden dieses Problem zu umgehen, müssen Sie in onDestroyView alle Verweise auf die Benutzeroberfläche löschen. Sobald die Fragment Instanz wiederverwendet werden Sie eine neue Benutzeroberfläche auf onCreateView erstellen gefragt. Es hat auch keinen Sinn, die Benutzeroberfläche nach onDestroyView zu behalten. Die Ui wird nicht benutzt werden.

Das Update in diesem Beispiel gerade ist onDestroyView zu

@Override 
public void onDestroyView() { 
    super.onDestroyView(); 
    mLeak = null; // now cleaning up! 
} 

Wechsel Und außerdem Verweise auf View s zu halten, sollten Sie natürlich nicht Referenzen halten auf die Activity (zB von onAttach - sauber auf onDetach) oder jede Context (es sei denn, es ist der Application Kontext).

+0

Haben Sie irgendwelche Ideen, wie mit dem Berg der Null-Zeiger, die dies verursachen könnte, umzugehen? Ich habe mehrere Animationslistener, Threads und jeder verwendet eine der Referenzen, die in onDestroyView gelöscht wird. Wann immer ich eine dieser Referenzen verwende, muss ich zuerst nach Null suchen. Das ist sehr unpraktisch. – Tamas

+1

@Tamas Listeners, Threads, ... sind alle in Ordnung, um verwiesen zu bleiben, solange sie keinen Verweis auf irgendetwas enthalten, das die 'Activity' ist oder auf sie verweist. Wenn sie etwas haben, auf das verwiesen wird, und die Activity neu erstellt wird, führt das nicht zu etwas Gültigem, also müssen Sie es trotzdem aktualisieren. – zapl

+1

@Tamas Beispiel: http://pastebin.com/8A18kMym im Grunde müssen Sie 'onAttach' /' onDetach' auf alles, was sich auf den Kontext bezieht, und 'onCreateView' /' onDestroyView' auf alles propagieren, das Verweise auf Ansichten behält. – zapl

2

setRetainInstance(true) wird verwendet, um während einer Aktivität Erholung, wie eine Bildschirm-Rotation oder andere Konfigurationsänderungen Instanzen von dynamischen Fragmenten zu behalten. Dies bedeutet nicht, dass das Fragment für immer vom System beibehalten wird.

Wenn eine Aktivität aus anderen Gründen beenden, wie der Benutzer die Aktivität Finishing (das heißt Zurückdrücken), sollte das Fragment für die Garbage Collection in Betracht.

3

Seien Sie vorsichtig, wenn Sie bestimmte Objekte beibehalten, die an die Aktivität gekoppelt sind.

Vorsicht: Während Sie ein beliebiges Objekt zurückgeben können, sollten Sie nie ein Objekt übergeben, die die Aktivität gebunden ist, wie ein Drawable, einen Adapter, eine Ansicht oder anderes Objekt, das verknüpft ist mit einem Kontext. Wenn Sie dies tun, werden alle Ansichten und Ressourcen der ursprünglichen Aktivitätsinstanz verloren gehen. (Undichte Ressourcen bedeuten, dass Ihre Anwendung sie fest im Griff hat und sie nicht durch Müll gesammelt werden können, so dass viel Speicher verloren gehen kann.

)

http://developer.android.com/guide/topics/resources/runtime-changes.html#RetainingAnObject

-7

Sie können onDestroy() overide und Garbage Collector aufrufen.

@Override 
public void onDestroy() { 
    super.onDestroy(); 
    System.gc(); 
    System.gc(); 
} 
+3

20% Chance, dass es jemals funktioniert: P 2x 'System.gc()' für die Sicherheit? Verlassen Sie sich niemals auf 'gc()' –

0

Die "setRetainInstance" wird verwendet, um den Status des Fragments beizubehalten, wenn die Aktivität neu erstellt wird. Laut offizieller Dokumentation: Wenn wir "setRetainInstance" verwenden, werden 2 Methoden des Lebenszyklus des Fragments nicht ausgeführt (onCreate, onDestroy). Die im Fragment enthaltenen Ansichten werden jedoch neu erstellt, da der Lebenszyklus von "onCreateView" ausgeführt wird. In diesen Fällen, wenn wir einige Daten in "onSaveInstanceState" gespeichert haben, sollten wir in "onActivityCreated" statt in "onCreate" danach fragen.

Oficial Info: https://developer.android.com/reference/android/app/Fragment.html#setRetainInstance(boolean)

Weitere Informationen: https://inthecheesefactory.com/blog/fragment-state-saving-best-practices/en