Кеширование изображений Android

Ответы:

177

А теперь изюминка: используйте системный кеш.

URL url = new URL(strUrl);
URLConnection connection = url.openConnection();
connection.setUseCaches(true);
Object response = connection.getContent();
if (response instanceof Bitmap) {
  Bitmap bitmap = (Bitmap)response;
} 

Предоставляет как память, так и кеш флэш-памяти, совместно используемый браузером.

гр. Я бы хотел, чтобы кто-нибудь сказал мне это до того, как я написал свой собственный кеш-менеджер.

Edrowland
источник
1
Вау, это был невероятно элегантный способ сделать это, большое спасибо. Он ни в коей мере не медленнее, чем мой собственный простой менеджер кеша, и теперь мне не нужно выполнять уборку в папке SD-карты.
Кевин Рид
11
connection.getContent()всегда возвращает для меня InputStream, что я делаю не так?
Tyler Collier
3
Если бы я мог теперь также установить дату истечения срока действия контента для кеша, моя жизнь была бы намного проще :)
Януш
11
@Scienceprodigy не знаю, что это за BitmapLoader, конечно, нет ни в одной известной мне стандартной библиотеке Android, но, по крайней мере, это привело меня в правильном направлении. Bitmap response = BitmapFactory.decodeStream((InputStream)connection.getContent());
Стивен Фури
6
Обязательно ознакомьтесь с ответом Джо ниже о дополнительных шагах, которые необходимо предпринять, чтобы заставить кеш работать
Кейт
65

Что касается элегантного connection.setUseCachesрешения, приведенного выше: к сожалению, без дополнительных усилий оно не сработает. Вам нужно будет установить ResponseCacheusing ResponseCache.setDefault. В противном случае бит HttpURLConnectionмолча игнорируется setUseCaches(true).

Подробнее см. Комментарии вверху FileResponseCache.java:

http://libs-for-android.googlecode.com/svn/reference/com/google/android/filecache/FileResponseCache.html

(Я бы разместил это в комментарии, но мне явно не хватает ТАКОЙ кармы.)

Джо
источник
Вот файл
Телемако
2
Когда вы используете HttpResponseCache, вы можете обнаружить, что HttpResponseCache.getHitCount()возвращается 0. Я не уверен, но я думаю, что это потому, что запрашиваемый вами веб-сервер в этом случае не использует кэширующие заголовки. Чтобы кеширование все равно работало, используйте connection.addRequestProperty("Cache-Control", "max-stale=" + MAX_STALE_CACHE);.
Almer
1
Ссылка Google codesearch не работает (снова?), Пожалуйста, обновите ссылку.
Феликс Д.
Кроме того, я не уверен, исправлено ли это поведение сейчас. По какой-то причине при возврате 304 с сервера HUC зависает при использовании .getContent()метода, потому что ответы 304 не имеют связанного тела ответа по стандарту RFC.
TheRealChx101
28

Преобразуйте их в растровые изображения, а затем либо сохраните их в коллекции (HashMap, List и т. Д.), Либо вы можете записать их на SD-карту.

При сохранении их в пространстве приложения с использованием первого подхода вы можете захотеть обернуть их вокруг java.lang.ref.SoftReference, особенно если их количество велико (чтобы они были собраны мусором во время кризиса). Однако это может привести к перезагрузке.

HashMap<String,SoftReference<Bitmap>> imageCache =
        new HashMap<String,SoftReference<Bitmap>>();

запись их на SD-карту не требует перезагрузки; просто разрешение пользователя.

Самух
источник
как записать образ на sd или память телефона?
d-man
Чтобы сохранить изображения на SD-карту: Вы можете либо зафиксировать потоки изображений, считанные с удаленного сервера, в память, используя обычные операции ввода-вывода файлов, либо, если вы преобразовали изображения в объекты Bitmap, вы можете использовать метод Bitmap.compress ().
Samuh
@ d-man Я бы посоветовал сначала записать файл на диск, а затем получить Uriссылку на путь, которую вы можете передать, ImageViewи другие пользовательские представления. Потому что каждый раз вы compressтеряете качество. Конечно, это верно только для алгоритмов с потерями. Этот метод также позволит вам даже хранить хэш файла и использовать его в следующий раз , когда вы запрашиваете для файла с сервера через If-None-Matchи ETagзаголовков.
TheRealChx101
@ TheRealChx101, не могли бы вы помочь понять, что вы имеете в виду в следующий раз, когда вы запрашиваете файл с сервера через заголовки If-None-Match и ETag , я в основном ищу подход к решению, при котором изображение должно оставаться, чтобы использовать локальный кеш формы для определенного период ИЛИ, если это не может быть достигнуто, то всякий раз, когда контент для URL изменяется, он должен отражаться в приложении с последним и кэшироваться.
CoDe
@CoDe Перейдите по этой ссылке сейчас, android.jlelse.eu/…
TheRealChx101,
27

Используйте LruCacheдля эффективного кэширования изображений. Вы можете прочитать LruCacheс сайта разработчика Android

Я использовал нижеприведенное решение для загрузки и кеширования изображений в android. Вы можете выполнить следующие шаги:

ШАГ 1: сделайте класс именованным ImagesCache. Я использовалSingleton object for this class

import android.graphics.Bitmap;
import android.support.v4.util.LruCache;

public class ImagesCache 
{
    private  LruCache<String, Bitmap> imagesWarehouse;

    private static ImagesCache cache;

    public static ImagesCache getInstance()
    {
        if(cache == null)
        {
            cache = new ImagesCache();
        }

        return cache;
    }

    public void initializeCache()
    {
        final int maxMemory = (int) (Runtime.getRuntime().maxMemory() /1024);

        final int cacheSize = maxMemory / 8;

        System.out.println("cache size = "+cacheSize);

        imagesWarehouse = new LruCache<String, Bitmap>(cacheSize)
                {
                    protected int sizeOf(String key, Bitmap value) 
                    {
                        // The cache size will be measured in kilobytes rather than number of items.

                        int bitmapByteCount = value.getRowBytes() * value.getHeight();

                        return bitmapByteCount / 1024;
                    }
                };
    }

    public void addImageToWarehouse(String key, Bitmap value)
    {       
        if(imagesWarehouse != null && imagesWarehouse.get(key) == null)
        {
            imagesWarehouse.put(key, value);
        }
    }

    public Bitmap getImageFromWarehouse(String key)
    {
        if(key != null)
        {
            return imagesWarehouse.get(key);
        }
        else
        {
            return null;
        }
    }

    public void removeImageFromWarehouse(String key)
    {
        imagesWarehouse.remove(key);
    }

    public void clearCache()
    {
        if(imagesWarehouse != null)
        {
            imagesWarehouse.evictAll();
        }       
    }

}

ШАГ 2:

создайте другой класс с именем DownloadImageTask, который используется, если растровое изображение недоступно в кеше, он загрузит его отсюда:

public class DownloadImageTask extends AsyncTask<String, Void, Bitmap>
{   
    private int inSampleSize = 0;

    private String imageUrl;

    private BaseAdapter adapter;

    private ImagesCache cache;

    private int desiredWidth, desiredHeight;

    private Bitmap image = null;

    private ImageView ivImageView;

    public DownloadImageTask(BaseAdapter adapter, int desiredWidth, int desiredHeight) 
    {
        this.adapter = adapter;

        this.cache = ImagesCache.getInstance();

        this.desiredWidth = desiredWidth;

        this.desiredHeight = desiredHeight;
    }

    public DownloadImageTask(ImagesCache cache, ImageView ivImageView, int desireWidth, int desireHeight)
    {
        this.cache = cache;

        this.ivImageView = ivImageView;

        this.desiredHeight = desireHeight;

        this.desiredWidth = desireWidth;
    }

    @Override
    protected Bitmap doInBackground(String... params) 
    {
        imageUrl = params[0];

        return getImage(imageUrl);
    }

    @Override
    protected void onPostExecute(Bitmap result) 
    {
        super.onPostExecute(result);

        if(result != null)
        {
            cache.addImageToWarehouse(imageUrl, result);

            if(ivImageView != null)
            {
                ivImageView.setImageBitmap(result);
            }
            else if(adapter != null)
            {
                adapter.notifyDataSetChanged();
            }
        }
    }

    private Bitmap getImage(String imageUrl)
    {   
        if(cache.getImageFromWarehouse(imageUrl) == null)
        {
            BitmapFactory.Options options = new BitmapFactory.Options();

            options.inJustDecodeBounds = true;

            options.inSampleSize = inSampleSize;

            try
            {
                URL url = new URL(imageUrl);

                HttpURLConnection connection = (HttpURLConnection)url.openConnection();

                InputStream stream = connection.getInputStream();

                image = BitmapFactory.decodeStream(stream, null, options);

                int imageWidth = options.outWidth;

                int imageHeight = options.outHeight;

                if(imageWidth > desiredWidth || imageHeight > desiredHeight)
                {   
                    System.out.println("imageWidth:"+imageWidth+", imageHeight:"+imageHeight);

                    inSampleSize = inSampleSize + 2;

                    getImage(imageUrl);
                }
                else
                {   
                    options.inJustDecodeBounds = false;

                    connection = (HttpURLConnection)url.openConnection();

                    stream = connection.getInputStream();

                    image = BitmapFactory.decodeStream(stream, null, options);

                    return image;
                }
            }

            catch(Exception e)
            {
                Log.e("getImage", e.toString());
            }
        }

        return image;
    }

ШАГ 3. Использование вашего ActivityилиAdapter

Примечание: если вы хотите загрузить изображение с URL-адреса из ActivityClass. Используйте второй конструктор DownloadImageTask, но если вы хотите отображать изображение из Adapterпервого конструктора DownloadImageTask(например, у вас есть изображение, ListViewи вы устанавливаете изображение из адаптера)

ИСПОЛЬЗОВАНИЕ ОТ ДЕЯТЕЛЬНОСТИ:

ImageView imv = (ImageView) findViewById(R.id.imageView);
ImagesCache cache = ImagesCache.getInstance();//Singleton instance handled in ImagesCache class.
cache.initializeCache();

String img = "your_image_url_here";

Bitmap bm = cache.getImageFromWarehouse(img);

if(bm != null)
{
  imv.setImageBitmap(bm);
}
else
{
  imv.setImageBitmap(null);

  DownloadImageTask imgTask = new DownloadImageTask(cache, imv, 300, 300);//Since you are using it from `Activity` call second Constructor.

  imgTask.execute(img);
}

ИСПОЛЬЗОВАНИЕ ОТ АДАПТЕРА:

ImageView imv = (ImageView) rowView.findViewById(R.id.imageView);
ImagesCache cache = ImagesCache.getInstance();
cache.initializeCache();

String img = "your_image_url_here";

Bitmap bm = cache.getImageFromWarehouse(img);

if(bm != null)
{
  imv.setImageBitmap(bm);
}
else
{
  imv.setImageBitmap(null);

  DownloadImageTask imgTask = new DownloadImageTask(this, 300, 300);//Since you are using it from `Adapter` call first Constructor.

  imgTask.execute(img);
}

Заметка:

cache.initializeCache()вы можете использовать этот оператор в самом первом Activity вашего приложения. После того, как вы инициализировали кеш, вам никогда не нужно будет инициализировать его каждый раз, если вы используете ImagesCacheinstance.

Я никогда не умею объяснять, но надеюсь, что это поможет новичкам понять, как использовать кеш LruCacheи его использование :)

РЕДАКТИРОВАТЬ:

Теперь есть очень известные библиотеки, известные как Picassoи, Glideкоторые можно использовать для очень эффективной загрузки изображений в приложении для Android. Попробуйте эту очень простую и полезную библиотеку Picasso для Android и Glide для Android . Вам не нужно беспокоиться об изображениях в кеше.

Picasso позволяет без проблем загружать изображения в ваше приложение - часто в одной строке кода!

Glide, как и Пикассо, может загружать и отображать изображения из многих источников, а также заботится о кэшировании и сохранении низкого уровня памяти при выполнении манипуляций с изображениями. Он использовался официальными приложениями Google (например, приложением для Google I / O 2015) и так же популярен, как и Picasso. В этой серии мы собираемся изучить различия и преимущества Glide над Picasso.

Вы также можете посетить блог, чтобы узнать о различиях между Glide и Picasso.

Зубаир Ахмед
источник
3
Замечательный ответ и объяснение! Я считаю, что это лучшее решение, поскольку оно работает в автономном режиме и использует Android LruCache. Я обнаружил, что решение edrowland не работает в авиарежиме даже с добавлением Джо, для интеграции которого потребовалось больше усилий. Кстати, похоже, что Android или сеть обеспечивают значительный объем кеширования, даже если вы не делаете ничего лишнего. (Один небольшой нюанс: для примера использования getImageFromWareHouse буква H должна быть в нижнем регистре.) Спасибо!
Эдвин Эванс
1
отличное объяснение :)
XtreemDeveloper
Не могли бы вы объяснить метод getImage (), в частности, что он делает с размером изображения и как это происходит. Я, например, не понимаю, почему вы снова вызываете функцию внутри себя и как она работает.
Greyshack
1
Проголосуйте за то, if(cache == null)что решило мою проблему! :)
MR. Garcia
1
Также см. Мой Отредактированный ответ в конце. Я уже упоминал об известных библиотеках, которые сейчас используются большинством разработчиков. Попробуйте те Пикассо: square.github.io/picasso и Glide: futurestud.io/blog/glide-getting-started
Зубайр Ахмед
18

Скачать изображение и сохранить на карту памяти можно так.

//First create a new URL object 
URL url = new URL("http://www.google.co.uk/logos/holiday09_2.gif")

//Next create a file, the example below will save to the SDCARD using JPEG format
File file = new File("/sdcard/example.jpg");

//Next create a Bitmap object and download the image to bitmap
Bitmap bitmap = BitmapFactory.decodeStream(url.openStream());

//Finally compress the bitmap, saving to the file previously created
bitmap.compress(CompressFormat.JPEG, 100, new FileOutputStream(file));

Не забудьте добавить разрешение на Интернет в свой манифест:

<uses-permission android:name="android.permission.INTERNET" />
Ljdawson
источник
10
Почему вы декодируете JPEG, а затем перекодируете его? Лучше загрузить URL-адрес в массив байтов, а затем использовать этот массив байтов для создания Bitmap и записи в файл. Каждый раз, когда вы декодируете и перекодируете JPEG, качество изображения ухудшается.
CommonsWare,
2
Справедливо, было больше скорости, чем чего-либо еще. Хотя, если сохранить как байтовый массив, а исходный файл не был в формате JPEG, разве не нужно было бы конвертировать файл? «decodeByteArray» из SDK возвращает «Декодированное растровое изображение или ноль, если данные изображения не могут быть декодированы», поэтому я думаю, что он всегда декодирует данные изображения, поэтому не потребуется ли повторное кодирование снова?
Ljdawson
Говоря об эффективности, не будет ли эффективным, если вместо передачи FileOutputStream мы передадим BufferedOutputStream?
Samuh
1
Я не предлагаю кэшировать изображения на SD-карту. после удаления приложения изображения не удаляются, в результате чего SD-карта заполняется бесполезным мусором. сохранение изображений в каталог кеша приложения предпочтительнее IMO
Джеймс
Теперь, когда ограничение APK составляет 50 МБ, кеширование на SD-карту может быть единственным способом для разработчиков.
Ljdawson
13

Я бы подумал об использовании кеша изображений droidfu. Он реализует как кэш изображений в памяти, так и на диске. Вы также получаете WebImageView, который использует преимущества библиотеки ImageCache.

Вот полное описание droidfu и WebImageView: http://brainflush.wordpress.com/2009/11/23/droid-fu-part-2-webimageview-and-webgalleryadapter/

Серебряный
источник
Он реорганизовал свой код с 2010 года; вот корневая ссылка: github.com/kaeppler/droid-fu
esilver 01
3
Эта ссылка по-прежнему не работает. Я написал аналогичную библиотеку под названием Android-ImageManager github.com/felipecsl/Android-ImageManager
Фелипе Лима
9

Я пробовал SoftReferences, они слишком агрессивно используются в Android, и я чувствовал, что их нет смысла использовать

2чашкиOfTech
источник
2
Согласовано - SoftReferences очень быстро восстанавливаются на устройствах, которые я тестировал
esilver
3
Сами Google подтвердили, что сборщик мусора Dalvik очень агрессивен по сбору SoftReferences. LruCacheВместо этого они рекомендуют использовать их .
kaka
9

Как предположил Thunder Rabbit, ImageDownloader лучше всего подходит для этой работы. Я также нашел небольшую вариацию класса по адресу:

http://theandroidcoder.com/utilities/android-image-download-and-caching/

Основное различие между ними заключается в том, что ImageDownloader использует систему кэширования Android, а модифицированный использует внутреннее и внешнее хранилище в качестве кэширования, сохраняя кэшированные изображения на неопределенный срок или до тех пор, пока пользователь не удалит их вручную. Также автор упоминает совместимость с Android 2.1.

EZFrag
источник
7

Это хороший улов Джо. В приведенном выше примере кода есть две проблемы: одна - объект ответа не является экземпляром Bitmap (когда мой URL-адрес ссылается на jpg, например http: \ website.com \ image.jpg, его

org.apache.harmony.luni.internal.net.www.protocol.http.HttpURLConnectionImpl $ LimitedInputStream).

Во-вторых, как указывает Джо, кеширование не происходит без настройки кэша ответов. Разработчикам Android остается откатывать собственный кеш. Вот пример этого, но он только кэширует в памяти, что на самом деле не является полным решением.

http://codebycoffee.com/2010/06/29/using-responsecache-in-an-android-app/

API кэширования URLConnection описан здесь:

http://download.oracle.com/javase/6/docs/technotes/guides/net/http-cache.html

Я все еще думаю, что это нормальное решение, чтобы пойти по этому пути, но вам все равно нужно написать кеш. Звучит весело, но я лучше напишу особенности.

Питер Паскаль
источник
7

Об этом есть специальная запись в официальном разделе обучения Android: http://developer.android.com/training/displaying-bitmaps/cache-bitmap.html

Раздел довольно новый, его не было, когда задавали вопрос.

Предлагаемое решение - использовать LruCache. Этот класс был представлен в Honeycomb, но он также включен в библиотеку совместимости.

Вы можете инициализировать LruCache, установив максимальное количество записей, и он автоматически отсортирует их для вас и очистит менее используемые, когда вы превысите лимит. В остальном он используется как обычная карта.

Пример кода с официальной страницы:

private LruCache mMemoryCache;

@Override
protected void onCreate(Bundle savedInstanceState) {
    ...
    // Get memory class of this device, exceeding this amount will throw an
    // OutOfMemory exception.
    final int memClass = ((ActivityManager) context.getSystemService(
            Context.ACTIVITY_SERVICE)).getMemoryClass();

    // Use 1/8th of the available memory for this memory cache.
    final int cacheSize = 1024 * 1024 * memClass / 8;

    mMemoryCache = new LruCache(cacheSize) {
        @Override
        protected int sizeOf(String key, Bitmap bitmap) {
            // The cache size will be measured in bytes rather than number of items.
            return bitmap.getByteCount();
        }
    };
    ...
}

public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
    if (getBitmapFromMemCache(key) == null) {
        mMemoryCache.put(key, bitmap);
    }
}

public Bitmap getBitmapFromMemCache(String key) {
    return mMemoryCache.get(key);
}

Раньше SoftReferences были хорошей альтернативой, но уже не, цитируя официальную страницу:

Примечание. В прошлом популярной реализацией кэша памяти был кеш растровых изображений SoftReference или WeakReference, однако это не рекомендуется. Начиная с Android 2.3 (уровень API 9) сборщик мусора более агрессивно собирает мягкие / слабые ссылки, что делает их довольно неэффективными. Кроме того, до Android 3.0 (уровень API 11) резервные данные растрового изображения хранились в собственной памяти, которая не высвобождалась предсказуемым образом, что могло привести к кратковременному превышению приложением пределов памяти и сбою.

шалафи
источник
3

Рассмотрите возможность использования универсальной библиотеки Image Loader от Сергея Тарасевича . В комплекте:

  • Многопоточная загрузка изображений. Это позволяет вам определить размер пула потоков
  • Кеширование изображений в памяти, на файловой системе устройства и на SD-карте.
  • Возможность слушать прогресс загрузки и события загрузки

Универсальный загрузчик изображений позволяет подробно управлять кешем для загруженных изображений со следующими конфигурациями кеша:

  • UsingFreqLimitedMemoryCache: Наименее часто используемое растровое изображение удаляется при превышении предельного размера кэша.
  • LRULimitedMemoryCache: Последнее использованное растровое изображение удаляется при превышении предельного размера кэша.
  • FIFOLimitedMemoryCache: Правило FIFO используется для удаления при превышении предельного размера кэша.
  • LargestLimitedMemoryCache: Самый большой растровый рисунок удаляется при превышении предельного размера кэша.
  • LimitedAgeMemoryCache: Кэшированный объект удаляется, когда его возраст превышает определенное значение .
  • WeakMemoryCache: Кэш памяти со слабыми ссылками на растровые изображения.

Простой пример использования:

ImageView imageView = groupView.findViewById(R.id.imageView);
String imageUrl = "http://site.com/image.png"; 

ImageLoader imageLoader = ImageLoader.getInstance();
imageLoader.init(ImageLoaderConfiguration.createDefault(context));
imageLoader.displayImage(imageUrl, imageView);

В этом примере используется значение по умолчанию UsingFreqLimitedMemoryCache.

Гуннар Карлссон
источник
При интенсивном использовании Universal Image Loader вызывает много утечек памяти. Я подозреваю, что это происходит потому, что в коде используются синглтоны (см. «GetInstance ()» в примере). После загрузки большого количества изображений и последующего поворота экрана несколько раз мое приложение постоянно вылетало из-за ошибки OutOfMemoryErrors в UIL. Это отличная библиотека, но это хорошо известный факт, что вам НИКОГДА не следует использовать синглтоны, особенно в Android ...
Герт Беллеманс
1
ИСПОЛЬЗУЙТЕ синглтоны, когда знаете как! :)
Ренетик 08
3

Что на самом деле сработало для меня, так это установка ResponseCache в моем основном классе:

try {
   File httpCacheDir = new File(getApplicationContext().getCacheDir(), "http");
   long httpCacheSize = 10 * 1024 * 1024; // 10 MiB
   HttpResponseCache.install(httpCacheDir, httpCacheSize);
} catch (IOException e) { } 

а также

connection.setUseCaches(true);

при загрузке растрового изображения.

http://practicaldroid.blogspot.com/2013/01/utilizing-http-response-cache.html

ZamPrano
источник
можно ли использовать lrucache в сочетании с httpresponsecache
iOSAndroidWindowsMobileAppsDev
1

Некоторое время я боролся с этим; ответы, использующие SoftReferences, будут терять свои данные слишком быстро. Ответы, предлагающие создать экземпляр RequestCache, были слишком беспорядочными, к тому же я так и не смог найти полный пример.

Но ImageDownloader.java у меня прекрасно работает. Он использует HashMap до тех пор, пока не будет достигнута емкость или пока не истечет время ожидания очистки, затем все будет перемещено в SoftReference, тем самым используя лучшее из обоих миров.

Громовой кролик
источник
0

Еще более поздний ответ, но я написал Android Image Manager, который прозрачно обрабатывает кеширование (память и диск). Код находится на Github https://github.com/felipecsl/Android-ImageManager

Фелипе Лима
источник
1
Я добавил это в ListView, и, похоже, он не очень хорошо с этим справляется. Есть ли какая-то специальная реализация для ListViews?
0

Поздний ответ, но я полагал , что я должен добавить ссылку на мой сайт , потому что я написал учебник , как сделать тайник изображения для андроид: http://squarewolf.nl/2010/11/android-image-cache/ обновления: страница была переведена в офлайн, так как источник был устаревшим. Я присоединяюсь к @elenasys в ее совете по использованию Ignition .

Итак, всем, кто сталкивается с этим вопросом и не нашел решения: надеюсь, вам понравится! = D

Томас Вервест
источник
0

Поздний ответ, но я думаю, что эта библиотека очень поможет с кешированием изображений: https://github.com/crypticminds/ColdStorage .

Просто аннотируйте ImageView с помощью @LoadCache (R.id.id_of_my_image_view, "URL_to_downlaod_image_from), и он позаботится о загрузке изображения и загрузке его в представление изображения. Вы также можете указать изображение-заполнитель и загрузку анимации.

Подробная документация аннотации представлена ​​здесь: - https://github.com/crypticminds/ColdStorage/wiki/@LoadImage-annotation

Анураг Мандал
источник