Pull to refresh

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

Development for Android *
Sandbox
Привет, 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. В моем случае это решит проблему с перерасходом памяти. Надеюсь, этот пост будет полезен и Вам!
Tags:
Hubs:
Total votes 20: ↑20 and ↓0 +20
Views 18K
Comments 28
Comments Comments 28

Posts