Sök…


Vanliga minnesläckor och hur man fixar dem

1. Fixa dina sammanhang:

Försök använda lämpligt sammanhang: till exempel eftersom en rostat bröd kan ses i många aktiviteter istället för i bara en, använd getApplicationContext() för rostat bröd, och eftersom tjänster kan fortsätta köras även om en aktivitet har avslutats startar en tjänst med:

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

Använd den här tabellen som en snabbguide för vilket sammanhang som är lämpligt: ange bildbeskrivning här

Originalartikel om sammanhang här .

2. Statisk hänvisning till kontext

Ett allvarligt fel i minnesläckan är att hålla en statisk referens till View . Varje View har en inre hänvisning till Context . Vilket innebär att en gammal aktivitet med hela visningshierarkin inte kommer att samlas in förrän appen avslutas. Du kommer att ha din app två gånger i minnet när du roterar skärmen.

Se till att det absolut inte finns någon statisk referens till Visa, kontext eller någon av deras ättlingar.

3. Kontrollera att du faktiskt är klar med dina tjänster.

Till exempel har jag en intentionService som använder Googles API för API-tjänster. Och jag glömde att ringa googleApiClient.disconnect(); :

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

4. Kontrollera bild- och bitmappsanvändning:

Om du använder Square's bibliotek Picasso hittade jag att jag läckte minne genom att inte använda .fit() , vilket drastiskt minskade mitt minnesfotavtryck från 50 MB i genomsnitt till mindre än 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. Om du använder sändningsmottagare avregistrerar du dem.

6. Om du använder java.util.Observer (Observermönster):

Se till att använda deleteObserver(observer);

Undvik att läcka aktiviteter med AsyncTask

Ett försiktighetsord : AsyncTask har många gotcha förutom minnesläckan som beskrivs här. Så var försiktig med detta API, eller undvik det helt om du inte helt förstår implikationerna. Det finns många alternativ (tråd, EventBus, RxAndroid, etc.).

Ett vanligt misstag med AsyncTask är att fånga en stark referens till Activity (eller 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!
  }
}

Detta är ett problem eftersom AsyncTask enkelt kan överleva överordnad Activity , till exempel om en konfigurationsändring inträffar medan uppgiften körs.

Det rätta sättet att göra detta är att göra din uppgift till en static klass som inte fångar upp föräldern och har en svag referens till 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
    }
  }
}

Anonym återuppringning i aktiviteter

Varje gång du skapar en anonym klass behåller den en implicit referens till sin överordnade klass. Så när du skriver:

public class LeakyActivity extends Activity
{

...

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

Du skickar faktiskt en referens till din LeakyActivity-instans till foo. När användaren navigerar bort från din LeakyActivity kan denna referens förhindra att LeakyActivity-instansen samlas in. Detta är en allvarlig läcka eftersom aktiviteter hänvisar till hela visningshierarkin och därför är ganska stora objekt i minnet.

Hur man undviker denna läcka:

Du kan naturligtvis undvika att använda anonyma återuppringningar i aktiviteter helt. Du kan också avregistrera alla dina återuppringningar med avseende på aktivitetslivscykeln. såhär:

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

Aktivitetskontext i statiska klasser

Ofta vill du lasta in några av Android: s klasser i enklare att använda verktygsklasser. Dessa verktygsklasser kräver ofta ett sammanhang för att komma åt Android OS eller dina apps resurser. Ett vanligt exempel på detta är en omslag för klassen SharedPreferences. För att få tillgång till Androids delade preferenser måste man skriva:

context.getSharedPreferences(prefsName, mode);

Och så kan man frestas att skapa följande klass:

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, om du ringer init() med ditt aktivitetsförhållanden, kommer LeakySharedPrefsWrapper att behålla en referens till din aktivitet och förhindra att den skräppost samlas in.

Hur man undviker:

När du ringer statiska hjälparfunktioner kan du skicka in applikationens sammanhang med context.getApplicationContext();

När du skapar statiska hjälpfunktioner kan du extrahera applikationskonteksten från det sammanhang du får (Calling getApplicationContext () i applikationskonteksten returnerar applikationskonteksten). Så fixeringen på vår omslag är enkel:

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

Om applikationskonteksten inte är lämpligt för ditt användningsfall kan du inkludera en kontextparameter i varje verktygsfunktion. Du bör undvika att hålla referenser till dessa sammanhangsparametrar. I det här fallet ser lösningen så ut:

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

Upptäck minnesläckor med LeakCanary-biblioteket

LeakCanary är ett Java-bibliotek med öppen källkod för att upptäcka minnesläckor i dina felsökningar.

Lägg bara till beroenden i 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'
 }

Sedan i din 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 kommer LeakCanary automatiskt att visa ett meddelande när ett aktivitetsminnesläcka upptäcks i din felsökning .

OBS: Släppkoden innehåller ingen referens till LeakCanary utom de två tomma klasserna som finns i leakcanary-android-no-op beroendet.

Undvik att läcka aktiviteter med lyssnare

Om du implementerar eller skapar en lyssnare i en aktivitet, ska du alltid uppmärksamma livscykeln för objektet som har lyssnaren registrerat.

Överväg en applikation där vi har flera olika aktiviteter / fragment intresserade av när en användare är inloggad eller ut. Ett sätt att göra detta skulle vara att ha en singleton-instans av en UserController som kan prenumereras på för att bli meddelad när användarens tillstånd ändras:

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

Sedan finns det två aktiviteter, 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();
    }
}

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

Vad som händer med det här exemplet är att varje gång användaren loggar in och sedan loggar ut igen MainActivity en MainActivity instans ut. Läckan inträffar eftersom det finns en hänvisning till aktiviteten i UserController#listeners .

Observera: Även om vi använder en anonym innerklass som lyssnare skulle aktiviteten fortfarande läcka:

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

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

Aktiviteten skulle fortfarande läcka, eftersom den anonyma inre klassen har en implicit hänvisning till den yttre klassen (i detta fall aktiviteten). Det är därför det är möjligt att anropa instansmetoder i den yttre klassen från den inre klassen. Faktum är att den enda typen av inre klasser som inte har en hänvisning till den yttre klassen är statiska inre klasser.

Kort sagt, alla fall av icke-statiska inre klasser har en implicit hänvisning till förekomsten av den yttre klassen som skapade dem.

Det finns två huvudsakliga metoder för att lösa detta, antingen genom att lägga till en metod för att ta bort en lyssnare från UserController#listeners eller använda en WeakReference att hålla referenserna till lyssnarna.

Alternativ 1: Ta bort lyssnare

Låt oss börja med att skapa en ny metod removeUserStateChangeListener(StateListener listener) :

public class UserController {

    ...

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

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

    ...
}

Låt oss sedan kalla denna metod i aktivitetens onDestroy metod:

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

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

Med denna modifiering MainActivity inte längre instansen av MainActivity när användaren loggar in och ut. Men om dokumentationen inte är klar, är chansen stor att nästa utvecklare som börjar använda UserController kan missa att det krävs att avregistrera lyssnaren när aktiviteten förstörs, vilket leder oss till den andra metoden att undvika dessa typer av läckor.

Alternativ 2: Använda svaga referenser

Till att börja med, låt oss börja med att förklara vad en svag referens är. En svag referens har, som namnet antyder, en svag referens till ett objekt. Jämfört med ett vanligt instansfält, som är en stark referens, hindrar inte en svag referens skräpkollektorn, GC, från att ta bort föremålen. I exemplet ovan skulle detta tillåta att MainActivity samlas in efter att det har förstörts om UserController använde WeakReference till referensen som lyssnarna.

Kort sagt, en svag referens berättar för GC att om ingen annan har en stark referens till detta objekt, gå vidare och ta bort det.

Låt oss ändra UserController att använda en lista med WeakReference att hålla reda på dess lyssnare:

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

Med den här modifieringen spelar det ingen roll om lyssnarna tas bort eller inte, eftersom UserController har några starka referenser till någon av lyssnarna. Att skriva denna pannkodskod varje gång är emellertid besvärligt. Låt oss därför skapa en generisk klass som heter 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);
        }
    }
}

Låt oss nu skriva om UserController att använda WeakCollection<T> istället:

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

    ...
}

Som visas i kodexemplet ovan tar WeakCollection<T> bort all pannplattkod som behövs för att använda WeakReference istället för en normal lista. För att UserController#removeUserStateChangeListener(StateListener) allt: Om ett samtal till UserController#removeUserStateChangeListener(StateListener) missas kommer inte lyssnaren och alla objekt som den refererar att läcka.

Undvik minnesläckor med Anonym klass, hanterare, timeruppgift, tråd


I Android använder varje utvecklare Anonymous Class (Runnable) minst en gång i ett projekt. Varje Anonymous Class har en referens till sin förälder (aktivitet). Om vi utför en långvarig uppgift förstörs inte överordnadsaktivitet förrän uppgiften är avslutad.
Exempel använder hanterare och Anonymous Runnable klass. Minnet kommer att läcka när vi avslutar aktiviteten innan Runnable är klar.

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

Hur löser vi det?

  1. Gör inte någon lång tid med Anonymous Class eller så behöver vi en Static class för det och skicka WeakReference till det (som aktivitet, visa ...). Thread är densamma med Anonymous Class .
  2. Avbryt Handler , Timer när aktiviteten förstörs.


Modified text is an extract of the original Stack Overflow Documentation
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow