Освой Android играючи
/* Моя кошка замечательно разбирается в программировании. Стоит мне объяснить проблему ей - и все становится ясно. */
John Robbins, Debugging Applications, Microsoft Press, 2000
java.lang.OutOfMemoryError
at android.graphics.Bitmap.nativeCreate(Bitmap.java:-2)
at android.graphics.Bitmap.createBitmap(Bitmap.java:689)
at com.squareup.ui.SignView.createSignatureBitmap(SignView.java:121)
Работая над Square Register, мы рисовали подпись клиента, используя битмап-кэш. Поскольку этот битмап размером с экран устройства, у нас было очень много ошибок с переполнением памяти OutOfMemory (OOM).
Мы пробовали несколько подходов ни один из которых не решил проблему:
Мы шли по ложному пути.
Проблема была не в размере битмапа. Когда память почти полностью использована, OOM может возникнуть где угодно. Конечно, вероятность, что это произойдет во время выделения больших объектов, вроде битмапов, увеличивается. OOM — это лишь симптом более глубокой проблемы: утечек памяти.
Многие объекты имеют ограниченное время жизни. Когда их время жизни заканчивается, ожидаются, что они будут собраны сборщиком мусора. Если цепочка ссылок указывает на объект в памяти, после ожидаемого завершения времени жизни объекта, то это создаёт утечку памяти. При накоплении некоторого количества утечек, у программы начинает заканчивается память.
Например, после того как был вызван метод Activity.onDestroy, то активность и вся иерархия компонентов и связанные с ними битмапы должны быть доступны для сборки мусора. Если фоновый поток во время работы удерживает ссылку на активность, то занимаемая ей память не может быть освобождена. Рано или поздно это приведет к выбросу ошибки OutOfMemoryError.
Охота на утечки памяти - это ручной процесс, хорошо описанный в серии статей Wrangling Dalvik от Raizlabs.
Вот ключевые этапы:
Что, если существовала бы библиотека, которая могла сделать это всё, ещё до возникновения OOM и позволила бы сосредоточится на исправлении утечки памяти?
LeakCanary — это Java-библиотека с открытым исходным кодом для обнаружения утечек памяти в отладочных сборках.
Давайте взглянем на следующий пример:
class Cat {
}
class Box {
Cat hiddenCat;
}
class Docker {
static Box container;
}
// ...
Box box = new Box();
Cat schrodingerCat = new Cat();
box.hiddenCat = schrodingerCat;
Docker.container = box;
Затем вы создаёте объект RefWatcher и предоставляете ему объект для отслеживания:
// Мы ожидаем что schrodingerCat должен исчезнуть (или нет). Давайте отслеживать его.
refWatcher.watch(schrodingerCat);
После того, как утечка была обнаружена, вы получаете неплохой трейс утечки.
* GC ROOT static Docker.container
* references Box.hiddenCat
* leaks Cat instance
Установка очень проста. С помощью всего одной строчки кода LeakCanary автоматически будет обнаруживать утечки активности:
public class ExampleApplication extends Application {
@Override public void onCreate() {
super.onCreate();
LeakCanary.install(this);
}
}
Вы получаете уведомление в неплохом виде:
После начала использования LeakCanary, мы обнаружили и исправили много утечек в нашем приложении. Мы даже нашли несколько утечек в Android SDK.
Результаты потрясающие: на 94% меньше ошибок с OutOfMemoryError!
Установи LeakCanary сейчас!
В файле build.gradle:
// новая ветка 2
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.2'
// старая ветка 1.6
dependencies {
debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.2'
releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.2'
// Optional, if you use support library fragments:
debugImplementation 'com.squareup.leakcanary:leakcanary-support-fragment:1.6.2'
}
Эта запись означает, что проверка на утечку памяти будет происходить только во время разработки приложения, а в релизе и в тестах проверять ничего не надо.
В классе 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);
// Normal app init code...
}
}
Готово! LeakCanary будет автоматически показывать уведомления при обнаружении утечки памяти.
LeakCanary.install() возвращает предварительно сконфигурированные RefWatcher. А также устанавливает ActivityRefWatcher, который автоматически определяет утечку после Activity.onDestroy().
public class ExampleApplication extends Application {
public static RefWatcher getRefWatcher(Context context) {
ExampleApplication application = (ExampleApplication) context.getApplicationContext();
return application.refWatcher;
}
private RefWatcher refWatcher;
@Override public void onCreate() {
super.onCreate();
refWatcher = LeakCanary.install(this);
}
}
Можно использовать RefWatcher для отслеживания утечек памяти у фрагментов:
public abstract class BaseFragment extends Fragment {
@Override public void onDestroy() {
super.onDestroy();
RefWatcher refWatcher = ExampleApplication.getRefWatcher(getActivity());
refWatcher.watch(this);
}
}
Вышла новая версия:
LeakCanary 1.6 - новинки в версии 1.6.