Экономим память: Picasso vs UniversalImageLoader

от автора

Привет, 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 – используемая приложением память в Мб. Ось Х – время проведения тест кейса.

Загрузка маленьких изображений

image
Размер кеша: Picasso=1.39 Мб, UIL=1.17 Мб

Загрузка больших изображений

image
Размер кеша: Picasso=3,67 Мб, UIL=5,44 Мб

Загрузка больших изображений с преобразованием до размера ImageView

image
Размер кеша: Picasso=3,67 Мб, UIL=5,44 Мб

Загрузка маленьких изображений и их обрезка до круглой картинки

image
Размер кеша: Picasso=1.39 Мб, UIL=1.17 Мб

Загрузка больших изображений и показ их в конфигурации RGB565

image

Результаты экспериментов с большими картинками меня впечатлили, и я решил, что стоит попробовать настроить конфигурацию UIL. Чтобы не сильно загружать память кешем – я попробовал отключить у UIL кеш в RAM. И, как вариант, установить кешируемый габарит картинки – не более, чем в половину экрана.

image

На основе эксперимента я сделал следующие выводы:

  • Если ваши списки работают с маленькими изображениями (сравнимыми с размером ImageView) – выбор библиотеки для вас не принципиален. Picasso создает чуть больший кеш на диске, при этом используя меньше RAM примерно на тот же размер.
  • Picasso показала потрясающие результаты по управлению памятью, работая с большими изображениями. UIL, по всей видимости, хранит оригинал изображения в памяти. Picasso хранит уже преобразованный размер картинки. Потому и кеш на диске у Picasso значительно меньше.
  • UIL может работать с той же эффективностью, что и Picasso, если его дополнительно настроить. Например, ограничить размер кеша в памяти. Или, как в одном из тестов – ограничить вручную размер кешируемых фотографий. Второй способ может быть непригоден для использования, поскольку устанавливает глобальную конфигурацию ImageLoader-а.
  • Работа с круглыми аватарами обходится «дешевле» через Picasso. Но, опять же, за счет того, что я вручную вызывал recycle() у оригинального Bitmap-а. Такое же действие можно выполнить и в UIL, устанавливая переопределенный BitmapDisplayer.
  • Picasso предельно проста в использовании и уже «с коробки» работает с памятью эффективно. Так выглядит инициализация и выполнение загрузки для библиотек:
    Picasso

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

    public 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 необходимо делать в самописных классах.
    RoundTransformation

    public 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";     } } 

    Config565Transformation

    public 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. В моем случае это решит проблему с перерасходом памяти. Надеюсь, этот пост будет полезен и Вам!

ссылка на оригинал статьи http://habrahabr.ru/post/218453/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *