Привет, Android-разработчики!
Я думаю, каждый из нас сталкивается с загрузкой изображений по URL. Самый простой способ решения этой задачи: использовать готовую стороннюю библиотеку. Как правило, одним из таких готовых решений оказывается Universal Image Loader (UIL), Picasso. Когда я спрашиваю у разработчика, почему он выбрал ту или иную библиотеку, то, как правило, получаю разные ответы. Например, «у Picasso/UIL нет проблем с memory leaks», или «Square делают только правильные вещи», или просто «Да вот использую UIL, работает – и хорошо».
Так вот, мне стало интересно: какая из этих 2-х библиотек оптимально использует память? Я использую UIL и имею проблему с OutOfMemory на старых устройствах. Возможно, Picasso это лекарство?
Так появилась идея этого benchmark-а.
Цель тестирования: определить, какая из библиотек (UIL или Picasso) минимально использует память устройства.
Тест кейсы:
— Загрузка маленьких изображений (240х240)
— Загрузка больших изображений (>400px по любому из габаритов)
— Загрузка больших изображений и преобразование их размера к габаритам ImageView
— Загрузка маленьких изображений и их показ в виде круглой картинки
— Загрузка больших изображений и показ их в конфигурации RGB565
Методика выполнения теста:
В качестве списка используем GridView шириной в 2 столбца. Адаптер настраивается отдельно под каждый тест кейс. В адаптер отдаем список заранее подготовленных URL, создавая, таким образом, одинаковые условия тестирования.
С периодом в 1 сек, список автоматически делает один проход вниз, а потом вверх с шагом в 4 изображения. По каждому шагу производится измерение памяти, использованной приложением.
Измеряем использованную память в 3 этапа для каждого тест кейса:
— первый запуск — с чистым кешем приложения;
— второй запуск: не закрывая приложение после первого прохода;
— третий запуск – после повторного открытия приложения без чистки кеша.
По окончанию выполнения тест кейса, я дополнительно записывал размер кеша, что тоже немаловажно для старых устройств.
Исходники Benchmark-а можно найти по ссылке
github.com/artemmanaenko/ImageLoadersTest. Проект собран под Gradle.
Итак, ниже результаты по каждому тест кейсу. Ось Y – используемая приложением память в Мб. Ось Х – время проведения тест кейса.
Размер кеша: Picasso=1.39 Мб, UIL=1.17 Мб
Размер кеша: Picasso=3,67 Мб, UIL=5,44 Мб
Размер кеша: Picasso=3,67 Мб, UIL=5,44 Мб
Размер кеша: Picasso=1.39 Мб, UIL=1.17 Мб
Результаты экспериментов с большими картинками меня впечатлили, и я решил, что стоит попробовать настроить конфигурацию UIL. Чтобы не сильно загружать память кешем – я попробовал отключить у UIL кеш в RAM. И, как вариант, установить кешируемый габарит картинки – не более, чем в половину экрана.
На основе эксперимента я сделал следующие выводы:
Я думаю, каждый из нас сталкивается с загрузкой изображений по URL. Самый простой способ решения этой задачи: использовать готовую стороннюю библиотеку. Как правило, одним из таких готовых решений оказывается Universal Image Loader (UIL), Picasso. Когда я спрашиваю у разработчика, почему он выбрал ту или иную библиотеку, то, как правило, получаю разные ответы. Например, «у Picasso/UIL нет проблем с memory leaks», или «Square делают только правильные вещи», или просто «Да вот использую UIL, работает – и хорошо».
Так вот, мне стало интересно: какая из этих 2-х библиотек оптимально использует память? Я использую UIL и имею проблему с OutOfMemory на старых устройствах. Возможно, Picasso это лекарство?
Так появилась идея этого benchmark-а.
Цель тестирования: определить, какая из библиотек (UIL или Picasso) минимально использует память устройства.
Тест кейсы:
— Загрузка маленьких изображений (240х240)
— Загрузка больших изображений (>400px по любому из габаритов)
— Загрузка больших изображений и преобразование их размера к габаритам ImageView
— Загрузка маленьких изображений и их показ в виде круглой картинки
— Загрузка больших изображений и показ их в конфигурации RGB565
Методика выполнения теста:
В качестве списка используем GridView шириной в 2 столбца. Адаптер настраивается отдельно под каждый тест кейс. В адаптер отдаем список заранее подготовленных URL, создавая, таким образом, одинаковые условия тестирования.
С периодом в 1 сек, список автоматически делает один проход вниз, а потом вверх с шагом в 4 изображения. По каждому шагу производится измерение памяти, использованной приложением.
Измеряем использованную память в 3 этапа для каждого тест кейса:
— первый запуск — с чистым кешем приложения;
— второй запуск: не закрывая приложение после первого прохода;
— третий запуск – после повторного открытия приложения без чистки кеша.
По окончанию выполнения тест кейса, я дополнительно записывал размер кеша, что тоже немаловажно для старых устройств.
Исходники Benchmark-а можно найти по ссылке
github.com/artemmanaenko/ImageLoadersTest. Проект собран под Gradle.
Итак, ниже результаты по каждому тест кейсу. Ось Y – используемая приложением память в Мб. Ось Х – время проведения тест кейса.
Загрузка маленьких изображений
Размер кеша: Picasso=1.39 Мб, UIL=1.17 Мб
Загрузка больших изображений
Размер кеша: Picasso=3,67 Мб, UIL=5,44 Мб
Загрузка больших изображений с преобразованием до размера ImageView
Размер кеша: Picasso=3,67 Мб, UIL=5,44 Мб
Загрузка маленьких изображений и их обрезка до круглой картинки
Размер кеша: Picasso=1.39 Мб, UIL=1.17 Мб
Загрузка больших изображений и показ их в конфигурации RGB565
Результаты экспериментов с большими картинками меня впечатлили, и я решил, что стоит попробовать настроить конфигурацию UIL. Чтобы не сильно загружать память кешем – я попробовал отключить у UIL кеш в RAM. И, как вариант, установить кешируемый габарит картинки – не более, чем в половину экрана.
На основе эксперимента я сделал следующие выводы:
- Если ваши списки работают с маленькими изображениями (сравнимыми с размером ImageView) – выбор библиотеки для вас не принципиален. Picasso создает чуть больший кеш на диске, при этом используя меньше RAM примерно на тот же размер.
- Picasso показала потрясающие результаты по управлению памятью, работая с большими изображениями. UIL, по всей видимости, хранит оригинал изображения в памяти. Picasso хранит уже преобразованный размер картинки. Потому и кеш на диске у Picasso значительно меньше.
- UIL может работать с той же эффективностью, что и Picasso, если его дополнительно настроить. Например, ограничить размер кеша в памяти. Или, как в одном из тестов – ограничить вручную размер кешируемых фотографий. Второй способ может быть непригоден для использования, поскольку устанавливает глобальную конфигурацию ImageLoader-а.
- Работа с круглыми аватарами обходится «дешевле» через Picasso. Но, опять же, за счет того, что я вручную вызывал recycle() у оригинального Bitmap-а. Такое же действие можно выполнить и в UIL, устанавливая переопределенный BitmapDisplayer.
- Picasso предельно проста в использовании и уже «с коробки» работает с памятью эффективно. Так выглядит инициализация и выполнение загрузки для библиотек:
Picassopublic class PicassoSquareFitAdapter extends BaseBenchmarkAdapter { public PicassoSquareFitAdapter(Context context, IUrlListContainer urlListContainer) { super(context, urlListContainer); } @Override protected void loadImage(ImageView imageView, String url) { Picasso.with(context).load(url).fit().into(imageView); } }
UILpublic class UILSquareFitAdapter extends BaseBenchmarkAdapter { private DisplayImageOptions options; public UILSquareFitAdapter(Context context, IUrlListContainer urlListContainer) { super(context, urlListContainer); ImageLoaderConfiguration config = ImageLoaderConfiguration.createDefault(context); ImageLoader.getInstance().init(config); options = new DisplayImageOptions.Builder() .imageScaleType(ImageScaleType.EXACTLY) .resetViewBeforeLoading(true) .cacheInMemory(true) .cacheOnDisc(true) .build(); } @Override protected void loadImage(ImageView imageView, String url) { ImageLoader.getInstance().displayImage(url, imageView, options); } }
- Есть у Picasso и минус: трансформации изображений и приведение к RGB565 необходимо делать в самописных классах.
RoundTransformationpublic class RoundTransformation implements Transformation { @Override public Bitmap transform(Bitmap source) { int size = Math.min(source.getWidth(), source.getHeight()); int x = (source.getWidth() - size) / 2; int y = (source.getHeight() - size) / 2; Bitmap squaredBitmap = Bitmap.createBitmap(source, x, y, size, size); if (squaredBitmap != source) { source.recycle(); } Bitmap bitmap = Bitmap.createBitmap(size, size, source.getConfig()); Canvas canvas = new Canvas(bitmap); Paint paint = new Paint(); BitmapShader shader = new BitmapShader(squaredBitmap, BitmapShader.TileMode.CLAMP, BitmapShader.TileMode.CLAMP); paint.setShader(shader); paint.setAntiAlias(true); float radius = size / 2f; canvas.drawCircle(radius, radius, radius, paint); squaredBitmap.recycle(); return bitmap; } @Override public String key() { return "circle"; } }
Config565Transformationpublic class Config565Transformation implements Transformation { @Override public Bitmap transform(Bitmap source) { Bitmap resultBitmap = Bitmap.createBitmap( source.getWidth(), source.getHeight(), Bitmap.Config.RGB_565 ); Canvas canvas = new Canvas(resultBitmap); Paint paint = new Paint(); paint.setFilterBitmap(true); canvas.drawBitmap(source, 0, 0, paint); source.recycle(); return resultBitmap; } @Override public String key() { return "Config565Transformation"; } }
Для себя я сделал вывод: проекты необходимо переводить на Picasso. В моем случае это решит проблему с перерасходом памяти. Надеюсь, этот пост будет полезен и Вам!