Android
Minnesläckor
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:
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?
- Gör inte någon lång tid med
Anonymous Class
eller så behöver vi enStatic class
för det och skickaWeakReference
till det (som aktivitet, visa ...).Thread
är densamma medAnonymous Class
. - Avbryt
Handler
,Timer
när aktiviteten förstörs.