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: Geben Sie hier die Bildbeschreibung ein

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?

  1. Machen Sie keine langen Operationen mit Anonymous Class oder wir benötigen eine Static class und übergeben Sie WeakReference (wie Aktivität, Ansicht ...). Thread ist mit der Anonymous Class identisch.
  2. Brechen Sie den Handler , Timer wenn die Aktivität zerstört wird.


Modified text is an extract of the original Stack Overflow Documentation
Lizenziert unter CC BY-SA 3.0
Nicht angeschlossen an Stack Overflow