March 11, 2012

#6 - Universal Image Loader. Часть 3 - Использование [RU+EN]

English version - "Universal Image Loader. Part 3 - Usage"

Предыдущие статьи:
    В прошлой статье мы проинициализировали ImageLoader конфигурацией и теперь он готов к непосредственному использованию по назначению.
    Для этого он имеет 4 перегруженных метода:
void displayImage(String url, ImageView view)
void displayImage(String url, ImageView view, DisplayImageOptions options)
void displayImage(String url, ImageView view, ImageLoadingListener listener)
void displayImage(String url, ImageView view, DisplayImageOptions options, ImageLoadingListener listener)

    Первый вариант - все просто: говорим из какого URL-а загрузить картинку и в какой ImageView отобразить. Опции отображения (DisplayImageOptions) в этом случае будут взяты из конфигурации (defaultDisplayImageOptions(...)).
    Второй вариант: вот мы уже можем определить отдельные опции для конкретной задачи. Приведем сначала примерчик создания своих опций:
DisplayImageOptions options = new DisplayImageOptions.Builder()
    .showStubImage(R.drawable.stub_image)
    .showImageForEmptyUrl(R.drawable.image_for_empty_url)
    .resetViewBeforeLoading()
    .cacheInMemory()
    .cacheOnDisc()
    .decodingType(ImageScaleType.EXACT)
    .build();
    Да, опять Builder. Как говорилось в первой статье, с помощью DisplayImageOptions мы можем указать:
  • надо ли отображать картинку-заглушку в ImageView, пока загружается реальная картинка, и какую именно заглушку отображать;
  • надо ли отображать какую-либо картинку в ImageView, если URL картинки был передан пустым (null), и какую именно картинку отображать;  
  • "обнулять" ли ImageView перед началом загрузки; 
  • надо ли кэшировать загруженную картинку в памяти;
  • надо ли кэшировать загруженную картинку на файловой системе.
  • декодировать изображение максимально быстро (ImageScaleType.POWER_OF_2) или максимально экономно для оперативной памяти (ImageScaleType.EXACT).
    Так вот, мы можем передавать эти опции при каждом вызове метода displayImage(), а можем указать дефолтные в конфигурации для инициализации, и они будут использоваться во всех случаях, когда опции не были явно переданы при вызове метода.
    Третий вариант: в дополнение ко всему вы можете "прослушивать" процесс загрузки и отображения картинки с помощью интерфейса ImageLoadingListener:
public interface ImageLoadingListener {
    void onLoadingStarted();
    void onLoadingFailed();
    void onLoadingComplete();
    void onLoadingCancelled();
}
    Ну а четвертый вариант - самый мощный: можете и опции определить, и "слушать" процесс.

    Советы и рекомендации
  1. Для того, чтобы ImageLoader смог выполнить свое предназначение, ему необходимо передать корректные параметры. И речь не столько о URL картинки, сколько об ImageView. Если вы создаете объект ImageView в коде (а не с помощью LayoutInflater), то в передавайте конструктор текущий Activity, а не контекст приложения:
  2. ImageView imageView = new ImageView(getApplicationContext()); // НЕ ПРАВИЛЬНО!
    
    ImageView imageView = new ImageView(MyActivity.this); // правильно
    ImageView imageView = new ImageView(getActivity()); // правильно (для Fragment'ов)
    
  3. Параметры maxImageWidthForMemoryCache(...) и maxImageHeightForMemoryCache(...) стоит настраивать в конфигурации только если вы хотите загружать в ImageView картинки размера, превышающего размеры экрана устройства (например для последующего увеличения). Во всех остальных случаях этого делать не нужно: эти параметры по умолчанию учитывают размеры экрана, что позволяет экономить память при работе с Bitmap'ами.
  4. Устанавливайте размер пула потоков в конфигурации с умом: большой размер пула (> 10) позволит работать множеству потоков одновременно, что может сильно сказаться на отзывчивости UI. Но это можно поправить установкой более низкого приоритета для потоков: чем ниже приоритет, тем отзывчивей UI во время работы ImageLoader'а, но тем долльше грузятся картинки. Отзывчивость UI крайне важна для списков (плавная прокрутка), поэтому стоит поиграться с натройкой параметров threadPoolSize(...) и threadPriority(...) для подбора оптимальной конфигурации для вашего приложения.
  5. Настройки memoryCacheSize(...) и memoryCache(...) перекрывают друг друга, используйте только одну из них для одного объекта конфигурации.
  6. Настройки discCacheSize(...), discCacheFileCount(...) и discCache(...) перекрывают друг друга, используйте только одну из них для одного объекта конфигурации. 
  7. Если при использовании ImageLoader'а в приложении вы всегда (или почти всегда) передаете в метод displayImage(...) одни и те же опции загрузки (DisplayImageOptions), то тогда разумным решением будет задать данные опции в конфигурации ImageLoader в качестве опций по умолчанию (метод defaultDisplayImageOptions(...)). Тогда данные опции не обязательно будет каждый раз указывать при вызове displayImage(...), т.к. если в метод явно не переданы опции, то для данной задачи будут использоваться дефолтные опции.
  8. Большой разницы в скорости между типами декодирования POWER_OF_2 и EXACT нет, но рекомендуется использовать POWER_OF_2 для разного рода списков (где нужно отображать много картинок небольшого размера), а  EXACT для галерей (когда нужно отображать картинки в больших размерах).
    На этом, пожалуй, Я завершу свой затянувшийся рассказ об Universal Image Loader'е.
    Иходники проекта по прежнему доступны на GitHub.  

52 comments:

  1. Насколько я понял, в новой версии убрали DecodingType.

    ReplyDelete
    Replies
    1. Точнее переименовали в ImageScaleType.
      FAST -> POWER_OF_2
      MEMORY_SAVING -> EXACT

      Delete
  2. Вместо картинки пришёл html (или другой мусор). Это возможно отловить? В логах вижу SkImageDecoder::Factory returned null, но как отловить ошибку декодирования я не вижу.

    ReplyDelete
    Replies
    1. С помощью слушателя:

      imageLoader.displayImage(imageUri, imageView, options, new SimpleImageLoadingListener() {
      @Override
      public void onLoadingFailed(FailReason failReason) {
      switch (failReason) {
      case IO_ERROR:
      // Вот сюда должен прийти callback в случае ошибки
      break;
      }
      }
      });

      Delete
    2. Не приходит callback. Ошибки при загрузке нету. Простой способ проверить -- указать в качестве адреса картинки http://ya.ru . ImageLoader пришедший html даже кеширует.

      Delete
    3. В новой версии 1.5.2 поправил этот момент. IO_ERROR должен приходить.

      Delete
  3. Как можно сделать, чтобы на диск кешировалось постоянно, а не только когда кеш памяти переполнится?

    ReplyDelete
    Replies
    1. С чего вы взяли, что на диск кэшируется только после переполнения памяти?

      Delete
    2. По логам видно, кроме того если после загрузки изображений отключить связь и убить приложение, то не все картинки сохранятся на карточке.

      Всё, я уже разобрался. Надо было .cacheOnDisk указывать перед .cacheInMemory.

      Delete
    3. Вообще-то кэширование на диске зависит только от вызванной или не вызванной .cacheOnDisk в options. Если не указано, то и кэшировать на диске не будет, даже при "переполнении памяти".

      Порядок вызова опций в options не имеет значения. Как и в конфигурации.

      Delete
  4. А нормально то, что в коде так получается что строчка : .imageScaleType(ImageScaleType.POWER_OF_2)
    выглядит так, что POWER_OF_2 зачеркнутым написанно?

    ReplyDelete
    Replies
    1. Это значит, что этот параметр устарел и его лучше не использовать (в будущем он будет удален). Всю инфу можно найти в Java доках, если они вам видны. В общем, замените POWER_OF_2 на IN_SAMPLE_POWER_OF_2.

      Delete
    2. This element neither has attached source nor attached Javadoc and hence no Javadoc could be found.
      А как можно увидеть javadoc?

      Delete
    3. Попробуйте использовать jar-ку, которая в Example проекте. Там в jar-ке сразу исходникик лежат с java-доками.

      Delete
    4. Не выходит. Она вообще не присоединяется к проекту - не читается(

      Delete
    5. universal-image-loader-1.7.0-with-src.jar

      Delete
    6. А как вы его присоединяете? И какая у вас IDE?

      Delete
    7. У меня Эклипс 4.2.0
      Добавляю через Add JARs... Я что-то не так делаю? =(

      Delete
    8. Надо jar-ки закидывать в папку libs в корне проекта. Из Build Path поудалять все ссылки на jar-ки, они должны автоматически присоединяться.

      Delete
  5. Оч. здорово получилось, по-моему!
    Огромный выбор настроек, меняющих поведение ImageLoader'a + возможность все запустить вообще без настроек.
    Прикрутил подгрузку картинок к приложению буквально с первого запуска (а до этого часа полтора читал туториалы и просматривал структуру классов :)).

    ReplyDelete
    Replies
    1. Все бы так читали туториалы сначала... :) Спасибо.

      Delete
  6. Здравствуйте!
    У меня проблема со ссылками вида:
    http://tonkosti.ru/images/5/54/Caretta_Bar%26Disco%2C_%D0%BE%D1%82%D0%B5%D0%BB%D1%8C_Adora_Golf_Resort_%D0%91%D0%B5%D0%BB%D0%B5%D0%BA.jpg
    Мне выдается ошибка:
    libjpeg error 27 from setjmp [0 0]
    Могли бы вы подсказать как справиться?

    ReplyDelete
    Replies
    1. Здравствуйте. Какие это у вас особенные картинки. Дело не в ссылке, а в том, что андроидовский декодер не может задекодить такую картинку. Почему - не знаю.

      Delete
    2. Мне тут сказали, что CMYK формат не читается ни на каких версиях андроида =(
      http://stackoverflow.com/questions/7608507/in-android-how-to-decode-a-jpeg-in-cmyk-color-format/7634763#7634763

      Delete
  7. Hello Sergey, I've a problem with Image Loader (wich is very fine), I just want to display a local drawable from local. So I put this URL into my String array :
    public static final String[] IMAGES = new String[]{ "drawable://" + R.drawable.app_icon }

    but it doesnt work and I've a null pointer exception..

    Can you tell me how display drawable ?

    ReplyDelete
    Replies
    1. Look here - http://stackoverflow.com/questions/14098993/how-to-use-universal-image-loader-for-loading-resources-locally

      Delete
    2. yes but I don't understand why : "drawable://" + R.drawable.app_icon, // Image from drawables doesn't work... do you??

      Delete
    3. Did you set ExtendedImageDownloader to configuration?

      Delete
    4. yes I'm doing this in UILApplication.java : ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(getApplicationContext())
      .threadPriority(Thread.NORM_PRIORITY - 2)
      .memoryCacheSize(2 * 1024 * 1024) // 2 Mb
      .denyCacheImageMultipleSizesInMemory()
      .discCacheFileNameGenerator(new Md5FileNameGenerator())
      .imageDownloader(new ExtendedImageDownloader(getApplicationContext()))
      .tasksProcessingOrder(QueueProcessingType.LIFO)
      .enableLogging() // Not necessary in common
      .build();

      So..

      Delete
  8. Anonymous12/2/13 14:49

    ...Если открыть изображение, пролистать его в любую сторону, то при повороте экрана изображение не сохраняется. А отображается повернутый рисунок, который открывался раньше, словно пролистывания и не было... Где это можно подправить?

    ReplyDelete
  9. Anonymous12/2/13 17:04

    а... понял.. У Вас в BaseActivity идет переопределение onSaveInstanceState

    ReplyDelete
  10. Скажите, а можно как либо сохранить картинку в файловый кэш?
    Допустим я сделал фотку, хочу её сразу поместить в файловый кэш, можно в принципе сохранить её по пути cacheDir который я задаю при инициализаци ImageLoader-а и прогонять имя теме же NameGenerator-ом, но нет ли более простого решения.

    ReplyDelete
  11. Каким образом у Вас в библиотеке обрабатывается ошибка, возникающая в случае, если в дисковом кэше закончилось место или если дисковый кэш располагался на SD-карте и карта была отключена от девайса?
    Если не обрабатывается, тот каким образом есть возможность обработать, а лучше предупредить данные ошибки из вне?
    Данные ошибки при использования дискового кэша на SD-карте приводят к тому, что картинки перестают загружаться, а хотелось бы, чтобы библиотека просто продолжала загружать картинки без использования кэша.

    ReplyDelete
    Replies
    1. Случаи такие не обрабатываются (что не есть хорошо), занес их в планы.
      На данный момент, чтобы хэндлить случаи отключения SD краты, надо сделать свой кэш (можно унаследоваться от какого-нибудь существующего, и засетить его в конфиг), в котором будет метод типа setCacheDir(File dir). Далее в случае отключения карты - сетить другую папку (StorageUtil.getCacheDirectory() пойдет).

      Следить за тем, не закончилось ли место на карте, тоже придется самому, и чистить кэш в этом случае.

      Над решением проблемы буду думать, но скоро ли будет - не знаю.

      Delete
  12. Здравствуйте, я взял Ваш пример, что бы попробовать что и как, и тут нагрянул вопрос, как в него можно прикрутить возможность сохранения текущего изображения, по нажатию кнопки.

    ReplyDelete
    Replies
    1. Если вы использовали кэширование на диске, то оно уже сохранено в кэше. Остается только скопировать его куда надо. Посмотрите в сторону DiscCacheUtil.

      Delete
  13. Здраствуйте, подскажите есть ли возможность обновить изображение в кеше. скажем загрузили картинку, она попала в кеш, затем эта картинка поменялась, скажем ориентацию например поменяла. Снова пытаемся загрузить ее, но ImageLoader берет все старую картинку из кеша( как ее обновить в кеше?

    ReplyDelete
    Replies
    1. Чтобы форсировать обновление картинки, надо удалить ее из кэшей сначала. Используйте MemoryCacheUtil и DiscCacheUtil для этого. А также посмотрите в сторону LimitedAgeMemoryCache и LimitedAgeDiscCache, которые через указанное время сами удаляют картинку из кэша.
      Учтите, что UIL сначала смотрит в кэш в памяти, потом в дисковый кэш, и потом загружает картинку из сети, если ее не нашлось в кэшах.
      Кэши конфигурятся в конфигурации, а включаются в DisplayImageOptions.

      Delete
  14. Anonymous15/4/13 15:59

    Здаствуйте, подскажите возможно ли получить изображение как BitMap, а не сразу отображать его в ImageView? Дело в том что мне нужно отобразить изображение в ImageView как Background...

    ReplyDelete
    Replies
    1. ImageSize targetSize = ImageSizeUtils.defineTargetSizeForView(imageView, 0 /* or maxImageWidth */, 0 /* or maxImageHeight */)
      ImageLoader.loadImage(..., targetSize, listener)

      В листенере в onLoadingComplete(...) получаете готовую битмапку.

      Delete
  15. Anonymous23/4/13 12:35

    Сергей, спасибо за библиотеку. Есть одна проблема. Не могу правильно настроить размер пула потоков. Список подлагивает. по разному пытался настроить.. но как-то без изменений

    ReplyDelete
    Replies
    1. Одна настройка размера пула не спасет, если пролемы в другом. Обязательно для списков используйте переиспользование вьюх. Вопросы вы можете задавать здесь - http://stackoverflow.com/questions/tagged/universal-image-loader

      Delete
    2. Anonymous23/4/13 14:47

      ViewHolder, ViewStub... Всё имеется. Если закоментировать displayImage то список не лагает.

      config такой
      new ImageLoaderConfiguration.Builder(
      getApplicationContext())
      .threadPoolSize(20).threadPriority(1)

      .defaultDisplayImageOptions(optionsList)
      .memoryCache(new LruMemoryCache(150 * 1024 * 1024)).build();


      DisplayImageOptions optionsList = new DisplayImageOptions.Builder()
      .showStubImage(R.drawable.placeholder)
      .showImageOnFail(R.drawable.placeholder)
      .imageScaleType(ImageScaleType.IN_SAMPLE_POWER_OF_2)
      .showImageForEmptyUri(R.drawable.placeholder).cacheInMemory()
      .cacheOnDisc().build();

      Delete
    3. Anonymous23/4/13 14:48

      .threadPoolSize(20).threadPriority(1) этого нету) уже удалил

      Delete
    4. Размер кэша в памяти - 150 метров? Это вы зря. Максимум 16.
      Повторюсь - всевопросы на StackOverFlow с подробным описанием и примерами кода.

      Delete
  16. Как можно использовать несколько ImageLoaderConfiguration?
    Мне надо один для списков.. что бы указывать размер высоты и ширины кеша в диск.. а второй для галерей, где надо кешировать полный размер

    ReplyDelete
    Replies
    1. Если только создать два инстанса ImageLoader'а. Нужно создать свой класс и унаследовать ImageLoader.

      Delete
    2. Спасибо за совет.

      Delete
  17. Anonymous1/5/13 12:01

    Привет. Почему иногда большие фотки обрезаются при загрузки?

    ReplyDelete