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, а также дам некоторые советы и рекомендации по использованию библиотеки.

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

48 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
    2. Пожалуйста :)

      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. http://bit.ly/1bVVtRT

      Delete
    2. 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
    3. Как бы не понятно, откуда ноги растут у этой 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
  21. Спасибо, отличная библиотека!

    ReplyDelete
  22. Anonymous19/4/15 01:09

    Можно как то совместить вашу библиотеку и PhotoView(https://github.com/chrisbanes/PhotoView)? Что то не придумывается как это сделать. Хотелось во ViewPager грузитить картинки UIL и что бы PhotoView их ресайзила. PhotoView берет картинки из drawable, uri, resource или bitmap

    ReplyDelete
    Replies
    1. Можно, просто передайвайте в ImageLoader.displayImage(...) ваш PhotoView.

      Delete
    2. Anonymous20/4/15 15:59

      Спасибо за ответ, попробую)

      Delete
    3. Anonymous22/4/15 10:40

      Да, работает, спасибо)

      Delete
  23. При пролистывании ListView, когда картинка грузится в первый раз в кэш, заметны фризы.
    Какой параметр отвечает за "плавность" подгрузки картинок в фоне?

    ReplyDelete
  24. Каким образом можно узнать имя фотографии, подгруженную в текущий момент времени?
    Хочу сделать возможным "поделиться" фотографией через приложения (whatsap, vk итп).
    Ведь можно это сделать используя UIL?

    ReplyDelete
  25. Здравствуйте. Я пробую использовать вашу библиотеку для загрузки картинок постов из интернета. На экране прокручиваются картинки на всю ширину экрана. Максимум одновременно пользователь видит 3-4 картинки при прокрутке. Подгрузка картинок в ленту бесконечная. По сравнению с другими библиотеками(например, Picasso) ваша намного шустрее работает. Но возможно я что-то не так делаю, так как у меня возникают проблемы с памятью при загрузке большого количества картинок.
    Я пробовал разные библиотеки, но с ними тоже возникает проблема памяти. Я пробовал выводить картинки разными способами. Сначала я просто подгружал через инфлатер посты в определённый контейнер. Затем я попробовал переделать всё с нуля и реализовал через RecyclerView и адаптер. А теперь я вообще для чистоты эксперимента упростил до примитива - теперь у меня просто на экран только лишь картинки выводятся. И всё равно возникает проблема с памятью.
    Т.е. проблема в том, что после определённой картинки (где-то 130) дальше картинки перестают грузиться и везде отображается картинка ошибки. В логе пишет, что OutOfMemory. Я тестирую на версии андроида 4.1. А на 2.3 ещё меньше картинок удаётся загрузить. На андроид 5 вроде бы этой проблемы не наблюдается.
    Я думал подобные библиотеки как раз нужны чтобы следить за тем, чтобы картинки, которые не видны на экране выгружались из памяти. На Cyberforum мне сказали, что наоборот эти библиотеки не предназначены для этого и надо самому писать реализацию выгрузки из памяти.
    Вот сам код: https://dl.dropboxusercontent.com/u/29604742/sites/HomeActivity.java
    Помогите пожалуйста, а то я в тупике!

    ReplyDelete
  26. This comment has been removed by the author.

    ReplyDelete