Эта статья посвящена обнаружению и решению проблем, связанных с утечкой памяти, в Android-приложении.
Что такое утечка памяти?
Довольно часто при использовании приложений можно встретить диалог ANR (“Приложение не отвечает”), столкнуться с какими-либо тормозами в приложении. Также в Android Studio при разработке приложения можно столкнуться с ошибкой OutOfMemoryError. Все эти проблемы происходят из-за утечек памяти.
Некоторые объекты даже могут не распознаваться сборщиком мусора как мусор. В этом случае мы ничего не сможем сделать.
Помогая сборщику мусора, разработчик помогает себе. Сохранение ссылок на объекты, которые больше не требуются, является плохой практикой, освобождение ссылок на объекты после того, как работа с ними закончена, помогает сборщику мусора убить этот объект, что в конечном итоге поможет справиться с проблемами утечек памяти. Если вы сохраняете ссылки на объекты без необходимости, это приводит только к утечкам памяти.
Утечки памяти могут очень легко произойти на устройстве, если не позаботиться об этой проблеме при разработке приложения, так как Android-устройства имеют довольно мало памяти. Утечка памяти – самая большая проблема для любого приложения на Android, но, несмотря на это, её не так сложно избежать, если уделить проблеме внимание при разработке приложения. Нам нужно помнить о нескольких вещах при создании приложений без утечек памяти.
Утечки памяти могут быть вызваны различными способами, поскольку вызвать её проще простого. И если вы можете игнорировать утечки памяти, то ваши пользователи этого делать скорее всего не будут.
Причины, по которым происходят утечки памяти
Самая важная причина утечки памяти – это наши собственные ошибки при создании приложения. Разберём некоторые распространённые ошибки, которых следует избегать во время разработки.
Сохранение ссылки на объект в фоновом режиме
Никогда не держите ссылки на объекты UI в фоновом режиме, так как это приводит к утечке памяти.
Использование статических представлений
Не используйте статические представления, поскольку они всегда доступны и никогда не убиваются.
Использование статического контекста
Никогда не используйте контекст как статический объект.
public class MainActivity extends AppCompatActivity { private static Button button; // никогда так не делайте! private static Context context; // и так никогда не делайте! }
Использование контекста
Будьте осторожны, используя контекст, важно знать, какой контекст больше подходит для определённых мест. Используйте контекст приложения, если это возможно, и используйте контекст активности только в случае необходимости.
Никогда не забывайте попрощаться со слушателями после работы
Не забудьте отвязать свои слушатели в методах onPause() / onStop() / onDestroy(). Если этого не сделать, они всегда сохраняют активность живой.
Делайте как показано ниже:
public class LocationListenerActivity extends Activity implements LocationUpdate{ @Override public void onLocationChange(Location location){ } @Override public void onStart(){ LocationListener.get().register(this); } @Override public void onStop(){ LocationListener.get().unregister(this); } }
Использование внутреннего класса
Если вы используете внутренний класс, используйте его как static, потому что статический класс не нуждается в неявной ссылке внешнего класса. Использование внутреннего класса как нестатического оставляет внешний класс живым, поэтому этого лучше избегать.
И если вы используете представления в статическом классе, передавайте его в конструктор и используйте как слабую ссылку.
Используйте что-то вроде этого:
public class MainActivity extends AppCompatActivity { TextView textView; AsyncTask asyncTask; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); textView = (TextView) findViewById(R.id.textView); asyncTask = new MyBackgroundTask(textView).execute(); } @Override protected void onDestroy() { asyncTask.cancel(true); super.onDestroy(); } private static class MyBackgroundTask extends AsyncTask<Void, Void, String> { private final WeakReference<TextView> textViewReference; public MyBackgroundTask(TextView textView) { this.textViewReference = new WeakReference<>(textView); } @Override protected void onCancelled() { } @Override protected String doInBackground(Void... params) { return "some text"; } @Override protected void onPostExecute(String result) { TextView textView = textViewReference.get(); if (textView != null) { textView.setText(result); } } } }
Использование анонимного класса
Они очень похожи на нестатичные внутренние классы и являются полезными, но всё же лучше избегать использования анонимных классов.
Использование представлений в коллекции
Избегайте размещения представлений в коллекциях, которые не имеют чёткой схемы очистки памяти. WeakHashMap сохраняет представления как значения, поэтому лучше избегать его использования.
Все эти причины приводят к утечкам памяти, а затем к ANR, тормозам в приложении и OurOfMemoryError, что в конечном итоге приводит к удалению приложения пользователем.
Поэтому не следует обвинять во всех бедах сборщик мусора. Если утечка памяти происходит из-за этих причин, то это ваши собственные ошибки, поскольку сборщик мусора не предназначен для их обработки. Старайтесь не совершать их.
Как обнаружить и решить проблему утечки памяти?
Мы познакомились с утечками памяти, однако нам также нужно найти способ, как с ними справиться. Итак, как мы будем обнаруживать их и исправлять. Даже исправление одной ошибки своими силами может оказаться сложной задачей, что уж говорить о исправлении утечек памяти во всём приложении.
Благодаря “спасителю” LeakCanary становится намного проще справляться с утечками памяти. Он работает вместе с приложением и помогает обнаруживать утечки памяти. Он даже уведомляет нам о том, в каком месте у приложения происходит утечка памяти.