Android
Speicherlecks
Suche…
Häufige Speicherlecks und deren Behebung
1. Korrigieren Sie Ihre Kontexte:
Versuchen Sie es mit dem entsprechenden Kontext: Zum Beispiel, da ein Toast in vielen Aktivitäten statt in nur einer getApplicationContext()
, verwenden Sie getApplicationContext()
für Toast. Da Dienste weiterhin ausgeführt werden können, obwohl eine Aktivität beendet ist, starten Sie einen Dienst mit:
Intent myService = new Intent(getApplicationContext(), MyService.class);
Verwenden Sie diese Tabelle als Kurzanleitung für den geeigneten Kontext:
Originalartikel zum Kontext hier .
2. Statischer Verweis auf Kontext
Ein schwerwiegender Speicherleckfehler besteht darin, einen statischen Verweis auf View
behalten. Jede View
hat einen inneren Bezug zum Context
. Dies bedeutet, dass eine alte Aktivität mit ihrer gesamten Ansichtshierarchie erst dann gesammelt wird, wenn die App beendet wird. Sie haben Ihre App zweimal im Speicher, wenn Sie den Bildschirm drehen.
Stellen Sie sicher, dass es absolut keine statischen Verweise auf Ansicht, Kontext oder deren Nachkommen gibt.
3. Prüfen Sie, ob Sie Ihre Dienste tatsächlich abschließen.
Ich habe zum Beispiel einen intentService, der die Google Location Service-API verwendet. Und ich habe vergessen, googleApiClient.disconnect();
:
//Disconnect from API onDestroy()
if (googleApiClient.isConnected()) {
LocationServices.FusedLocationApi.removeLocationUpdates(googleApiClient, GoogleLocationService.this);
googleApiClient.disconnect();
}
4. Überprüfen Sie die Verwendung von Bildern und Bitmaps:
Wenn Sie die Picasso - Bibliothek von Square verwenden, habe ich festgestellt, dass mir Speicherplatz verloren ging, weil ich nicht .fit()
verwendet habe. .fit()
mein Speicherbedarf von 50 MB im Durchschnitt drastisch auf weniger als 19 MB reduziert.
Picasso.with(ActivityExample.this) //Activity context
.load(object.getImageUrl())
.fit() //This avoided the OutOfMemoryError
.centerCrop() //makes image to not stretch
.into(imageView);
5. Wenn Sie Broadcast-Receiver verwenden, heben Sie die Registrierung auf.
6. Wenn Sie java.util.Observer
(Observer-Muster) verwenden:
deleteObserver(observer);
Sie sicher, dass Sie deleteObserver(observer);
Vermeiden Sie undichte Aktivitäten mit AsyncTask
Ein Wort der Vorsicht : Bei AsyncTask gibt es neben dem hier beschriebenen Speicherverlust viele Gotchas. Seien Sie also vorsichtig mit dieser API oder vermeiden Sie sie ganz, wenn Sie die Auswirkungen nicht vollständig verstehen. Es gibt viele Alternativen (Thread, EventBus, RxAndroid usw.).
Ein häufiger Fehler mit AsyncTask
ist eine starke Bezugnahme auf den Host zu erfassen Activity
(oder Fragment
):
class MyActivity extends Activity {
private AsyncTask<Void, Void, Void> myTask = new AsyncTask<Void, Void, Void>() {
// Don't do this! Inner classes implicitly keep a pointer to their
// parent, which in this case is the Activity!
}
}
Dies ist ein Problem, da AsyncTask
die übergeordnete Activity
leicht AsyncTask
kann, z. B. wenn während der Task eine Konfigurationsänderung vorgenommen wird.
Der richtige Weg, dies zu tun, ist, Ihre Aufgabe zu einer static
Klasse zu machen, die das übergeordnete Element nicht erfasst und einen schwachen Verweis auf die Activity
class MyActivity extends Activity {
static class MyTask extends AsyncTask<Void, Void, Void> {
// Weak references will still allow the Activity to be garbage-collected
private final WeakReference<MyActivity> weakActivity;
MyTask(MyActivity myActivity) {
this.weakActivity = new WeakReference<>(myActivity);
}
@Override
public Void doInBackground(Void... params) {
// do async stuff here
}
@Override
public void onPostExecute(Void result) {
// Re-acquire a strong reference to the activity, and verify
// that it still exists and is active.
MyActivity activity = weakActivity.get();
if (activity == null
|| activity.isFinishing()
|| activity.isDestroyed()) {
// activity is no longer valid, don't do anything!
return;
}
// The activity is still valid, do main-thread stuff here
}
}
}
Anonymer Rückruf bei Aktivitäten
Jedes Mal, wenn Sie eine anonyme Klasse erstellen, behält sie einen impliziten Verweis auf die übergeordnete Klasse bei. Also wenn du schreibst:
public class LeakyActivity extends Activity
{
...
foo.registerCallback(new BarCallback()
{
@Override
public void onBar()
{
// do something
}
});
}
Sie senden tatsächlich einen Verweis auf Ihre LeakyActivity-Instanz an foo. Wenn der Benutzer von Ihrer LeakyActivity weg navigiert, kann diese Referenz verhindern, dass die LeakyActivity-Instanz Müll gesammelt wird. Dies ist ein schwerwiegendes Leck, da Aktivitäten auf ihre gesamte Ansichtshierarchie verweisen und daher recht große Objekte im Speicher sind.
So vermeiden Sie dieses Leck:
Natürlich können Sie anonyme Rückrufe in Aktivitäten vollständig vermeiden. Sie können auch alle Ihre Rückrufe in Bezug auf den Aktivitätslebenszyklus aufheben. wie so:
public class NonLeakyActivity extends Activity
{
private final BarCallback mBarCallback = new BarCallback()
{
@Override
public void onBar()
{
// do something
}
});
@Override
protected void onResume()
{
super.onResume();
foo.registerCallback(mBarCallback);
}
@Override
protected void onPause()
{
super.onPause();
foo.unregisterCallback(mBarCallback);
}
}
Aktivitätskontext in statischen Klassen
Häufig möchten Sie einige Android-Klassen in einfachere Dienstprogrammklassen einbetten. Diese Dienstprogrammklassen benötigen häufig einen Kontext, um auf das Android-Betriebssystem oder die Ressourcen Ihrer Apps zuzugreifen. Ein typisches Beispiel hierfür ist ein Wrapper für die SharedPreferences-Klasse. Um auf die gemeinsamen Einstellungen von Androids zuzugreifen, muss man schreiben:
context.getSharedPreferences(prefsName, mode);
Und so kann man versucht sein, die folgende Klasse zu erstellen:
public class LeakySharedPrefsWrapper
{
private static Context sContext;
public static void init(Context context)
{
sContext = context;
}
public int getInt(String name,int defValue)
{
return sContext.getSharedPreferences("a name", Context.MODE_PRIVATE).getInt(name,defValue);
}
}
Wenn Sie jetzt init()
mit Ihrem Aktivitätskontext aufrufen, behält der LeakySharedPrefsWrapper einen Verweis auf Ihre Aktivität bei und verhindert, dass Müll gesammelt wird.
Wie zu vermeiden:
Wenn Sie statische context.getApplicationContext();
aufrufen, können Sie den Kontext der Anwendung mithilfe von context.getApplicationContext();
senden context.getApplicationContext();
Beim Erstellen statischer Hilfsfunktionen können Sie den Anwendungskontext aus dem Kontext extrahieren, den Sie erhalten (Aufruf von getApplicationContext () im Anwendungskontext gibt den Anwendungskontext zurück). Die Korrektur für unseren Wrapper ist also einfach:
public static void init(Context context)
{
sContext = context.getApplicationContext();
}
Wenn der Anwendungskontext nicht für Ihren Anwendungsfall geeignet ist, können Sie in jede Dienstprogrammfunktion einen Context-Parameter einschließen. Vermeiden Sie es, Verweise auf diese Kontextparameter beizubehalten. In diesem Fall würde die Lösung so aussehen:
public int getInt(Context context,String name,int defValue)
{
// do not keep a reference of context to avoid potential leaks.
return context.getSharedPreferences("a name", Context.MODE_PRIVATE).getInt(name,defValue);
}
Erkennen Sie Speicherverluste mit der LeakCanary-Bibliothek
LeakCanary ist eine Open Source Java-Bibliothek zur Erkennung von Speicherverlusten in Ihren Debug-Builds.
build.gradle
einfach die Abhängigkeiten im build.gradle
:
dependencies {
debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5.1'
releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.1'
testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.1'
}
Dann in Ihrer Application
Klasse:
public class ExampleApplication extends Application {
@Override public void onCreate() {
super.onCreate();
if (LeakCanary.isInAnalyzerProcess(this)) {
// This process is dedicated to LeakCanary for heap analysis.
// You should not init your app in this process.
return;
}
LeakCanary.install(this);
}
}
Jetzt zeigt LeakCanary automatisch eine Benachrichtigung an, wenn in Ihrem Debug- Build ein Aktivitätsspeicherleck entdeckt wird.
HINWEIS: Der Versionscode enthält keinen anderen Verweis auf LeakCanary als die beiden leeren Klassen, die in der leakcanary-android-no-op
sind.
Vermeiden Sie undichte Aktivitäten mit Zuhörern
Wenn Sie einen Listener in einer Aktivität implementieren oder erstellen, achten Sie immer auf den Lebenszyklus des Objekts, für das der Listener registriert ist.
Stellen Sie sich eine Anwendung vor, bei der wir verschiedene Aktivitäten / Fragmente haben, die daran interessiert sind, wann ein Benutzer an- oder abgemeldet ist. Eine Möglichkeit, dies zu tun, besteht darin, über eine Einzelinstanz eines UserController
zu verfügen, die abonniert werden kann, um benachrichtigt zu werden, wenn sich der Status des Benutzers ändert:
public class UserController {
private static UserController instance;
private List<StateListener> listeners;
public static synchronized UserController getInstance() {
if (instance == null) {
instance = new UserController();
}
return instance;
}
private UserController() {
// Init
}
public void registerUserStateChangeListener(StateListener listener) {
listeners.add(listener);
}
public void logout() {
for (StateListener listener : listeners) {
listener.userLoggedOut();
}
}
public void login() {
for (StateListener listener : listeners) {
listener.userLoggedIn();
}
}
public interface StateListener {
void userLoggedIn();
void userLoggedOut();
}
}
Dann gibt es zwei Aktivitäten, SignInActivity
:
public class SignInActivity extends Activity implements UserController.StateListener{
UserController userController;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.userController = UserController.getInstance();
this.userController.registerUserStateChangeListener(this);
}
@Override
public void userLoggedIn() {
startMainActivity();
}
@Override
public void userLoggedOut() {
showLoginForm();
}
...
public void onLoginClicked(View v) {
userController.login();
}
}
Und MainActivity
:
public class MainActivity extends Activity implements UserController.StateListener{
UserController userController;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.userController = UserController.getInstance();
this.userController.registerUserStateChangeListener(this);
}
@Override
public void userLoggedIn() {
showUserAccount();
}
@Override
public void userLoggedOut() {
finish();
}
...
public void onLogoutClicked(View v) {
userController.logout();
}
}
In diesem Beispiel passiert MainActivity
: Jedes Mal, wenn sich der Benutzer MainActivity
und abmeldet, tritt eine MainActivity
Instanz aus. Das Leck tritt auf, weil es einen Verweis auf die Aktivität in UserController#listeners
.
Bitte beachten Sie: Selbst wenn wir eine anonyme innere Klasse als Zuhörer verwenden, leckt die Aktivität immer noch:
...
this.userController.registerUserStateChangeListener(new UserController.StateListener() {
@Override
public void userLoggedIn() {
showUserAccount();
}
@Override
public void userLoggedOut() {
finish();
}
});
...
Die Aktivität würde immer noch auslaufen, da die anonyme innere Klasse implizit auf die äußere Klasse verweist (in diesem Fall die Aktivität). Aus diesem Grund ist es möglich, Instanzmethoden aus der inneren Klasse in der äußeren Klasse aufzurufen. Tatsächlich sind die einzigen inneren Klassen, die keinen Bezug zur äußeren Klasse haben, statische innere Klassen.
Kurz gesagt, alle Instanzen nicht statischer innerer Klassen enthalten einen impliziten Verweis auf die Instanz der äußeren Klasse, die sie erstellt hat.
Es gibt zwei Hauptansätze, um dieses WeakReference
zu lösen: entweder durch Hinzufügen einer Methode zum Entfernen eines Listeners aus UserController#listeners
oder durch Verwendung einer WeakReference
, um die Referenz der Listener zu WeakReference
.
Alternative 1: Zuhörer entfernen
Beginnen wir mit dem Erstellen einer neuen Methode removeUserStateChangeListener(StateListener listener)
:
public class UserController {
...
public void registerUserStateChangeListener(StateListener listener) {
listeners.add(listener);
}
public void removeUserStateChangeListener(StateListener listener) {
listeners.remove(listener);
}
...
}
Dann rufen wir diese Methode in der onDestroy
Methode der Aktivität auf:
public class MainActivity extends Activity implements UserController.StateListener{
...
@Override
protected void onDestroy() {
super.onDestroy();
userController.removeUserStateChangeListener(this);
}
}
Durch diese Änderung werden die Instanzen von MainActivity
nicht mehr durchgesickert, wenn sich der Benutzer MainActivity
und abmeldet. Wenn die Dokumentation jedoch nicht eindeutig ist, besteht die Gefahr, dass der nächste Entwickler, der UserController
möglicherweise nicht mehr weiß, dass es erforderlich ist, die Registrierung des Listeners aufzuheben, wenn die Aktivität zerstört wird. Dies führt uns zur zweiten Methode, um diese Art von Lecks zu vermeiden.
Alternative 2: Verwendung schwacher Referenzen
Lassen Sie uns zunächst erklären, was eine schwache Referenz ist. Eine schwache Referenz enthält, wie der Name schon sagt, eine schwache Referenz auf ein Objekt. Verglichen mit einem normalen Instanzfeld, das eine starke Referenz ist, hindern schwache Referenzen den Garbage Collector (GC) nicht daran, die Objekte zu entfernen. In dem obigen Beispiel würde dies ermöglichen, dass MainActivity
nach der MainActivity
von MainActivity
gesammelt wird, wenn der UserController
WeakReference
für die Referenz der Listener verwendet.
Kurz gesagt, eine schwache Referenz sagt dem GC, dass, wenn niemand anderes eine starke Referenz auf dieses Objekt hat, es fortfahren und es entfernen kann.
Lassen Sie uns UserController
ändern, dass eine Liste von WeakReference
, um die Listener zu WeakReference
:
public class UserController {
...
private List<WeakReference<StateListener>> listeners;
...
public void registerUserStateChangeListener(StateListener listener) {
listeners.add(new WeakReference<>(listener));
}
public void removeUserStateChangeListener(StateListener listenerToRemove) {
WeakReference referencesToRemove = null;
for (WeakReference<StateListener> listenerRef : listeners) {
StateListener listener = listenerRef.get();
if (listener != null && listener == listenerToRemove) {
referencesToRemove = listenerRef;
break;
}
}
listeners.remove(referencesToRemove);
}
public void logout() {
List referencesToRemove = new LinkedList();
for (WeakReference<StateListener> listenerRef : listeners) {
StateListener listener = listenerRef.get();
if (listener != null) {
listener.userLoggedOut();
} else {
referencesToRemove.add(listenerRef);
}
}
}
public void login() {
List referencesToRemove = new LinkedList();
for (WeakReference<StateListener> listenerRef : listeners) {
StateListener listener = listenerRef.get();
if (listener != null) {
listener.userLoggedIn();
} else {
referencesToRemove.add(listenerRef);
}
}
}
...
}
Bei dieser Änderung spielt es keine Rolle, ob die Listener entfernt werden oder nicht, da UserController
keine starken Verweise auf die Listener enthält. Es ist jedoch umständlich, diesen Boilerplate-Code jedes Mal zu schreiben. Erstellen wir daher eine generische Klasse namens WeakCollection
:
public class WeakCollection<T> {
private LinkedList<WeakReference<T>> list;
public WeakCollection() {
this.list = new LinkedList<>();
}
public void put(T item){
//Make sure that we don't re add an item if we already have the reference.
List<T> currentList = get();
for(T oldItem : currentList){
if(item == oldItem){
return;
}
}
list.add(new WeakReference<T>(item));
}
public List<T> get() {
List<T> ret = new ArrayList<>(list.size());
List<WeakReference<T>> itemsToRemove = new LinkedList<>();
for (WeakReference<T> ref : list) {
T item = ref.get();
if (item == null) {
itemsToRemove.add(ref);
} else {
ret.add(item);
}
}
for (WeakReference ref : itemsToRemove) {
this.list.remove(ref);
}
return ret;
}
public void remove(T listener) {
WeakReference<T> refToRemove = null;
for (WeakReference<T> ref : list) {
T item = ref.get();
if (item == listener) {
refToRemove = ref;
}
}
if(refToRemove != null){
list.remove(refToRemove);
}
}
}
Lassen Sie uns UserController
jetzt erneut schreiben, um stattdessen WeakCollection<T>
verwenden:
public class UserController {
...
private WeakCollection<StateListener> listenerRefs;
...
public void registerUserStateChangeListener(StateListener listener) {
listenerRefs.put(listener);
}
public void removeUserStateChangeListener(StateListener listenerToRemove) {
listenerRefs.remove(listenerToRemove);
}
public void logout() {
for (StateListener listener : listenerRefs.get()) {
listener.userLoggedOut();
}
}
public void login() {
for (StateListener listener : listenerRefs.get()) {
listener.userLoggedIn();
}
}
...
}
Wie im obigen WeakCollection<T>
gezeigt, entfernt die WeakCollection<T>
den gesamten Boilerplate-Code, der zur Verwendung von WeakReference
anstelle einer normalen Liste erforderlich ist. Um das UserController#removeUserStateChangeListener(StateListener)
: Wenn ein Aufruf an UserController#removeUserStateChangeListener(StateListener)
verpasst wird, gehen der Listener und alle darauf verweisenden Objekte nicht verloren.
Vermeiden Sie Speicherverluste mit Anonymous Class, Handler, Timer Task, Thread
In Android verwendet jeder Entwickler mindestens einmal in einem Projekt die Anonymous Class
(lauffähig). Jede Anonymous Class
hat einen Verweis auf ihr übergeordnetes Element (Aktivität). Wenn Sie eine lang andauernde Aufgabe ausführen, wird die übergeordnete Aktivität erst zerstört, wenn die Aufgabe beendet ist.
Beispiel verwendet den Handler und die anonyme Runnable
Klasse. Der Speicher ist undicht, wenn wir die Aktivität beenden, bevor Runnable
beendet ist.
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
// do abc long 5s or so
}
}, 10000); // run "do abc" after 10s. It same as timer, thread...
Wie lösen wir es?
- Machen Sie keine langen Operationen mit
Anonymous Class
oder wir benötigen eineStatic class
und übergeben SieWeakReference
(wie Aktivität, Ansicht ...).Thread
ist mit derAnonymous Class
identisch. - Brechen Sie den
Handler
,Timer
wenn die Aktivität zerstört wird.