March 11, 2012

#5 - Universal Image Loader. Часть 2 - Конфигурация [RU+EN]

English version - "Universal Image Loader. Part 2 - Configuration"

Предыдущая статья: "Universal Image Loader. Part 1 - Введение"

    Все манипуляции проходят посредством класса ImageLoader. Это singletone, поэтому чтобы получить единственный экземпляр класса надо вызвать метод getInstance(). Перед использованием ImageLoader'а по назначению (для отображения картинок), необходимо проинициализировать его конфигурацией - ImageLoaderConfiguration, с помощью метода init(...). Ну а дальше можно со спокойной душой юзать все вариации метода displayImage(...).

    В общем, самый простой вариант использования ImageLoader'a (с конфигурацией по умолчанию) представлен ниже:
ImageView imageView = ... // вьюха, где будет отображать картинку
String imageUrl = ... // URL картинки (н-р: "http://site.com/image.png", "file:///mnt/sdcard/img/image.jpg")

ImageLoader imageLoader = ImageLoader.getInstance(); // Получили экземпляр
imageLoader.init(ImageLoaderConfiguration.createDefault(context)); // Проинициализировали конфигом по умолчанию
imageLoader.displayImage(imageUrl, imageView); // Запустили асинхронный показ картинки
   
    А теперь рассмотрим полный функционал.

    Как вы уже поняли, сначала ImageLoader надо проинициализировать с помощью объекта конфигурации. Т.к. ImageLoader - синглтон, то инициализировать его нужно один раз за запуск приложения. Я бы рекомендовал делать это в перегруженном Application.onCreate(). Повторная инициализация уже проинициализированного ImageLoader'а не будет иметь никакого эффекта.
    Значит, создаем конфигурацию, а это объект класса ImageLoaderConfiguration. Создаем с помощью Builder'а:
File cacheDir = StorageUtils.getCacheDirectory(context, "UniversalImageLoader/Cache");

ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(getApplicationContext())
            .memoryCacheExtraOptions(480, 800) // width, height
            .discCacheExtraOptions(480, 800, CompressFormat.JPEG, 75) // width, height, compress format, quality
            .threadPoolSize(5)
            .threadPriority(Thread.MIN_PRIORITY + 2)
            .denyCacheImageMultipleSizesInMemory()
            .memoryCache(new UsingFreqLimitedMemoryCache(2 * 1024 * 1024)) // 2 Mb
            .discCache(new UnlimitedDiscCache(cacheDir))
            .discCacheFileNameGenerator(new HashCodeFileNameGenerator())
            .imageDownloader(new BaseImageDownloader(5 * 1000, 30 * 1000)) // connectTimeout (5 s), readTimeout (30 s)
            .defaultDisplayImageOptions(DisplayImageOptions.createSimple())
            .enableLogging()
            .build();
    Рассмотрим каждую настройку:
  •  memoryCacheExtraOptions() используется при декодировании изображений в объекты Bitmap. Чтобы не хранить в памяти полноразмерную картинку, она уменьшается до размера, определяемых из значений параметров ImageView, куда грузится картинка: maxWidth и maxHeight (в первую очередь), layout_width и layout_height (во вторую очередь). Если эти параметры не определены (значения fill_parent и wrap_content считаются неопределенными), то тогда берутся размеры, заданные параметрами из данного memoryCacheExtraOptions() ;
    • Значения по умолчанию - размеры экрана аппарата
  •  discCacheExtraOptions() задает параметры для хранения картинок на "дисковом кэше". По умолчанию на дисковом кэше хранятся полноразмерные картинки. С помощью данной настройки можно задать максимальный размер для хранимых картинок, а также формат сжатия и качество (для JPEG).
  •  threadPoolSize(int) задает размер пула потоков. Каждое задание на загрузку и отображение картинки выполняется в отдельном потоке, а те потоки, в которых происходит загрузка изображений из сети, попадают в пул. Т.о. размер пула определяет количество потоков, работающих одновременно. Задание большого размера пула может существенно снизить скорость работы UI, например в списке может подтормаживать прокрутка. 
    • Значения по умолчанию - 5
  •  threadPriority(int) задает приоритет в системе (от 1 до 10) всех потоков, в которых выполняются задачи;
    • Значения по умолчанию - 4
  •  вызов denyCacheImageMultipleSizesInMemory() накладывает запрет на хранение в памяти разных размеров одной и той же картинки. Т.к в дисковом кэше хранятся полноразмерные картинки, а при загрузке в память они уменьшаются до размеров ImageView, в которых они должны отобразиться, то бывают случаи, что одну и ту же картинку необходимо отобразить сначала в маленькой вьюшке, а потом в большой. При этом в памяти будут хранится две Bitmap'ки разных размеров, представляющие одинаковую картинку. Это поведение по умолчанию. Инструкция  denyCacheImageMultipleSizesInMemory() обеспечивает удаление из кэша в памяти предыдущего размера загружаемой картинки.
  •  с помощью memoryCache(...) можно задать реализацию кэша в памяти. Можно воспользоваться готовыми решениями (все они представляют собой реализации кэша ограниченного размера, где при превышении размера кэша из него удаляется объект по определенному алгоритму):
    • FIFOLimitedCache (удаляется объект по принципу First-In-First-Out)
    • LargestLimitedCache (удаляется самый большой по размеру объект)
    • UsingAgeLimitedCache (удаляется объект с самой давней датой доступа)
    • UsingFreqLimitedCache (удаляется самый редко используемый объект)
    Либо можно подсунуть свой вариант кэша, реализовав интерфейс MemoryCacheAware<String, Bitmap>;
    • Значение по умолчанию - UsingFreqLimitedCache с ограничением памяти в 2 Мб
  •  memoryCacheSize(int) задает максимальный размер кэша в памяти. В этом случае используется кэш по умолчанию -  UsingFreqLimitedCache.
    • Значение по умолчанию - 2 Мб 
  • с помощью discCache(...) можно задать реализацию кэша на файловой системе. Можно воспользоваться готовыми решениями (в них файлы, соответствующие определенным URL-ам, именуются как хеш-коды этих URL-ов):
  • Либо можно подсунуть свою реализацию кэша, реализовав интерфейс DiscCacheAware.
  •  discCacheSize(int) задает максимальный размер кэша на файловой системе. В этом случае используется TotalSizeLimitedDiscCache.
  •  discCacheFileCount(int) задает максимальное количество файлов в дисковом кэше. В этом случае используется FileCountLimitedDiscCache.
  •  с помощью discCacheFileNameGenerator(...)  можно задать свой генератор имен для файлов закешированных на диске картинок: на входе получается URI картинки, на выходе надо выдать уникальное имя для файла (FileNameGenerator).
    Есть готовые варианты:
    • HashCodeFileNameGenerator - в качестве имени файла берется java хеш-код URI картинки
    • Md5FileNameGenerator - в качестве имени файла генерируется хеш-код по алгоритму MD5
    По умолчанию используется HashCodeFileNameGenerator
  • с помощью imageDownloader(...) можно задать свою реализацию загрузчика картинок: нужно представить свою имплементацию интерфейса ImageDownloader.
    Можно воспользоваться готовыми реализациями (по надобности их также можно унаследовать): По умолчанию используется BaseImageDownloader
  • с помощью defaultDisplayImageOptions(...) можно задать опции отображения картинок, которые будут использоваться для всех вызовов метода displayImage(...), где не были переданы кастомные опции. Подробнее, что это за эти опции, будет рассказано ниже.
  •  enableLogging() включает подробное логгирование процесса работы ImageLoader'a.
    Мы можем сами сконструировать объект конфигурации, либо довериться разработчику (т.е. мне) и воспользоваться конфигурацией по умолчанию:
ImageLoaderConfiguration config = ImageLoaderConfiguration.createDefault(context);

    Итак, конфигурация создана. Теперь ею можно проинициализировать ImageLoader:
ImageLoader.getInstance().init(config);
Вот и все, ImageLoader готов к использованию. Но об этом Я расскажу уже в следующей статье.

Следующая статья: "Universal Image Loader. Part 3 - Usage"
Исходники проекта доступны здесь.

99 comments:

  1. Не могу понять почему происходит именно так. Возможно это известная ситуация. Используестя андроид 2.1. Вот кусок лога

    08-19 20:46:42.249: I/ImageLoader(469): Load image from Internet [http://img...jpg_48x80]

    08-19 20:46:42.500: I/global(469): Default buffer size used in BufferedInputStream constructor. It would be better to be explicit if an 8k buffer is required.

    На 4.1 картинки показываются. Возможно я что-то пропустил и есть минимальные требования к SDK?

    ReplyDelete
    Replies
    1. Минимальный API Level - 1.5. Не понял, в чем проблема? Не отображаются картинки на 2.1? В приведенных логах никаких ошибок не вижу. Или это все, что есть в логах?

      Delete
    2. Да именно так на android 2.1 картинки не показываются.
      Вот лог http://codepaste.ru/11565/
      Предпологаю что причина в фабрике SkImageDecoder::Factory returned null

      Начиная с android 2.2 картинки отображаются.

      Delete
    3. А пример проекта с GitHub (который UniversalImageLoaderExample) у вас работает на 2.1? Показывает картинки?

      Delete
  2. Скажите а можно добавить в loader свой httpclient? Например, если в приложении уже используется кастомный httpclient как синглтон, чтоб уже можно было его передать в ваш лоадер.

    ReplyDelete
    Replies
    1. Да, можно. Надо в конфигурацию передать свой imageDownloader. Можно воспользоваться HttpClientImageDownloader, который в конструктор принимает HttpClient.

      Delete
  3. Подскажите пожалуйста, можно ли указать путь к картинке, которая находится в assets ?
    т.к. при file:///android_asset/roks.png
    выдает ошибку
    /android_asset/roks.png (No such file or directory)
    хотя файл присутствует. при изменении на file:///mnt/sdcard/ все работает прекрасно
    в чем может быть ошибка ?

    ReplyDelete
    Replies
    1. Путь типа file://... указывает на файловую систему девайса. Assets папка лежит в приложении и не может быть доступна, как обычная папка на файловой системе.
      Тут есть два выхода:
      - в начале скопировать все файлы из assets на SD карту, а потом доступаться к ним через file://...
      - создать свой ImageDownloader (и засетить его в конфигурации), перегрузить InputStream getStreamFromOtherSource(URI imageUri), в нем по URI найти нужный вам файл в assets и перегнать его в InputStream. Для картинок из assets вызывать displayImage(...) c URI типа этого: "assets://roks.png".

      Скоро примерчик для второго случая включу в Example проект на GitHub.

      Delete
  4. Почему может не сохранять картинки в папке для кеша? пермишн стоит. папка существует (перепроверено много раз). Вот мои конфиги

    ImageLoaderConfiguration imageLoaderconfig = new ImageLoaderConfiguration.Builder(context)
    .memoryCacheExtraOptions(50, 50) // width, height
    .discCacheExtraOptions(50, 50, Bitmap.CompressFormat.PNG, 75)
    .threadPoolSize(3)
    .threadPriority(Thread.NORM_PRIORITY - 2)
    .denyCacheImageMultipleSizesInMemory()
    .offOutOfMemoryHandling()
    .memoryCache(new UsingFreqLimitedMemoryCache(5 * 1024 * 1024)) // 2 Mb
    .discCacheSize(15000)
    .discCacheFileCount(50)
    .discCache(new UnlimitedDiscCache(cacheDir))
    .discCacheFileNameGenerator(new HashCodeFileNameGenerator())
    .defaultDisplayImageOptions(DisplayImageOptions.createSimple())
    .enableLogging()
    .build();

    ReplyDelete
    Replies
    1. Readme читали? https://github.com/nostra13/Android-Universal-Image-Loader#useful-info
      Кэшировать или не кэшировать картинку выставляется в DisplayImageOptions. В простых опциях - DisplayImageOptions.createSimple() - кэширование ни в памяти, ни на диске не включено.

      Также вызовы в вашей конфигурации:
      .discCacheSize(15000)
      .discCacheFileCount(50)
      .discCache(new UnlimitedDiscCache(cacheDir))
      перекрывают друг друга. Информация об этом есть в Java доках, и в логах выводятся варнинги. Выбирайте что-то одно.

      Delete
    2. Ой ёй, извините что так сильно натупил.. Ушел читать доки.

      Delete
  5. Anonymous14/1/13 18:10

    Привет, спасибо за библиотечку.
    Есть один вопрос. У меня почему-то фотки (которые я снимаю и складываю во внутреннее хранилище) отображаются в неправильной ориентации. Они всегда повернуты на 90 градусов.

    Вы с этим не сталкивались? Может подскажете как это исправить?

    Спасибо.

    ReplyDelete
    Replies
    1. Привет. Скорее всего это из-за того, что поворот хранится в EXIF метаданных, а UIL никак не учитывает EXIF при отображении (пока). Вы можете создать свой BitmapDisplayer, где будете учитывать поворот, и засетить его в конфигурацию.

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

      Delete
  6. Anonymous15/1/13 08:59

    Спасибо Сергей.

    ReplyDelete
  7. Anonymous19/1/13 11:59

    при быстрой прокрутки, картинки сбрасываються на дефолтные, как оставить картинки на своих местах ?

    ReplyDelete
  8. Anonymous30/1/13 13:36

    Подскажите пожалуйста есть ли возможность установить listener на загрузку ряда картинок? Чтоб, к примеру, ProgressDialog скрывался только после загрузки всей очереди , а не после каждой картинки

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

      Delete
  9. Кэширую изображению вот сюда: cacheDir = StorageUtils.getCacheDirectory(Context)
    как из этого каталога определенно получить изображение?
    Там они все храняться с непонятными именами..

    ReplyDelete
    Replies
    1. File image = DiscCacheUtil.findInCache(imageUri, imageLoader.getDiscCache());

      Delete
  10. Ссылка на изображение содержит кириллицу.
    В списке все отображается нормально. В ImageLoaderConfiguration указал memoryCache. Когда пытаюсь найти путь к изображению в кэшэ, использую этот метод
    DiscCacheUtil.findInCache(logoUrl.get(position), ImageLoader.getInstance().getDiscCache())
    он возвращает null.
    В чем может быть проблема?

    ReplyDelete
    Replies
    1. Читайте Readme - Useful Info, где и как включается кэш.
      В конфигурации вы лишь настраиваете кэши. А уж кэшировать или нет - это в DisplayImageOptions. И не забывайте разницу между кэшированием в памяти и на диске.

      Delete
    2. В DisplayImageOptions указал cacheInMemory()

      Delete
    3. Нет, вот все, что указал:
      options = new DisplayImageOptions.Builder()
      .showStubImage(R.drawable.stub_image)
      .showImageForEmptyUri(R.drawable.image_for_empty_url)
      .cacheInMemory()
      .cacheOnDisc()
      .imageScaleType(ImageScaleType.IN_SAMPLE_POWER_OF_2).build();

      Delete
    4. Что-то так и не понял, в чем косяк... Может быть так, что это проблема заключается в том, что путь к картинке содержит кириллицу?

      Delete
    5. Конфигурация какая у вас?

      Delete
    6. File cacheDir = StorageUtils.getCacheDirectory(EngineBrowseByOnline.this);

      ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(getApplicationContext())
      .memoryCacheExtraOptions(100, 100)
      .threadPoolSize(5)
      .threadPriority(Thread.MIN_PRIORITY + 2)
      .offOutOfMemoryHandling()
      .memoryCache(new UsingFreqLimitedMemoryCache(2 * 1024 * 1024))
      .discCache(new UnlimitedDiscCache(cacheDir))
      .discCacheFileNameGenerator(new HashCodeFileNameGenerator())
      .discCacheFileCount(100)
      .defaultDisplayImageOptions(DisplayImageOptions.createSimple()).enableLogging()
      .build();

      Delete
    7. Да, это проблема связана с кириллицей. Пофикшу в следующей версии.

      Пока решение для вас такое:

      String encodedUrl = Uri.encode(logoUrl.get(position), "@#&=*+-_.,:!?()/~'%");
      DiscCacheUtil.findInCache(encodedUrl, ImageLoader.getInstance().getDiscCache());

      Delete
    8. Спасибо!
      Жду следующей версии..

      Delete
  11. Anonymous18/3/13 14:08

    Сергей, спасибо! Отличная библиотека! Подскажите, а если мне не нужно сохранять картинки в кеше. Как правильно настроить объект?

    ReplyDelete
    Replies
    1. Где не нужно кэшировать? В памяти или на файловой системе?
      В DisplaytImageOptions есть .cacheInMemory() и .cacheOnDisc(). Если эти опции не включать, то и кэшироваться не будет.

      Delete
    2. Anonymous18/3/13 14:42

      ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(ImagePagerActivity.this).build();
      imageLoader.init(config);

      DisplayImageOptions options = new DisplayImageOptions.Builder()
      .showImageForEmptyUri(R.drawable.ic_empty)
      .showImageOnFail(R.drawable.ic_error)
      .resetViewBeforeLoading().cacheOnDisc()
      .imageScaleType(ImageScaleType.IN_SAMPLE_INT)
      .bitmapConfig(Bitmap.Config.RGB_565)
      .displayer(new FadeInBitmapDisplayer(300))
      .build();

      Я рисую изображение, показываю его пользователю с помощью DisplayImageOptions. После я рисую уже новое изображение, но сохраняю его с тем же именем и в тоге получаю старую картинку.

      Delete
    3. Anonymous18/3/13 14:45

      Все, понял свою ошибку. Надо было убрать .resetViewBeforeLoading().cacheOnDisc().

      Delete
  12. Anonymous21/5/13 13:30

    Сергей, привет. За библиотеку спасибо! Подскажи, пожалуйста, столкнулся с проблемой. Если изображение уже есть в кэше и по этому же url подсунуть другое изображение, то оно не загрузится, а отобразится старое из кэша.
    Вот, например: Есть файл site.ru/image/sample.jpg на котором изображена буква А, это изображение загрузили и сохранили в кэш. Потом по этому же адресу на веб сервере положили изображение с буквой Б (т.е. содержимое изменилось, а адрес нет). Так вот UIL не загружает новый файл, потому что url не изменился и показывает старое изображение из кэша. Как такое решить?

    ReplyDelete
    Replies
    1. Надо удалить сперва изображения из кэшей. Посмотри в сторону MemoryCacheUtil и DiscCacheUtil. Есть соответствующие имплементации кэшей (LimitedAge...), которые будут удалять закэшированные картинки через указанное время, тем самым форсируя их повторную загрузку с сервера.

      Delete
    2. Anonymous21/5/13 13:53

      LimitedAge.. видимо в более поздних версиях библиотеки появилось, у меня версия 1.5.2 :) Буду обновляться, спасибо

      Delete
  13. Здравствуйте!
    Подскажите, в чем может быть проблема.
    Сделал gridview с загрузкой изображений. url лежат в txt файле.
    Все работает, но при прокрутке экрана изображения меняются, на место старого ставится новое.

    Код:

    public class MainActivity extends Activity {


    ImageLoader imageLoader;
    GridView gv;
    ArrayList imageUrls = getUrlList();

    @Override
    public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    imageLoader = ImageLoader.getInstance();
    imageLoader.init(ImageLoaderConfiguration.createDefault(getApplicationContext()));


    gv = (GridView) findViewById(R.id.gridView);
    gv.setAdapter(new ImageAdapter());


    public class ImageAdapter extends BaseAdapter {


    @Override
    public int getCount() {
    return imageUrls.size();
    }

    @Override
    public Object getItem(int position) {
    return null;
    }

    @Override
    public long getItemId(int position) {
    return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
    final ImageView imageView;
    if (convertView == null) {
    imageView = (ImageView) getLayoutInflater().inflate(R.layout.grid_image, parent, false);
    } else {
    imageView = (ImageView) convertView;
    }

    imageLoader.displayImage(imageUrls.get(position), imageView);

    return imageView;
    }


    }

    public ArrayList getUrlList() {
    //Метод для считывания url изображений в ArrayList.
    }
    }

    ReplyDelete
    Replies
    1. В DisplayImageOptions надо включить .resetViewBeforeLoading().

      Delete
  14. FileCountLimitedDiscCache (ограниченный по размеру кэш)
    TotalSizeLimitedDiscCache (ограниченный по количеству файлов кэш)

    я так понимаю здесь перепутаны местами описания

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

    ReplyDelete
    Replies
    1. Ничем не могу помочь.

      Delete
    2. Ой, да я удалил уже коммент) У меня другой вопрос, как сделать принудительный кеш на сдкарту? Сейчас кеширует куда-то, но не понимаю куда, посмотреть только можно в свойствах приложения. А надо бы, чтобы кешировало в отдельную папку на диске.

      imageLoader = ImageLoader.getInstance(); // Получили экземпляр

      File cacheDir = StorageUtils.getOwnCacheDirectory(this, "UniversalImageLoader/Cache");

      ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(getApplicationContext())
      //.memoryCacheExtraOptions(480, 800) // width, height
      //.discCacheExtraOptions(480, 800, CompressFormat.JPEG, 75, null) // width, height, compress format, quality
      .threadPoolSize(5)
      .threadPriority(Thread.MIN_PRIORITY + 2)
      .denyCacheImageMultipleSizesInMemory()
      //.memoryCache(new UsingFreqLimitedMemoryCache(4 * 1024 * 1024)) // 2 Mb
      .discCache(new UnlimitedDiscCache(cacheDir))
      .discCacheFileNameGenerator(new HashCodeFileNameGenerator())
      .imageDownloader(new BaseImageDownloader(this, 5 * 1000, 30 * 1000)) // connectTimeout (5 s), readTimeout (30 s)
      .defaultDisplayImageOptions(DisplayImageOptions.createSimple())
      .enableLogging()
      .build();

      imageLoader.init(config);

      Папки не создается, может я что-то делаю не так?

      Delete
    3. У вас разрешение на запись на карточку прописано?

      Delete
    4. Если имеется ввиду

      То да, прописано. Папка создается, но туда ничего не помещается. Может что-то стоит отключить в ImageLoaderConfiguration?

      Спасибо.

      Delete
    5. android.permission.WRITE_EXTERNAL_STORAGE

      Delete
    6. Значит вы наверное прочто не включаете кэширование. Его надо включать в display options.

      Delete
    7. Подскажите пожалуйста как это сделать? То, что я нашел DisplayImageOptions, там включил cacheOnDisc(), но мне выдает "The method cacheOnDisc() from the type DisplayImageOptions.Builder is deprecated". Что делать? И если я создам DisplayImageOptions, то мне всегда его надо добавлять при загрузке каждой картинки?

      Delete
    8. вроде разобрался, поставил в DisplayImageOptions - cacheOnDisc(true), теперь кеширует на диск. Буду проверять как работает)

      Delete
  16. Если в проекте надо грузить картинки с разными конфигами - в случае вашего синглтона это никак?

    ReplyDelete
    Replies
    1. А чем конфиги отличаться будут?

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

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

    ReplyDelete
  19. Anonymous2/10/13 20:36

    Когда вызывается ImageLoadingListener.onLoadingCancelled(String, View)? (я использую ViewPager, и иногда, при одновременной загрузке нескольких картинок, одна из них не не загружается, а вызывается onLoadingCancelled)

    ReplyDelete
    Replies
    1. ImageLoadingListener.onLoadingCancelled(...) вызывается когда ImageView была переиспользована для загрузки новой картинки.

      Delete
    2. как этого можно избежать? я столкнулся с подобной ошибкой..

      Delete
    3. Это не ошибка. Или у вас картинки не отображаются?

      Delete
  20. Здравствуйте! крутая штука, спасибо. Столкнулся с проблемой: если в системных настройках почистить кэш, то лоадер не находит папку cach, в логи пишет FileNotFoundException. Хотелось бы использовать именно эту папку для возможности чистки кэша из настроек. Есть ли решение кроме как изменять исходники?

    ReplyDelete
    Replies
    1. Здравствуйте. Я уже не помню точно, но папка вроде должна пересоздаваться, если ее вдруг удалили.

      Delete
    2. Создается она при старте приложения (при инициализации лоадера), а если удалить ее при запущенном приложении то в логи сыпет ексепшены а вместо фоток - черный экран. Буду искать решение, спасибо за ваши труды.

      Delete
    3. Пожалуйста.
      Какая версия библиотеки? Логика по воссоздания папки кэша точно есть: LoadAndDisplayImageTask.getImageFileInDiscCache()

      Delete
    4. Туплю - либа была 1.6.1 - в onResume() сам создавал папку cach. Скачал 1.8.6 - все решилось. Спасибо за терпение)

      Delete
  21. Здравствуйте, в одном из первых постов Вы написали, что добавили в пример как работать с assets. Если не трудно укажите пожалуйста где этот пример конкретно лежит, я не могу найти указанный Вами тут
    https://github.com/nostra13/Android-Universal-Image-Loader/issues/123 - ExtendedImageDownloader

    Спасибо

    ReplyDelete
    Replies
    1. Здравствуйте. Теперь UIL поддерживает загрузку из assets из коробки. Просто передавайте Uri типа "assets://..."

      Delete
    2. Спасибо большое

      Delete
  22. Здравствуйте, спасибо за библиотеку. Можно ли ее настроить чтобы также показывать thumbnails видео файлов? Вот вопрос на stackoverflow:
    http://stackoverflow.com/questions/20931585/is-it-possible-to-display-video-thumbnails-using-universal-image-loader-and-how

    Спасибо!

    ReplyDelete
    Replies
    1. Уже сделал. Я сделал для этого новый ImageDecoder, ответ можно посмотреть в том же линке.

      Delete
    2. Anonymous19/2/14 21:08

      Здравствуйте Сергей, спасибо за библиотеку. Как загрузить картины с хорошим качеством ?

      Delete
    3. Здравствуйте. Они загружаются в дисковый кэш в полном качестве, в кэш в памяти ресайзятся для экономии памяти. Чтобы не ресайзились используйте DisplayImageOptions.imageScaleType(ImageScaleType.NONE).

      Delete
  23. Anonymous19/2/14 23:01

    Попробовал поставить , но качество тоже самое
    DisplayImageOptions specialOptions = new DisplayImageOptions.Builder().cloneFrom(options).imageScaleType(ImageScaleType.NONE).build(); (LoadAndDisplayImageTask класс) и .cacheOnDisc().imageScaleType(ImageScaleType.NONE) (ImagePagerActivitu класс)

    ReplyDelete
    Replies
    1. Anonymous20/2/14 21:34

      Пожалуйста скажите где поставить эго

      Delete
  24. Anonymous22/2/14 12:25

    Здравствуйте Сергей , очень приятно что есть такие программисты как вы , я хотель получать фотографии с очень хорошим качеством , как мне это сделать попробовал DisplayImageOptions.imageScaleType(ImageScaleType.NONE) но все тоже самое , как мне быть где конкретно это поставить , может быть на еще что то поменять .Заранее Спасибо

    ReplyDelete
    Replies
    1. Здравствуйте, а что "то же самое"? Почему вы думаете, что показываются картинки не в полном качестве? Покажите свой конфиг, опции, сам код вызова imageLoader.

      Delete
    2. Anonymous22/2/14 22:41

      private boolean downloadSizedImage(File targetFile, int maxWidth, int maxHeight) throws IOException {
      // Download, decode, compress and save image
      ImageSize targetImageSize = new ImageSize(maxWidth, maxHeight);
      DisplayImageOptions specialOptions = new DisplayImageOptions.Builder().cloneFrom(options).imageScaleType(ImageScaleType.NONE).build(); //// Здесь
      Это в LoadAndDisplayImageTask и


      options = new DisplayImageOptions.Builder()
      .showImageForEmptyUri(R.drawable.ic_empty)
      .showImageOnFail(R.drawable.ic_error).resetViewBeforeLoading()
      .cacheOnDisc().imageScaleType(ImageScaleType.NONE) //// Здесь
      .bitmapConfig(Bitmap.Config.RGB_565)
      .displayer(new FadeInBitmapDisplayer(300)).build(); а это в ImagePagerActivity

      Delete
    3. Почему вы думаете, что показываются картинки не в полном качестве?

      Delete
    4. Anonymous22/2/14 22:52

      Я точно не уверен , а ImageScaleType.NONE поставлены в правильных местах ?

      Delete
    5. Исходники менять не нужно.
      Я так и не увидел вашей конфигурации и кода вызова displayImage().

      Delete
  25. Anonymous26/2/14 19:28

    Сергей Здравствуйте , спасибо за библиотеку , я отключил кэш .cacheInMemory(false) , .cacheOnDisc(false) в DisplayImageOptions но кэш заполняется в чем проблема ?

    ReplyDelete
  26. Здравствуйте, Сергей!
    Есть ли возможность использовать в качестве имени не MD5 от URL, а MD5 от самой картинки, от ByteArray, скажем.

    ReplyDelete
    Replies
    1. Здравствуйте. Есть возможность, но это будет неэффективно, т.к. вы должны будете делать дополнительный запрос. А вообще за имена отвечает FileNameGenerator, который можно передать в конфигурацию..

      Delete
    2. В том то и дело, что у меня есть URL и Hash, в случае, если Hash поменялся, мне необходимо загрузить новую картинку. Посему я бы хотел переложить эту работу на Ваш Loader, т.е давать ему хеш, который я получаю от сервера и URL, если хеш той картинки, что я получаю от сервера не совпадает с тем что у меня есть, то я по тому же самому URL загружаю новую. Расскажите, пожалуйста, какая есть для этого возможность.

      Delete
    3. FileNameGenerator я смотрел с самого началаю Иначе бы не писал, т.к. в качестве параметра используется URI, как мне использовать hash от самой картинки в качестве имени, подскажите, пожалуйста?

      Delete
    4. Нет такой возможности. Когда в начале мы проверяем наличие картинки в кэше на диске - у нас есть только URI. По нему мы генерим имя файла и потом ищем этот файл в кэше, чтобы понять, нужно ли загружать картинку из сети. Чтобы генерировать имена исходя их самой картинки - ее по-любому надо загрузить сначала, т.е. смысл дискового кэша теряется. Можете его просто отключить тогда.

      Delete
  27. Добрый День, Сергей! Спасибо за библиотеку!
    У меня следующая задача, на сайте изображения хранятся по папкам, предполагаемый уровень вложенности не будет превышать 2, то есть есть каталоги по датам, а в них определенные темы. Выкачивая эти изображения хотелось бы повторить структуру каталогов и на устройстве для дальнейшей обработки как даты, так и названия тем в приложении. Подскажите, пожалуйста, наилучший способ этого добиться, если это возможно, а если нет, то можно ли что-то предпринять для решения этой задачи? Заранее спасибо за ответ!

    ReplyDelete
    Replies
    1. Добрый. Какую имплементацию дискового кэша вы используете? Если дефолтную (UnlimitedDisckCache), то можете унаследоваться от нее, перегрузить метод [File get(String imageUri)] и там исходя из URL возвращаться файл в нужной папке (файл не обязательно должен существовать, этот метод вызывается когда UIL хочет получить объект файла для такого-то URL, и если тот не существует, то этот файл создается).
      Т.е. метод может выглядет примерно так:

      @Override
      public File get(String imageUri) {
      String fileName = fileNameGenerator.generate(imageUri);
      File dir = cacheDir;

      ... // Определяем из URL нужно ли создавать подпапку
      dir = new File(dir, "subdir"); // Если да, создаем подпапку
      if (!dir.exists()) {
      dir.mkdirs();
      }

      return new File(dir, fileName);
      }

      Свой кэш потом надо засетить в конфигурацию.

      Delete
    2. Да, это то, что нужно было! Спасибо!

      Еще 1 уточняющий вопрос, какая реакция при достижении установленного размера дискового кэша? То есть он очищается полностью или только его часть?

      Delete
    3. Удаляется файлы, которые использовались максимально давно. Удаляются, пока общий размер кэша не вложится в лимит.

      Delete
  28. 11-30 19:46:30.608: E/ImageLoader(22224): URI: content://com.android.contacts/contacts/1326/photo/photo, calling user: com.mylauncher, calling package:com.mylauncher
    11-30 19:46:30.608: E/ImageLoader(22224): java.lang.IllegalArgumentException: URI: content://com.android.contacts/contacts/1326/photo/photo, calling user: com.mylauncher, calling package:com.mylauncher
    11-30 19:46:30.608: E/ImageLoader(22224): at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:167)
    11-30 19:46:30.608: E/ImageLoader(22224): at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:137)
    11-30 19:46:30.608: E/ImageLoader(22224): at android.content.ContentProviderProxy.query(ContentProviderNative.java:366)
    11-30 19:46:30.608: E/ImageLoader(22224): at android.content.ContentResolver.query(ContentResolver.java:370)
    11-30 19:46:30.608: E/ImageLoader(22224): at android.content.ContentResolver.query(ContentResolver.java:313)
    11-30 19:46:30.608: E/ImageLoader(22224): at android.provider.ContactsContract$Contacts.openContactPhotoInputStream(ContactsContract.java:1985)
    11-30 19:46:30.608: E/ImageLoader(22224): at android.provider.ContactsContract$Contacts.openContactPhotoInputStream(ContactsContract.java:2016)
    11-30 19:46:30.608: E/ImageLoader(22224): at com.nostra13.universalimageloader.core.download.BaseImageDownloader.getStreamFromContent(BaseImageDownloader.java:187)
    11-30 19:46:30.608: E/ImageLoader(22224): at com.nostra13.universalimageloader.core.download.BaseImageDownloader.getStream(BaseImageDownloader.java:90)
    11-30 19:46:30.608: E/ImageLoader(22224): at com.nostra13.universalimageloader.core.LoadAndDisplayImageTask.downloadImage(LoadAndDisplayImageTask.java:290)
    11-30 19:46:30.608: E/ImageLoader(22224): at com.nostra13.universalimageloader.core.LoadAndDisplayImageTask.tryCacheImageOnDisk(LoadAndDisplayImageTask.java:273)
    11-30 19:46:30.608: E/ImageLoader(22224): at com.nostra13.universalimageloader.core.LoadAndDisplayImageTask.tryLoadBitmap(LoadAndDisplayImageTask.java:229)
    11-30 19:46:30.608: E/ImageLoader(22224): at com.nostra13.universalimageloader.core.LoadAndDisplayImageTask.run(LoadAndDisplayImageTask.java:135)
    11-30 19:46:30.608: E/ImageLoader(22224): at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1076)
    11-30 19:46:30.608: E/ImageLoader(22224): at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:569)
    11-30 19:46:30.608: E/ImageLoader(22224): at java.lang.Thread.run(Thread.java:856)

    В чем ошыбка?

    ReplyDelete
    Replies
    1. Вызов идет з адаптера который наследует ArrayAdapter у функции getView
      Вот код вызова лоадера:

      String photoContact;
      try {
      photoContact = ContactsController.getPhotoUri(context, rowItem.getContactId()).toString();
      ImageLoader.getInstance().displayImage(photoContact, holder.imagePhoto);
      } catch (Exception e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
      }

      И сама функция возврвта URI:

      /**
      * @return Return the contact photo URI by CONTACT_ID
      */
      public static Uri getPhotoUri(Context context, int contactId) {
      try {
      Cursor cur = context.getContentResolver().query(
      ContactsContract.Data.CONTENT_URI,
      null,
      ContactsContract.Data.CONTACT_ID + "=" + contactId + " AND "
      + ContactsContract.Data.MIMETYPE + "='"
      + ContactsContract.CommonDataKinds.Photo.CONTENT_ITEM_TYPE + "'", null,
      null);
      if (cur != null) {
      if (!cur.moveToFirst()) {
      return null; // no photo
      }
      } else {
      return null; // error in cursor process
      }
      } catch (Exception e) {
      e.printStackTrace();
      return null;
      }
      Uri person = ContentUris.withAppendedId(ContactsContract.Contacts.CONTENT_URI, contactId);
      return Uri.withAppendedPath(person, ContactsContract.Contacts.Photo.CONTENT_DIRECTORY);
      }

      Использую дефолтовые настройки... з интернета грузит хорошо, а з самого телефона никак ((( Может я сто то не так делаю?

      Delete
  29. Отличная, качественная работа!

    ReplyDelete
  30. Привет. Подскажи пожалуйста. Очень часто выскакивает ошибка OutOfMemoryError по твоей инструкции в Github проблему решить не получилось.

    ReplyDelete
    Replies
    1. Привет. Тогда вряд ли чем-то смогу помочь. Возможно где-то есть утечки памяти, попрофайли память.

      Delete
  31. Здравствуй. Помоги плиз.
    DisplayImageOptions options = new DisplayImageOptions.Builder()
    .showImageForEmptyUri(R.drawable.appicon)
    .showImageOnFail(R.drawable.appicon)
    .cacheInMemory(true)
    .imageScaleType(ImageScaleType.EXACTLY_STRETCHED)
    .displayer(new RoundedBitmapDisplayer(20))
    .build();

    ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(getApplicationContext())
    .defaultDisplayImageOptions(options)
    .build();

    ImageLoader.getInstance().init(config);

    а вот фрагмент из xml

    При таком xml картинка не отображается.
    Но стоит только сделать например android:layout_height="50dp" картинка сразу отображается, но непропорционального размера, а растягивается на 50 в высоту и на весь экран в ширину. Но я хочу использовать wrap_content, и чтобы картинка отображалась не растянутой, а пропорциональной. Почему так не работает

    ReplyDelete
  32. Здравстуйте!

    Объясните, пожалуйста начинающему как в своем кастомизированном адаптере для списка заполнить у ViewHolder'а поле с изображением? Ведь получается, если я ничего не напутал, что библиотека работает только из активити (так как ей нужен контекст). Где тогда получать инстанс ImageLoader.getInstance() и задавать для него конфиг?

    метод getView адаптера примерно такой:

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
    if (convertView == null) {
    LayoutInflater inflater = (LayoutInflater) getContext()
    .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    convertView = inflater.inflate(R.layout.news_row_layout,
    parent, false);

    ViewHolder viewHolder = new ViewHolder();

    viewHolder.title = (TextView) convertView.findViewById(R.id.news_name);
    viewHolder.announce = (TextView) convertView.findViewById(R.id.news_annotation);
    viewHolder.date = (TextView) convertView.findViewById(R.id.news_date);
    viewHolder.imageNews = (ImageView) convertView.findViewById(R.id.image_of_news);

    convertView.setTag(viewHolder);
    }

    ViewHolder holder = (ViewHolder) convertView.getTag();

    EntryNews news = list.get(position);

    holder.title.setText(news.getTitle());
    holder.announce.setText(news.getAnnounce());
    holder.date.setText(news.getDate());

    // Как здесь при помощи библиотеки загрузить изображение в imageNews ?

    return convertView;
    }

    ReplyDelete
    Replies
    1. Библиотека работает отовсюду, надо просто передать ей ImageView и URL картинки. Ну и опции возможно.
      Т.е. как-то так - ImageLoader.getInstance().displayImage(imageUrl, viewHolder.imageNews)

      Глобальную конфигурацию стоит настраивать с Application классе или в первой Activity.
      Все это можно почитать в Readme и посмотреть в примерном проекте - https://github.com/nostra13/Android-Universal-Image-Loader/tree/master/sample


      Delete
  33. Приветствую!
    imageLoader.init(ImageLoaderConfiguration.createDefault(context));
    context - что это за переменная и для чего она?

    .discCacheExtraOptions вроде как уже не нужен? Чем он отличается от .diskCacheExtraOptions и почему, когда я пытаюсь сделать .diskCacheExtraOptions(480, 800, CompressFormat.JPEG), он CompressFormat.JPEG подчеркивает красным, а при замене на Bitmap.CompressFormat.JPEG вообще отказывается работать. Как быть?

    ReplyDelete
  34. Добрый день!
    Как загрузить картинку (BitMap) по линку?

    Я пробовал так:
    ImageLoader imageLoader = ImageLoader.getInstance();
    if (!imageLoader.isInited()) {
    imageLoader.init(ImageLoaderConfiguration.createDefault(context));
    }
    BitMap image = imageLoader.loadImageSync("Ссылка на картинку");

    Но картинку не подгружает (((

    ReplyDelete