Zoeken…


Veelvoorkomende geheugenlekken en hoe ze te verhelpen

1. Fix je contexten:

Probeer de juiste context te gebruiken: bijvoorbeeld omdat een Toast te zien is in veel activiteiten in plaats van in slechts één, gebruik getApplicationContext() voor toasts, en aangezien services kunnen blijven draaien, hoewel een activiteit is beëindigd, start een service met:

Intent myService = new Intent(getApplicationContext(), MyService.class);

Gebruik deze tabel als een snelle gids voor welke context geschikt is: voer hier de afbeeldingsbeschrijving in

Oorspronkelijk artikel over context hier .

2. Statische verwijzing naar context

Een ernstige geheugenlekfout is het behouden van een statische verwijzing naar View . Elke View heeft een innerlijke verwijzing naar de Context . Dat betekent dat een oude activiteit met de hele weergavehiërarchie niet zal worden verzameld voordat de app is beëindigd. Je hebt je app twee keer in het geheugen wanneer je het scherm draait.

Zorg ervoor dat er absoluut geen statische verwijzing is naar Beeld, Context of een van hun nakomelingen.

3. Controleer of u daadwerkelijk uw services afmaakt.

Ik heb bijvoorbeeld een intentService die de Google-locatieservice-API gebruikt. En ik vergat googleApiClient.disconnect(); te bellen googleApiClient.disconnect(); :

//Disconnect from API onDestroy()
if (googleApiClient.isConnected()) {
    LocationServices.FusedLocationApi.removeLocationUpdates(googleApiClient, GoogleLocationService.this);
    googleApiClient.disconnect();
}

4. Controleer het gebruik van afbeeldingen en bitmaps:

Als je de Picasso-bibliotheek van Square gebruikt, ontdekte ik dat ik geheugen lekte door de .fit() niet te gebruiken, waardoor mijn geheugenvoetafdruk drastisch werd gereduceerd van gemiddeld 50 MB tot minder dan 19 MB:

Picasso.with(ActivityExample.this)                   //Activity context
                .load(object.getImageUrl())           
                .fit()                                //This avoided the OutOfMemoryError
                .centerCrop()                         //makes image to not stretch
                .into(imageView);

5. Als u uitzendontvangers gebruikt, moet u ze afmelden.

6. Als u java.util.Observer (waarnemerspatroon) gebruikt:

Zorg ervoor dat u deleteObserver(observer);

Vermijd lekkende activiteiten met AsyncTask

Een waarschuwing : AsyncTask heeft veel gotcha's afgezien van het geheugenlek dat hier wordt beschreven. Dus wees voorzichtig met deze API, of vermijd het helemaal als u de implicaties niet volledig begrijpt. Er zijn veel alternatieven (Thread, EventBus, RxAndroid, enz.).

Een veel voorkomende fout bij AsyncTask is het vastleggen van een sterke verwijzing naar de host- Activity (of 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!
  }
}

Dit is een probleem omdat AsyncTask makkelijk de ouder kan overleven Activity , bijvoorbeeld wanneer een wijziging in de configuratie gebeurt terwijl de taak wordt uitgevoerd.

De juiste manier om dit te doen, is om van uw taak een static klasse te maken, die de ouder niet vasthoudt en een zwakke verwijzing naar de host- 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
    }
  }
}

Anoniem terugbellen in activiteiten

Elke keer dat u een anonieme klasse maakt, behoudt deze een impliciete verwijzing naar de bovenliggende klasse. Dus als je schrijft:

public class LeakyActivity extends Activity
{

...

    foo.registerCallback(new BarCallback() 
    {            
        @Override
        public void onBar() 
        {
            // do something                
        }            
    });
}

U stuurt in feite een verwijzing naar uw LeakyActivity-instantie naar foo. Wanneer de gebruiker weg van uw LeakyActivity navigeert, kan deze verwijzing voorkomen dat de LeakyActivity-instantie afval wordt verzameld. Dit is een ernstig lek omdat activiteiten een verwijzing bevatten naar hun gehele weergavehiërarchie en daarom vrij grote objecten in het geheugen zijn.

Hoe dit lek te voorkomen:

U kunt natuurlijk voorkomen dat u anonieme callbacks volledig in activiteiten gebruikt. U kunt ook de registratie van al uw callbacks met betrekking tot de activiteitenlevenscyclus ongedaan maken. zoals zo:

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

Activiteitscontext in statische klassen

Vaak wil je sommige van de Android-klassen verpakken in gemakkelijker te gebruiken hulpprogramma-klassen. Die hulpprogramma's vereisen vaak een context om toegang te krijgen tot het Android-besturingssysteem of de bronnen van uw apps. Een bekend voorbeeld hiervan is een wrapper voor de klasse SharedPreferences. Om toegang te krijgen tot de gedeelde voorkeuren van Androids moet men schrijven:

context.getSharedPreferences(prefsName, mode);

En dus kan men in de verleiding komen om de volgende klasse te maken:

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

nu, als u init() aanroept met uw activiteitscontext, zal de LeakySharedPrefsWrapper een verwijzing naar uw activiteit behouden, waardoor wordt voorkomen dat het afval wordt verzameld.

Hoe te vermijden:

Wanneer u statische helperfuncties aanroept, kunt u de toepassingscontext verzenden met context.getApplicationContext();

Wanneer u statische helperfuncties maakt, kunt u de toepassingscontext extraheren uit de context die u krijgt (aanroepen van getApplicationContext () in de toepassingscontext retourneert de toepassingscontext). Dus de oplossing voor onze wrapper is eenvoudig:

public static void init(Context context)
{
    sContext = context.getApplicationContext();
}

Als de toepassingscontext niet geschikt is voor uw use case, kunt u een Context-parameter opnemen in elke hulpprogramma-functie. Vermijd daarom verwijzingen naar deze contextparameters. In dit geval ziet de oplossing er als volgt uit:

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

Detecteer geheugenlekken met de LeakCanary-bibliotheek

LeakCanary is een Open Source Java-bibliotheek om geheugenlekken in uw debug-builds te detecteren.

Voeg gewoon de afhankelijkheden toe in de 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'
 }

Vervolgens in uw Application :

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

Nu zal LeakCanary automatisch een melding tonen wanneer een activiteitgeheugenlek wordt gedetecteerd in uw debug- build.

OPMERKING: Release-code bevat geen andere verwijzing naar LeakCanary dan de twee lege klassen die bestaan in de leakcanary-android-no-op .

Vermijd lekkende activiteiten met luisteraars

Als u een luisteraar in een activiteit implementeert of maakt, let dan altijd op de levenscyclus van het object waarop de luisteraar is geregistreerd.

Overweeg een toepassing waarbij we verschillende activiteiten / fragmenten hebben waarin geïnteresseerd is wanneer een gebruiker is ingelogd of uitgelogd. Een manier om dit te doen zou zijn om een enkele instantie van een UserController waarop kan worden geabonneerd om een melding te ontvangen wanneer de status van de gebruiker verandert:

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

Dan zijn er twee activiteiten, 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();
    }
}

En 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();
    }
}

Wat er met dit voorbeeld gebeurt, is dat elke keer dat de gebruiker inlogt en vervolgens weer MainActivity , een instantie MainActivity wordt gelekt. Het lek treedt op omdat er een verwijzing is naar de activiteit in UserController#listeners .

Let op: zelfs als we een anonieme binnenklasse als luisteraar gebruiken, zou de activiteit nog steeds lekken:

...
this.userController.registerUserStateChangeListener(new UserController.StateListener() {
    @Override
    public void userLoggedIn() {
        showUserAccount();
    }

    @Override
    public void userLoggedOut() {
        finish();
    }
});    
...

De activiteit zou nog steeds lekken, omdat de anonieme binnenklasse een impliciete verwijzing heeft naar de buitenklasse (in dit geval de activiteit). Daarom is het mogelijk om instantiemethoden in de buitenklasse vanuit de binnenklasse aan te roepen. In feite is het enige type binnenklassen dat geen verwijzing naar de buitenklasse heeft, statische binnenklassen.

Kortom, alle instanties van niet-statische binnenklassen bevatten een impliciete verwijzing naar de instantie van de buitenklasse die ze heeft gemaakt.

Er zijn twee WeakReference om dit op te lossen, hetzij door een methode toe te voegen om een luisteraar van UserController#listeners te verwijderen of door een WeakReference te gebruiken om de referentie van de luisteraars vast te houden.

Alternatief 1: luisteraars verwijderen

Laten we beginnen met het maken van een nieuwe methode removeUserStateChangeListener(StateListener listener) :

public class UserController {

    ...

    public void registerUserStateChangeListener(StateListener listener) {
        listeners.add(listener);
    }

    public void removeUserStateChangeListener(StateListener listener) {
        listeners.remove(listener);
    }

    ...
}

Laten we deze methode vervolgens aanroepen in de onDestroy methode van de activiteit:

public class MainActivity extends Activity implements UserController.StateListener{
    ...

    @Override
    protected void onDestroy() {
        super.onDestroy();
        userController.removeUserStateChangeListener(this);
    }
}

Met deze aanpassing worden de instanties van MainActivity niet meer gelekt wanneer de gebruiker in en uitlogt. Als de documentatie echter niet duidelijk is, is de kans groot dat de volgende ontwikkelaar die UserController begint te gebruiken, mist dat het nodig is om de luisteraar af te melden wanneer de activiteit wordt vernietigd, wat ons leidt tot de tweede methode om dit soort lekken te vermijden.

Alternatief 2: zwakke referenties gebruiken

Laten we eerst beginnen met uit te leggen wat een zwakke referentie is. Een zwakke verwijzing, zoals de naam al doet vermoeden, bevat een zwakke verwijzing naar een object. Vergeleken met een normaal instantieveld, dat een sterke referentie is, weerhoudt een zwakke referentie de vuilnisman, GC, er niet van de objecten te verwijderen. In het bovenstaande voorbeeld zou MainActivity vuilnis kunnen worden verzameld nadat het is vernietigd als de UserController WeakReference gebruikt als referentie voor de luisteraars.

Kort gezegd, een zwakke verwijzing vertelt de GC dat als niemand anders een sterke verwijzing naar dit object heeft, dit moet doorgaan en verwijderen.

Laten we de UserController aanpassen om een lijst van WeakReference te gebruiken om zijn luisteraars bij te houden:

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

Met deze aanpassing maakt het niet uit of de luisteraars al dan niet worden verwijderd, omdat UserController geen sterke verwijzingen naar een van de luisteraars bevat. Het is echter altijd lastig om deze standaardplaatcode te schrijven. Laten we daarom een generieke klasse genaamd 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);
        }
    }
}

Laten we nu UserController opnieuw schrijven om in plaats daarvan WeakCollection<T> gebruiken:

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

    ...
}

Zoals getoond in het bovenstaande codevoorbeeld, verwijdert de WeakCollection<T> alle boilerplate-code die nodig is om WeakReference te gebruiken WeakReference plaats van een normale lijst. Om het helemaal af te maken: Als een aanroep van UserController#removeUserStateChangeListener(StateListener) wordt gemist, zullen de luisteraar en alle objecten waarnaar wordt verwezen niet lekken.

Vermijd geheugenlekken met Anonymous Class, Handler, Timer Task, Thread


In Android gebruikt elke ontwikkelaar minstens één keer in een project Anonymous Class (uitvoerbaar). Elke Anonymous Class verwijst naar de ouder (activiteit). Als we een langlopende taak uitvoeren, wordt de bovenliggende activiteit niet vernietigd totdat de taak is beëindigd.
Voorbeeld gebruikt handler en klasse Anoniem Runnable . Het geheugen zal lekken als we de activiteit stoppen voordat de Runnable is afgelopen.

  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...

Hoe lossen we het op?

  1. Werk niet lang met Anonymous Class of we hebben er een Static class voor nodig en geven er WeakReference door (zoals activiteit, weergave ...). Thread is hetzelfde met Anonymous Class .
  2. Annuleer de Handler , Timer wanneer activiteit is vernietigd.


Modified text is an extract of the original Stack Overflow Documentation
Licentie onder CC BY-SA 3.0
Niet aangesloten bij Stack Overflow