March 11, 2012

#4 - Universal Image Loader. Часть 1 - Введение [RU+EN]

English version - "Universal Image Loader. Part 1 - Introduction"

    При разработке приложений под Android довольно часто можно столкнуться с задачей отображения некоего графического контента из интернета. Поэтому снова и снова приходится  обеспечивать загрузку изображений из сети, их обработку и отображение в условиях ограниченности оперативной памяти. И несмотря на однородность задачи каждый новый проект накладывает на задачу свои специфические требования: возможно нужно организовать кэширование загруженных картинок; если картинки довольно большие, то необходимо обеспечить эффективную работу с памятью для предотвращения злополучной ошибки OutOfMemoryError; возможно во время загрузки изображения нужно показывать картинку-заглушку; а может одну и ту же картинку надо будет отображать в разных размерных вариациях; и т.д. В результате тратятся время и ресурсы на адаптацию программного кода под специфические нужды. Именно данная проблематика и подтолкнула меня к созданию библиотеки с открытым исходным кодом - Universal Image Loader, целью которой является универсализация решения вышеописанной задачи в виде гибкого и конфигурируемого инструмента.
    На данный момент библиотеку можно использовать повсеместно, где надо загрузить и отобразить (и возможно ещё закэшировать) картинку из интернета или из файловой системы смартфона. Классические примеры возможности применения ImageLoader'а - это списки, таблицы, галереи, где необходимо отображать изображения из сети.

    Главные фишки ImageLoader'а:
  • асинхронная загрузка и отображение изображений из интернета или с SD-карты;
  • возможность кэширования загруженных картинок в памяти и/или на файловой системе устройства;
  • возможность отслеживания процесса загрузки посредством "слушателей"
  • эффективная работа с памятью при кэшировании картинок в памяти;
  • широкие возможности настройки инструмента под свои нужды.
    Что же можно настраивать в ImageLoader'e?

    Глобальные настройки:
  • максимальный размер кэшируемых в памяти картинок;
  • тайм-аут для установки соединения и загрузки картинки;
  • максимальное количество потоков для загрузки изображений, работающих одновременно;
  • приоритет потоков по загрузке и отображению картинок;
  • программная реализация дискового кэша (можно выбрать одну из готовых реализаций или создать свою собственную);
  • программная реализация кэша в памяти (можно выбрать одну из готовых реализаций или создать свою собственную);
  • опции загрузки изображения по умолчанию
    Опции загрузки изображения (применяются к каждому отдельному вызову ImageLoader.displayImage(...)) предоставляют возможность указать:
  • отображать ли картинку-"заглушку" в ImageView, пока реальная картинка грузится? (если да, то нужно указать эту "заглушку");
  • отображать ли какую-либо картинку в ImageView, если URL картинки был передан пустым? (если да, то нужно указать эту картинку); 
  • кэшировать ли загруженную картинку в памяти?
  • кэшировать ли загруженную картинку на файловой системе?
  • тип декодирования изображения (максимально быстрый или максимально экономный для памяти).
    Как уже упоминалось, программные реализации дискового кэша и кэша в памяти можно подсунуть свои. Но скорее всего вам будет достаточно готовых решений, которые в большинстве своем представляют собой кэши, ограниченные по какому-либо параметру (размер, количество файлов) и имеющие свою логику самоочищения при превышении лимита (FIFO, самый старый объект, самый большой объект, наиболее редко используемый).

    Возможностей конфигурирования достаточно, но это не тот случай, наподобие "главного принципа UNIX": "Вы можете сконфигурировать ВСЁ. И вы БУДЕТЕ  конфигурировать все." :) В случае ImageLoader'а, вы можете настраивать все, но это отнюдь не обязательно: конфигурация по умолчанию всегда доступна и подходит для большинства случаев.

    Особенности реализации
    Немного слов о структуре проекта. Каждая задача на загрузку и отображение картинки (а это, забегая вперед, вызов ImageLoader.displayImage(imageView, imageUrl)) выполняется в отдельном потоке, кроме случаев, если картинка находится в кэше в памяти - тогда она просто сразу отображается. Существует отдельная очередь потоков, куда попадают задачи, если нужная картинка закэширована на файловой системе. Если же нужной картинки нет в кэше, то задача-поток попадает в пул потоков. Т.о. быстрому отображению закэшированных картинок ничего не препятствует.
    Алгоритм обработки задачи упрощенно представлен на схеме:



    Можно условно выделить основные действующие лица в проекте:
  • вышеупомянутые очередь и пул потоков;
  • кэш в памяти;
  • дисковый кэш;
  • декодер изображений, который перегоняет файлы картинок в объекты Bitmap.
А управляет всем этим главный класс ImageLoader, через который и происходит основное взаимодействие с пользователем.
    В следующей части Я расскажу непосредственно о Universal Image Loader API, а также дам некоторые советы и рекомендации по использованию библиотеки.

Следующие статьи:
Исходники проекта доступны здесь.

38 comments:

  1. Anonymous5/6/12 14:35

    Здравствуйте Сергей! Как с Вами можно связаться? есть вопросы. Спасибо

    ReplyDelete
    Replies
    1. Здравствуйте. На GitHub есть мой email - вот здесь

      Delete
  2. Anonymous29/1/13 00:34

    Молодьцы! Еще не юзал, но по статьям это то, что мне нужно.

    ReplyDelete
  3. Anonymous9/2/13 12:49

    Спасибо. Хорошая библиотека. Не подскажите каким образом можно сделать автоматическое слайдшоу? В каком направлении "копать"?

    ReplyDelete
    Replies
    1. ViewPager и Timer вам в руки.

      Delete
  4. Anonymous9/2/13 14:10

    Спасибо. Я к этому же и пришел))

    ReplyDelete
  5. Пытаюсь использовать вместе с facebook api и выдает гору ошибок, может подскажете в чем моя ошибка?

    ReplyDelete
    Replies
    1. Извините, но что-то меня подводят мои экстрасенсорные способности в последнее время. Так что не смогу помочь.

      Delete
  6. Anonymous19/3/13 08:42

    Подскажите, а как можно масштабировать изображение? Дело в том, что при загрузки, оно становится меньше размера экрана и находится по середине. Изображение находится во внутренней памяти телефона, когда просматриваю стандартными средствами android - изображение на весь экран.

    ReplyDelete
    Replies
    1. UIL не предоставляет функциональность масштабирования. Для этого вам нужно дополнительно использовать другие библиотеки (PhotoView, ImageViewTouch, gesture-image-view, ...). Также надо будет указать UIL, чтобы он не уменьшал картинки при декодировании: в опциях .imageScaleType(ImageScaleType.NONE).

      Delete
  7. Anonymous21/3/13 15:09

    Здравствуйте, отличная библиотека, спасибо! А не подскажите как можно добавить функциональность double-click zoom?

    ReplyDelete
    Replies
    1. Для этого надо использовать сторонние библиотеки: PhotoView, ImageViewTouch, gesture-image-view,...

      http://stackoverflow.com/questions/13398288/image-zoom-issue-with-universal-image-loader-and-view-pager

      Delete
  8. Отличная библиотека, спасибо! а можно URI с Drawable использовать?

    ReplyDelete
  9. Добрый день, огромное спасибо за библиотеку!
    Вопрос: предпочтительнее изображения использовать из assets нежели из drawable я так понимаю ?

    ReplyDelete
    Replies
    1. Добрый. Да, лучше. А почему не использовать ImageView.setImageResource(...)?

      Delete
  10. И как можно сделать донат, не юзая пэйпал ? )

    ReplyDelete
  11. Anonymous10/4/13 03:14

    Где не искал, не нашел самого просто примера : как отобразить картинку из урла в ImageView.. помогите пожалуйста !

    ReplyDelete
    Replies
    1. Anonymous10/4/13 03:21

      Все круто.. я был пьян и было поздно.. Спасибо Вам за прекрасный труд !

      Delete
  12. This comment has been removed by the author.

    ReplyDelete
  13. Сергей, а есть возможность сделать resize на bitmap, после того как картинка скачалась с интернета, или хотя бы размер битмапа узнать что бы сделать изменить layout params.
    например:

    if (response.getBitmap() != null) {
    int bmWidth = response.getBitmap().getWidth(), bmHeight = response.getBitmap().getHeight();
    float ratio = (float) bmWidth / (float) bmHeight;
    holder.eventImage.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, (int) (displayWidth / ratio)));
    holder.eventImage.setImageBitmap(response.getBitmap());

    ReplyDelete
    Replies
    1. Посмотрите в сторону DisplayImageOptions.imageScaleType(...). Узнать размеры битмапы можно в коллбэке onLoadingComplete(...).

      Delete
  14. This comment has been removed by the author.

    ReplyDelete
  15. В вашем примере происходит утечка памяти, как вы это объясните?

    One instance of "org.apache.harmony.xnet.provider.jsse.TrustManagerImpl" loaded by "" occupies 242 576 (10,75%) bytes. The memory is accumulated in one instance of "java.util.HashMap$HashMapEntry[]" loaded by "".

    Keywords
    org.apache.harmony.xnet.provider.jsse.TrustManagerImpl
    java.util.HashMap$HashMapEntry[]

    ReplyDelete
    Replies
    1. Anonymous3/12/13 17:24

      Я полностью поддерживаю, то, что вы делаете! Но анализатор памяти указал на возможные утечки выдал:

      One instance of "org.apache.harmony.xnet.provider.jsse.TrustManagerImpl" loaded by "" occupies 242 576 (10,75%) bytes. The memory is accumulated in one instance of "java.util.HashMap$HashMapEntry[]" loaded by "".

      Keywords
      org.apache.harmony.xnet.provider.jsse.TrustManagerImpl
      java.util.HashMap$HashMapEntry[]

      Delete
    2. Как бы не понятно, откуда ноги растут у этой java.util.HashMap$HashMapEntry[]
      Скорее всего это memoryCache, и один элемент занял 242Kb. Почему там утечка - не совсем понятно.

      Delete
  16. Anonymous24/2/14 00:38

    Привет , хорошая библиотека спасибо , у меня ошибка

    02-24 01:30:46.290: E/dalvikvm-heap(2044): Out of memory on a 2048016-byte allocation.
    02-24 01:30:46.298: I/dalvikvm(2044): "uil-pool-1-thread-2" prio=3 tid=19 RUNNABLE
    02-24 01:30:46.306: I/dalvikvm(2044): | group="main" sCount=0 dsCount=0 obj=0x41f18d20 self=0x5791a918
    02-24 01:30:46.306: I/dalvikvm(2044): | sysTid=2271 nice=13 sched=0/0 cgrp=apps/bg_non_interactive handle=1090281488
    02-24 01:30:46.306: I/dalvikvm(2044): | state=R schedstat=( 895751973 1427551255 5208 ) utm=65 stm=23 core=0
    02-24 01:30:46.313: I/dalvikvm(2044): at android.graphics.BitmapFactory.nativeDecodeStream(Native Method)
    02-24 01:30:46.313: I/dalvikvm(2044): at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:623)
    02-24 01:30:46.313: I/dalvikvm(2044): at com.nostra13.universalimageloader.core.decode.BaseImageDecoder.decode(BaseImageDecoder.java:78)
    02-24 01:30:46.313: I/dalvikvm(2044): at com.nostra13.universalimageloader.core.LoadAndDisplayImageTask.decodeImage(LoadAndDisplayImageTask.java:285)
    02-24 01:30:46.313: I/dalvikvm(2044): at com.nostra13.universalimageloader.core.LoadAndDisplayImageTask.tryLoadBitmap(LoadAndDisplayImageTask.java:241)
    02-24 01:30:46.313: I/dalvikvm(2044): at com.nostra13.universalimageloader.core.LoadAndDisplayImageTask.run(LoadAndDisplayImageTask.java:141)
    02-24 01:30:46.313: I/dalvikvm(2044): at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1080)
    02-24 01:30:46.313: I/dalvikvm(2044): at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:573)
    02-24 01:30:46.313: I/dalvikvm(2044): at java.lang.Thread.run(Thread.java:856)

    Тестировал на Nexus 5 , Nexus 4 , Tab 2 , Library version 1.9.2

    ReplyDelete
  17. Добрый вечер.

    А возможно ли добавить метод в imageLoader который будет возвращать drawable или Bitmap по url.
    В зависимости от того как вы их храните в памяти.
    Т.к. есть возможность вызывать load, а забрать drawable - нет
    Аля

    ImageLoader.getInstance().getDrawable(url);
    or
    ImageLoader.getInstance().getBitmap(url);

    ReplyDelete
    Replies
    1. loadImageSync(...) возвращает Bitmap по URL. Bitmap легко можно обернуть в BitmapDrawable, чтобы получить drawable.

      Delete
  18. Здраствуйте! Нужна помощь...никак не могу встроить ProgressBar в ListView. Задумка такая, пока идет загрузка картинок включается ProgressBar, после того как они загрузились включается ListView, а ProgressBar выключается! По принципу GridView....

    ReplyDelete
  19. Сергей, спасибо за отличную библиотеку! Все прекрасно работает.
    Но есть одна проблемка, больше касается медленных устройств. При прокрутке списка происходит подтормаживание, рывки из-за загрузки изображений, хотя они и производятся в фоновых потоках, но по всей видимости отнимают процессорное время, это несколько раздражает пользователей. Сейчас идет тенденция на увеличение плавности интерфейса Андроид-приложений и это правильно.
    Напрашивается решение, что нужно как-то блокировать, приостанавливать загрузку на время прокрутки и возобновлять её только, когда прокрутка заканчивается.
    Есть какие мысли по этому поводу?
    Если есть какие готовые решения, буду рад за подсказку.

    ReplyDelete
    Replies
    1. Посмотрите в сторону PauseOnScrollListener. Пример использовани есть в example проекте.

      Delete
  20. у меня такой вопрос при парсинге xml sax parser из rss все выводится коректно а вот картинке в постах нет можно ли с помощью Uneversal image loader релизовать это

    ReplyDelete
    Replies
    1. Не понимаю вопроса.

      Delete