Поиск…


Общие утечки памяти и способы их устранения

1. Исправьте свои контексты:

Попробуйте использовать соответствующий контекст: например, поскольку тост можно увидеть во многих действиях, а не только в одном, используйте getApplicationContext() для тостов, и поскольку службы могут продолжать работать, даже если действие закончилось, запустите службу с помощью:

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

Используйте эту таблицу в качестве краткого руководства для контекста: введите описание изображения здесь

Оригинальная статья о контексте здесь .

2. Статическая ссылка на контекст

Серьезной ошибкой памяти является сохранение статической ссылки на View . Каждый View имеет внутреннюю ссылку на Context . Это означает, что старая активность со всей иерархией представления не будет собираться мусором, пока приложение не будет прекращено. При повороте экрана у вас будет ваше приложение дважды в памяти.

Убедитесь, что нет никаких статических ссылок на View, Context или их потомков.

3. Убедитесь, что вы фактически завершаете свои услуги.

Например, у меня есть намеренная служба, которая использует API службы местоположения Google. И я забыл называть googleApiClient.disconnect(); :

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

4. Проверьте использование изображений и растровых изображений:

Если вы используете библиотеку Square Picasso, я обнаружил, что я .fit() память, не используя .fit() , что резко сократило объем памяти с 50 МБ в среднем до менее 19 МБ:

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

5. Если вы используете широковещательные приемники, отмените их регистрацию.

6. Если вы используете java.util.Observer (шаблон наблюдателя):

Обязательно используйте deleteObserver(observer);

Избегайте утечки активности с помощью AsyncTask

Слово предостережения: AsyncTask имеет много Гочи обособленно от утечки памяти , описанной здесь. Поэтому будьте осторожны с этим API или избегайте его вообще, если вы не полностью понимаете последствия. Существует много альтернатив (Thread, EventBus, RxAndroid и т. Д.).

Одна из распространенных ошибок с AsyncTask заключается в том, чтобы зафиксировать сильную ссылку на Activity хоста (или 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!
  }
}

Это проблема, потому что AsyncTask может легко пережить родительскую Activity , например, если во время выполнения задачи происходит изменение конфигурации.

Правильный способ сделать это - сделать вашу задачу static классом, который не захватывает родителя и содержит слабую ссылку на хост. 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
    }
  }
}

Анонимный обратный вызов в действиях

Каждый раз, когда вы создаете анонимный класс, он сохраняет неявную ссылку на свой родительский класс. Поэтому, когда вы пишете:

public class LeakyActivity extends Activity
{

...

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

Фактически вы отправляете ссылку на ваш экземпляр LeakyActivity для foo. Когда пользователь переходит от вашей LeakyActivity, эта ссылка может помешать экземпляру LeakyActivity быть собранным мусором. Это серьезная утечка, поскольку действия содержат ссылку на всю их иерархию представлений и, следовательно, являются довольно большими объектами в памяти.

Как избежать этой утечки:

Конечно, вы можете избежать анонимных обратных вызовов в действиях. Вы также можете отменить регистрацию всех ваших обратных вызовов в отношении жизненного цикла деятельности. вот так:

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

Контекст активности в статических классах

Часто вы захотите перенести некоторые классы Android в более простые в использовании служебные классы. Эти классы полезности часто требуют контекста для доступа к ресурсам Android и вашим приложениям. Общим примером этого является оболочка для класса SharedPreferences. Чтобы получить доступ к общим настройкам Androids, необходимо написать:

context.getSharedPreferences(prefsName, mode);

И поэтому может возникнуть соблазн создать следующий класс:

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

теперь, если вы вызываете init() с вашим контекстом активности, LeakySharedPrefsWrapper сохранит ссылку на вашу деятельность, не позволяя ей собирать мусор.

Как избежать:

При вызове статических вспомогательных функций вы можете отправить контекст приложения, используя context.getApplicationContext();

При создании статических вспомогательных функций вы можете извлечь контекст приложения из предоставленного вами контекста (вызов метода getApplicationContext () в контексте приложения возвращает контекст приложения). Итак, исправление нашей обертки просто:

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

Если контекст приложения не подходит для вашего варианта использования, вы можете включить параметр Context в каждую функцию утилиты, вы должны избегать ссылок на эти параметры контекста. В этом случае решение будет выглядеть так:

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

Обнаружение утечек памяти с помощью библиотеки LeakCanary

LeakCanary - это библиотека Java с открытым исходным кодом для обнаружения утечек памяти в ваших отладочных сборках.

Просто добавьте зависимости в 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'
 }

Затем в вашем классе 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);
  }
}

Теперь LeakCanary автоматически отобразит уведомление, когда в вашей отладочной сборке обнаружена утечка памяти активности.

ПРИМЕЧАНИЕ. Код деблокирования не будет содержать ссылки на LeakCanary, кроме двух пустых классов, которые существуют в leakcanary-android-no-op .

Избегайте утечки активности с помощью прослушивателей

Если вы реализуете или создаете слушателя в Activity, всегда обращайте внимание на жизненный цикл объекта, на котором зарегистрирован слушатель.

Рассмотрим приложение, в котором у нас есть несколько различных действий / фрагментов, заинтересованных в том, что пользователь вошел в систему или вышел из системы. Одним из способов сделать это было бы иметь экземпляр singleton UserController который можно подписаться, чтобы получить уведомление при изменении состояния пользователя:

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

Затем есть два действия: 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();
    }
}

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

Что происходит с этим примером, так это то, что каждый раз, когда пользователь входит в систему, а затем выходит из системы снова, экземпляр MainActivity просачивается. Утечка происходит из-за ссылки на активность в UserController#listeners .

Обратите внимание: даже если мы используем анонимный внутренний класс в качестве слушателя, активность все равно будет протекать:

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

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

Активность все равно будет протекать, потому что анонимный внутренний класс имеет неявную ссылку на внешний класс (в данном случае активность). Вот почему во внешнем классе можно вызвать методы экземпляра из внутреннего класса. Фактически, единственным типом внутренних классов, которые не имеют ссылки на внешний класс, являются статические внутренние классы.

Короче говоря, все экземпляры нестатических внутренних классов содержат неявную ссылку на экземпляр внешнего класса, который их создал.

Существует два основных подхода к решению этого вопроса, добавив метод удаления слушателя из прослушивателей UserController#listeners или используя WeakReference для хранения ссылки слушателей.

Альтернатива 1: Удаление слушателей

Начнем с создания нового метода removeUserStateChangeListener(StateListener listener) :

public class UserController {

    ...

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

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

    ...
}

Затем назовем этот метод в методе onDestroy активности:

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

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

С этой модификацией экземпляры MainActivity больше не просачиваются, когда пользователь входит в систему и выходит из MainActivity . Однако, если документация не ясна, возможно, что следующий разработчик, который начинает использовать UserController может пропустить, что требуется отменить регистрацию слушателя, когда действие будет уничтожено, что приведет нас к второму методу предотвращения этих типов утечек.

Альтернатива 2: Использование слабых ссылок

Прежде всего, давайте начнем с объяснения того, что такое слабая ссылка. Слабая ссылка, как следует из названия, содержит слабую ссылку на объект. По сравнению с обычным полем экземпляра, которое является сильной ссылкой, слабые ссылки не останавливают сборщик мусора, GC, от удаления объектов. В приведенном выше примере это позволит MainActivity быть собранным с мусором после его уничтожения, если UserController использовал WeakReference для ссылки на слушателей.

Короче говоря, слабая ссылка говорит GC, что, если никто другой не имеет сильной ссылки на этот объект, продолжайте и удалите его.

Давайте WeakReference UserController чтобы использовать список 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);
            }
        }
    }
    ...    
}

С этой модификацией не имеет значения, удалены ли слушатели или нет, поскольку UserController содержит сильных ссылок на любого из слушателей. Однако писать этот шаблонный код каждый раз является громоздким. Поэтому создадим общий класс под названием 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);
        }
    }
}

Теперь давайте WeakCollection<T> UserController вместо 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();
        }
    }

    ...
}

Как показано в приведенном выше примере кода, WeakCollection<T> удаляет весь шаблонный код, необходимый для использования WeakReference вместо обычного списка. В довершение всего: если вызов UserController#removeUserStateChangeListener(StateListener) пропущен, слушатель и все объекты, на которые он ссылается, не будут течь.

Избегайте утечек памяти с помощью анонимного класса, обработчика, задачи таймера, потока


В android каждый разработчик использует Anonymous Class (Runnable) хотя бы один раз в проекте. Любой Anonymous Class имеет ссылку на родителя (активность). Если мы выполним долговременную задачу, родительская активность не будет уничтожена до завершения задачи.
В примере используется класс обработчика и Анонимный Runnable . Память будет протекать, когда мы прекратим действие до завершения 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...

Как мы его решаем?

  1. Не делайте никаких WeakReference с Anonymous Class или нам нужен Static class и WeakReference в него WeakReference (например, активность, просмотр ...). Thread же с Anonymous Class .
  2. Отмените Handler , Timer когда действие уничтожено.


Modified text is an extract of the original Stack Overflow Documentation
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow