Android
Wycieki pamięci
Szukaj…
Typowe wycieki pamięci i sposób ich usunięcia
1. Napraw swoje konteksty:
Spróbuj użyć odpowiedniego kontekstu: Na przykład, ponieważ Toast można zobaczyć w wielu działaniach zamiast w jednym, użyj getApplicationContext()
dla tostów, a ponieważ usługi mogą być uruchomione, mimo że działanie zakończyło się, uruchom usługę z:
Intent myService = new Intent(getApplicationContext(), MyService.class);
Użyj tej tabeli jako szybkiego przewodnika po tym, jaki kontekst jest odpowiedni:
Oryginalny artykuł na temat kontekstu tutaj .
2. Statyczne odniesienie do kontekstu
Poważnym błędem wycieku pamięci jest utrzymywanie statycznego odniesienia do View
. Każdy View
ma wewnętrzne odniesienie do Context
. Co oznacza, że stara czynność z całą hierarchią widoku nie zostanie wyrzucona do pamięci, dopóki aplikacja nie zostanie zakończona. Podczas obracania ekranu aplikacja będzie dwa razy w pamięci.
Upewnij się, że absolutnie nie ma statycznego odniesienia do widoku, kontekstu ani któregokolwiek z ich potomków.
3. Sprawdź, czy faktycznie kończysz swoje usługi.
Na przykład mam intentService, która korzysta z interfejsu API usługi lokalizacyjnej Google. I zapomniałem wywołać googleApiClient.disconnect();
:
//Disconnect from API onDestroy()
if (googleApiClient.isConnected()) {
LocationServices.FusedLocationApi.removeLocationUpdates(googleApiClient, GoogleLocationService.this);
googleApiClient.disconnect();
}
4. Sprawdź użycie obrazu i map bitowych:
Jeśli korzystasz z biblioteki Square Picassa , zauważyłem, że przeciekałem pamięć, nie używając .fit()
, co drastycznie zmniejszyło mój obszar pamięci z średnio 50 MB do mniej niż 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. Jeśli korzystasz z odbiorników telewizyjnych, wyrejestruj je.
6. Jeśli używasz java.util.Observer
(wzorzec obserwatora):
Upewnij się, że używasz deleteObserver(observer);
Unikaj przecieków Działania za pomocą AsyncTask
Słowo ostrzeżenia : AsyncTask ma wiele błędów poza opisanym tutaj wyciekiem pamięci. Uważaj więc na ten interfejs API lub całkowicie go unikaj, jeśli nie w pełni rozumiesz implikacje. Istnieje wiele alternatyw (Thread, EventBus, RxAndroid itp.).
Jeden wspólny błąd z AsyncTask
jest uchwycić wyraźne odniesienie do gospodarza Activity
(lub 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!
}
}
Jest to problem, ponieważ AsyncTask
łatwo przeżyć rodzica Activity
, na przykład w przypadku zmiany konfiguracji dzieje, gdy zadanie jest uruchomione.
We właściwy sposób to zrobić, aby Twoje zadanie static
klasy, które nie uchwycić rodziców i trzyma słabe odniesienie do gospodarza 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
}
}
}
Anonimowe wywołanie zwrotne w działaniach
Za każdym razem, gdy tworzysz anonimową klasę, zachowuje ona niejawne odniesienie do swojej klasy nadrzędnej. Więc kiedy piszesz:
public class LeakyActivity extends Activity
{
...
foo.registerCallback(new BarCallback()
{
@Override
public void onBar()
{
// do something
}
});
}
W rzeczywistości wysyłasz referencję do instancji LeakyActivity do foo. Gdy użytkownik opuści Twoją LeakyActivity, to odwołanie może zapobiec gromadzeniu śmieci przez instancję LeakyActivity. Jest to poważny wyciek, ponieważ działania zawierają odniesienie do całej hierarchii widoków i dlatego są raczej dużymi obiektami w pamięci.
Jak uniknąć tego wycieku:
Oczywiście możesz całkowicie uniknąć używania anonimowych wywołań zwrotnych w działaniach. Możesz także wyrejestrować wszystkie swoje połączenia zwrotne w odniesieniu do cyklu życia aktywności. tak jak:
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);
}
}
Kontekst działania w klasach statycznych
Często będziesz chciał zawinąć niektóre klasy Androida w łatwiejsze w użyciu klasy narzędzi. Te klasy narzędzi często wymagają kontekstu, aby uzyskać dostęp do systemu operacyjnego Android lub zasobów aplikacji. Typowym tego przykładem jest opakowanie dla klasy SharedPreferences. Aby uzyskać dostęp do wspólnych preferencji Androidów, należy napisać:
context.getSharedPreferences(prefsName, mode);
I tak można pokusić się o utworzenie następującej klasy:
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);
}
}
teraz, jeśli wywołasz init()
w kontekście aktywności, LeakySharedPrefsWrapper zachowa odniesienie do twojej aktywności, zapobiegając gromadzeniu śmieci.
Jak uniknąć:
Podczas wywoływania funkcji statycznego pomocnika można wysłać w kontekście aplikacji za pomocą kontekstu.getApplicationContext context.getApplicationContext();
Podczas tworzenia statycznych funkcji pomocniczych można wyodrębnić kontekst aplikacji z podanego kontekstu (Wywołanie getApplicationContext () w kontekście aplikacji zwraca kontekst aplikacji). Tak więc poprawka do naszego opakowania jest prosta:
public static void init(Context context)
{
sContext = context.getApplicationContext();
}
Jeśli kontekst aplikacji jest nieodpowiedni dla twojego przypadku użycia, możesz dołączyć parametr Context do każdej funkcji narzędzia, powinieneś unikać utrzymywania odniesień do tych parametrów kontekstu. W takim przypadku rozwiązanie wyglądałoby tak:
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);
}
Wykryj wycieki pamięci za pomocą biblioteki LeakCanary
LeakCanary to biblioteka Java Open Source do wykrywania wycieków pamięci w kompilacjach debugowania.
Po prostu dodaj zależności w 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'
}
Następnie w swojej klasie 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);
}
}
Teraz LeakCanary automatycznie wyświetli powiadomienie o wykryciu wycieku pamięci aktywności w kompilacji debugowania .
UWAGA: Kod wersji nie będzie zawierał odniesienia do LeakCanary poza dwiema pustymi klasami, które istnieją w leakcanary-android-no-op
od leakcanary-android-no-op
.
Unikaj nieszczelnych zajęć ze słuchaczami
Jeśli implementujesz lub tworzysz detektor w działaniu, zawsze zwracaj uwagę na cykl życia obiektu, który zarejestrował detektor.
Rozważ aplikację, w której mamy kilka różnych działań / fragmentów zainteresowanych, gdy użytkownik jest zalogowany lub wylogowany. Jednym ze sposobów na to byłoby posiadanie pojedynczej instancji UserController
którą można subskrybować, aby otrzymywać powiadomienia o zmianie stanu użytkownika:
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();
}
}
Następnie są dwie czynności, 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();
}
}
I 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();
}
}
W tym przykładzie dzieje się tak, że za każdym razem, gdy użytkownik loguje się, a następnie MainActivity
wystąpienie wycieku MainActivity
. Wyciek występuje, ponieważ istnieje odwołanie do działania w UserController#listeners
.
Uwaga: nawet jeśli użyjemy anonimowej klasy wewnętrznej jako nasłuchiwania, działanie nadal przecieka:
...
this.userController.registerUserStateChangeListener(new UserController.StateListener() {
@Override
public void userLoggedIn() {
showUserAccount();
}
@Override
public void userLoggedOut() {
finish();
}
});
...
Aktywność nadal by się wyciekła, ponieważ anonimowa klasa wewnętrzna zawiera niejawne odniesienie do klasy zewnętrznej (w tym przypadku działania). Dlatego możliwe jest wywoływanie metod instancji w klasie zewnętrznej z klasy wewnętrznej. W rzeczywistości jedynym rodzajem klas wewnętrznych, które nie mają odniesienia do klasy zewnętrznej, są statyczne klasy wewnętrzne.
Krótko mówiąc, wszystkie wystąpienia niestatycznych klas wewnętrznych zawierają niejawne odniesienie do wystąpienia klasy zewnętrznej, która je utworzyła.
Istnieją dwa główne podejścia do rozwiązania tego, albo poprzez dodanie metody usunięcia detektora z UserController#listeners
lub użycie WeakReference
do przechowywania referencji detektorów.
Alternatywa 1: Usuwanie słuchaczy
Zacznijmy od utworzenia nowej metody removeUserStateChangeListener(StateListener listener)
:
public class UserController {
...
public void registerUserStateChangeListener(StateListener listener) {
listeners.add(listener);
}
public void removeUserStateChangeListener(StateListener listener) {
listeners.remove(listener);
}
...
}
Następnie onDestroy
tę metodę w metodzie onDestroy
:
public class MainActivity extends Activity implements UserController.StateListener{
...
@Override
protected void onDestroy() {
super.onDestroy();
userController.removeUserStateChangeListener(this);
}
}
Dzięki tej modyfikacji instancje MainActivity
nie są już wyciekane, gdy użytkownik loguje się i MainActivity
. Jeśli jednak dokumentacja nie jest jasna, istnieje szansa, że następny programista, który zacznie używać UserController
może przegapić wymóg wyrejestrowania nasłuchiwacza po zniszczeniu działania, co prowadzi nas do drugiej metody unikania tego rodzaju wycieków.
Alternatywa 2: Używanie słabych referencji
Po pierwsze, zacznijmy od wyjaśnienia, czym jest słabe odniesienie. Słabe odniesienie, jak sugeruje nazwa, zawiera słabe odniesienie do obiektu. W porównaniu do zwykłego pola instancji, które jest silnym odwołaniem, słabe odniesienia nie powstrzymują śmieciarza, GC, przed usunięciem obiektów. W powyższym przykładzie pozwoliłoby MainActivity
na zbieranie śmieci po MainActivity
jeśli UserController
użył WeakReference
do odwołania do nasłuchiwania.
Krótko mówiąc, słabe odniesienie mówi GC, że jeśli nikt inny nie ma silnego odniesienia do tego obiektu, śmiało i usuń go.
Pozwól nam zmodyfikować UserController
aby używał listy WeakReference
do śledzenia nasłuchiwania:
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);
}
}
}
...
}
Dzięki tej modyfikacji nie ma znaczenia, czy detektory zostaną usunięte, ponieważ UserController
nie zawiera żadnych silnych odniesień do żadnego z detektorów. Jednak pisanie tego kodu za każdym razem jest uciążliwe. Dlatego WeakCollection
ogólną klasę o nazwie 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);
}
}
}
Teraz WeakCollection<T>
ponownie UserController
aby zamiast tego używał WeakCollection<T>
:
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();
}
}
...
}
Jak pokazano w powyższym przykładzie kodu, WeakCollection<T>
usuwa cały kod WeakCollection<T>
potrzebny do użycia WeakReference
zamiast normalnej listy. UserController#removeUserStateChangeListener(StateListener)
: jeśli nie zostanie UserController#removeUserStateChangeListener(StateListener)
wywołanie UserController#removeUserStateChangeListener(StateListener)
, detektor i wszystkie obiekty, do których się odwołuje, nie wyciekną.
Unikaj wycieków pamięci za pomocą Anonimowej klasy, modułu obsługi, zadania czasomierza, wątku
W Androidzie każdy programista korzysta z Anonymous Class
(Runnable) przynajmniej raz w projekcie. Każda Anonymous Class
ma odniesienie do swojego elementu nadrzędnego (działania). Jeśli wykonamy długotrwałe zadanie, działanie nadrzędne nie zostanie zniszczone, dopóki zadanie nie zostanie zakończone.
Przykład wykorzystuje moduł obsługi i klasę Anonymous Runnable
. Pamięć będzie nieszczelna, gdy zakończymy działanie przed zakończeniem Runnable
.
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...
Jak to rozwiązujemy?
- Nie wykonuj żadnych długich operacji z
Anonymous Class
lub potrzebujemy do tegoStatic class
iWeakReference
do niejWeakReference
(takie jak aktywność, widok ...).Thread
jest taki sam w przypadkuAnonymous Class
. - Anuluj
Handler
,Timer
, gdy działalność jest zniszczona.