1. Используйте векторные изображения вместо растровых
Конечно, большие изображения с множеством мелких деталей не стоит переводить в вектор — это займёт столько же места, как и растр, если не больше. Однако мелкие изображения, вроде иконок и остальных деталей пользовательского интерфейса следует конвертировать в вектор в целях экономии памяти. Да и работать с векторными изображениями зачастую гораздо удобнее. А теперь, самое главное — как это сделать?
- Открываем Android Studio.
- Кликаем правой кнопкой мыши по папке drawable или её содержимому > New > Vector Asset
- Указываем путь к своему svg-файлу. Если ваше изображение имеет неправильную форму, то советую поставить галочку рядом с параметром Override — в противном случае картинка будет подогнана под стандартные размеры, что может исказить её пропорции.
- Next > Finish
- Готово!
Для конвертации из растра в вектор могу посоветовать отличное бесплатное приложение Inkscape. Немного о работе с ним:
- Открываем Inkscape.
- Перетаскиваем в него любой растр. В открывшемся окне выбираем параметры импорта и жмём ОК.
- В верхнем тулбаре, предварительно выбрав наше изображение, выбираем Контур > Векторизировать растр (Shift + Alt + B).
- Теперь самое главное. В новом окне ставим галочку рядом с «Убрать фон» и выбираем следующее: будет ли наше изображение цветным, и сколько нужно сделать сканирований. Нельзя забывать, что от этих двух параметров напрямую зависит размер нашего векторного файла.
Больше сканирований — больше цветов, оттенков и деталей. Моему лепрекону, чтобы приобрести божеский вид, по причине большего количество цветов в изображении понадобилось 30 сканирований. Это довольно много, лучше делать не больше десяти и выбирать картинки попроще.
- Закрываем окно, нажимаем на растр и удаляем его клавишей Delete, переходим в Файл > Свойства документа (Shift + Ctrl + D), подгоняем размер страницы под содержимое.
Теперь проведём небольшой тест, доказывающий преимущество вектора в экономии памяти.
Я создал новый проект с одним-единственным ImageView, к которому применил анимацию, перемещающую его из точки А в точку Б, поочерёдно изменив изображения на растровое и векторное. Смотрим данные.
Растр
Вектор
Разница почти что в два раза. Думаю, это достаточно убедительно.
2. Увеличьте размер «кучи»
Для этого перейдите в манифест вашего проекта (app > manifests > AndroidManifest.xml) и в колонке application добавьте строку:
android:largeHeap="true"
По сути, увеличение «кучи» — это не решение проблемы OutOfMemory, а её задвигание на дальнюю полку. Вместо того, чтобы оптимизировать использование приложением памяти устройства, мы даём ему больше места. Не стоит забывать и то, что у каждого устройства свой объём памяти, как основной, так и дополнительной, выделяемой под приложение.
3. Избегайте утечек памяти
Любое приложение в своей работе использует множество объектов, которые, естественно, занимают определённое место в памяти. В идеале, сборщик мусора должен удалять из неё неиспользуемые объекты, но иногда появляются так называемые «утечки памяти», которые вызывают серьёзные проблемы в работе приложения. Существуют различные причины утечек памяти, о которых подробно рассказано тут.
От себя хотел бы посоветовать библиотеку WeakHandler, разработанной компанией Badoo, и призванной устранить утечки памяти, связанные с неправильным использованием android.os.Handler. Для использования данной библиотеки добавьте в свой gradle-файл (Gradle Scripts > build.gradle (Module:app)) в колонку dependencies строку:
compile 'com.badoo.mobile:android-weak-handler:1.1'
а в Java-файл:
private WeakHandler mHandler;
protected void onCreate(Bundle savedInstanceState) {
mHandler = new WeakHandler();
...
}
private void onClick(View view) {
mHandler.postDelayed(new Runnable() {
view.setVisibility(View.INVISIBLE);
}, 5000);
}
И не забудьте импортировать сам WeakHandler, если студия не сделала это автоматически.
4. Избегайте больших покадровых анимаций
Покадровая анимация в Android Studio — штука удобная, но далеко не самая экономичная. При использовании в ней большого количества изображений, вы непременно получите OutOfMemory.
Но, если уж вам это очень понадобилось, лучше используйте gif-изображение вместе с библиотекой Android Gif Drawable. Эта библиотека упрощает работу c gif, а также потребляет гораздо меньше памяти, чем покадровые анимации Android Studio. Для использования данной библиотеки добавьте в свой gradle-файл (Gradle Scripts > build.gradle (Module:app)) в колонку dependencies строку:
implementation 'pl.droidsonroids.gif:android-gif-drawable:1.2.16'
и в свой второй gradle-файл (Gradle Scripts > build.gradle(Module:«название вашего приложения»)) в колонки buildscript и allproject строку:
mavenCentral()
а в Java-файл:
GifDrawable gifFromResource = new GifDrawable( getResources(), R.drawable.имя_файла );
gifFromResource.start();
Для отключения gif вместо start() пишем stop(). Также не забывайте сжать gif-ки, это сэкономит ещё больше места.
Надеюсь, что моя статья была вам полезна. Спасибо.
Комментарии (10)
yavfast
06.05.2019 02:49Замечание про Handler. Eсли нет необходимости обрабатывать свои Messages, и надо что-то сделать в UI потоке, то предпочтительней использовать runOnUiThread() или сделать синглтон на базе MainLooper для отвязки от контекста. Если наплодить много Handler-ов и Looper-ов, то можно словить ANR из-за глобального synchronize в методе обработки очереди сообщений. Этой проблеме уже много-много лет.
Nexus7
06.05.2019 19:43VectorDrawable не даст экономии памяти по сравнению с обычным ImageView, т.к. после рендеринга векторной графики в растровую картинку результат помещается в кэш и при отрисовке виджета картинка берётся из кэша.
VectorDrawable имеет смысл применять только для экономии конечного размера APK-файла для больших картинок (если не использовать Bundle), и сокращения времени работы дизайнера, которому не нужно будет рендерить пять размеров каждой иконки.
При всех возможных плюсах у векторной графики есть существенный недостаток — сложно сделать pixel-prefect картинку, особенно маленького размера, которая будет нормально отображаться на всех разрешениях. Особенно страдает hdpi-плотность с 1.5 пикселем относительно базового mdpi.Scara31 Автор
07.05.2019 01:38во многом согласен с вами, но как вы тогда объясните скриншоты из профайлера с показателями занятой памяти?
VDG
07.05.2019 03:23Разница возможно в том, что для растра в памяти сидит bitmap, который система держит про запас (особенно, начиная с 8.0), и кеш для ImageView, в который bitmap отрисовался. А для вектора нет bitmap`а, а есть только кеш для ImageView.
Если вручную отрисовать битмап, например, на Canvas, а потом тут же освободить через recycle, то возможно сборщик мусора его тут же и приберёт.
И есть такая фича в новых версиях андроида, — если выделили вам разом 10Мб, то после освобождения отбирать их сразу не будут.Scara31 Автор
07.05.2019 03:43Спасибо за объяснение) По всей видимости, вектор занимает меньше места из-за каких-то багов/недоработок студии, но почему бы это не использовать? Потому и включил векторную графику в свой список
Nexus7
07.05.2019 18:35В конечном итоге все картинки, что из пиксельных файлов, что из векторных попадают на экран в виде OpenGL-текстур. Для скорости перерисовки виджетов в onDraw() картинки хранятся в виде битмапов, которые создаются в Native-коде, для которого у вас показываются одинаковые значения 2.8 Мбайт. Куда уходят мегабайты в Java-объектах надо смотреть в профилировщике более подробно.
Попробуйте провести эксперимент: отображать не одну картинку, а сразу штук сто в одном ScrollView/LinearLayout, загружая их все из разных файлов, чтобы они гарантированно брались каждая из своего кэша и все отображались на экране при скролле. Для чистоты эксперимента можно использовать одни и те же лейауты, меняя только src в ImageView. Так же стоит задать одинаковый исходный размер растровой и векторной картинок где-нибудь в 256px и размер ImageView тоже в 256px, чтобы битмапы не масштабировались. Одна картинка 256x256x4 будет занимать 256к, разница потребления памяти между одной такой картинкой и сотней будет явно заметна.
Заодно сравните скорость распаковки растровых картинок из PNG-файла и рендеринга компилированных векторных картинок, особенно, если они достаточно сложные.
egordeev
вроде иконку запуска приложения тоже можно векторной делать
Scara31 Автор
да, можно
egordeev
del