Кешування зображень Android


141

Як я можу кешувати зображення після їх завантаження з Інтернету?

Відповіді:


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;
} 

Забезпечує кеш пам’яті та флеш-пам’яті кеш-пам’яті, що ділиться з браузером

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


1
О, це був неймовірно елегантний спосіб зробити це, дякую велике. Це нітрохи не повільніше, ніж мій власний простий кеш-менеджер, і тепер мені не потрібно вести господарство в папці SD-карти.
Кевін читати

11
connection.getContent()завжди повертає InputStream для мене, що я роблю неправильно?
Тайлер Коллієр

3
Якби я тепер міг також встановити термін придатності для вмісту кешу, моє життя було б набагато простіше :)
Janusz

11
@Scienceprodigy поняття не має, що це BitmapLoader, звичайно, це не в будь-якій стандартній бібліотеці для Android, про яку я знаю, але це принаймні привело мене в правильному напрямку. Bitmap response = BitmapFactory.decodeStream((InputStream)connection.getContent());
Стівен Фугрі

6
Не забудьте переглянути відповідь Джо нижче про додаткові кроки, які потрібно вжити, щоб кеш працював
Кіт

65

Щодо елегантного connection.setUseCachesрішення вище: на жаль, воно не спрацює без додаткових зусиль. Вам потрібно буде встановити ResponseCacheкористування ResponseCache.setDefault. Інакше HttpURLConnectionбуде мовчки ігнорувати setUseCaches(true)шматочок.

FileResponseCache.javaДеталі див. У коментарях вгорі :

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

(Я опублікував це в коментарі, але мені, мабуть, не вистачає ТАК карми.)



2
Коли ви користуєтесь an HttpResponseCache, ви можете знайти HttpResponseCache.getHitCount()повертається 0. Я не впевнений, але я думаю, що це тому, що веб-сервер, про який ви запитуєте, не використовує заголовки кешування в цьому випадку. Щоб кеш-роботу все-таки працювало, використовуйте connection.addRequestProperty("Cache-Control", "max-stale=" + MAX_STALE_CACHE);.
Альмер

1
Посилання на пошуковий код Google померло (знову?), Оновіть його.
Фелікс Д.

Також я не впевнений, чи виправлена ​​ця поведінка зараз чи ні. Чомусь повернення 304 з сервера повинно вивісити HUC при використанні .getContent()методу, оскільки 304 відповіді не мають асоційованого органу відповідей за стандартом RFC.
TheRealChx101

27

Перетворіть їх у бітові карти, а потім збережіть їх у колекції (HashMap, список тощо) або можете записати їх на SDcard.

Зберігаючи їх у прикладному просторі за допомогою першого підходу, ви можете обернути їх навколо java.lang.ref.SoftReference, зокрема, якщо їх кількість велика (щоб вони зібрали сміття під час кризи). Це може спричинити перезавантаження.

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

запис їх на SDcard не потребує перезавантаження; просто дозвіл користувача.


як ми можемо записати зображення на SD або пам'ять телефону?
d-man

Щоб зберегти зображення на SD-картці: Ви можете або зафіксувати зчитувані потоки зображень з віддаленого сервера в пам'яті за допомогою звичайних операцій вводу / виводу файлів, або якщо ви перетворили зображення в об'єкти Bitmap, можете скористатися методом Bitmap.compress ().
Самух

@ 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-адреси з Activityкласу. Використовуйте другий конструктор 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);
}

ВИКОРИСТАННЯ ВІД ADAPTER:

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()Ви можете використовувати цю заяву в першій діяльності Вашої програми. Після ініціалізації кеша вам ніколи не доведеться ініціалізувати його кожен раз, якщо ви використовуєте ImagesCacheекземпляр.

Я ніколи не вмію пояснювати речі, але сподіваюся, що це допоможе новачкам у тому, як кешувати LruCacheта використовувати його :)

Редагувати:

Зараз уже кілька днів є дуже відомі бібліотеки, відомі як Picassoі Glideякі можна використовувати для ефективної завантаження зображень в додатку для Android. Спробуйте цю дуже просту і корисну бібліотеку Picasso для android та Glide For Android . Не потрібно турбуватися про зображення кешу.

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

Glide, як і Пікассо, може завантажувати та показувати зображення з багатьох джерел, а також дбаючи про кешування та зберігаючи низький вплив на пам'ять під час маніпуляцій із зображеннями. Він використовувався офіційними програмами Google (наприклад, програма Google I / O 2015) і настільки ж популярний, як і Пікассо. У цій серії ми будемо досліджувати відмінності та переваги ковзання над Пікассо.

Ви також можете відвідати блог щодо різниці між Glide та Picasso


3
Видатна відповідь та пояснення! Я думаю, що це найкраще рішення, оскільки воно працює в режимі офлайн і використовує Android LruCache. Я виявив, що рішення Edrowland не працює в літаковому режимі навіть з додаванням Джо, що вимагало більше зусиль для інтеграції. До речі, схоже, що Android або мережа забезпечує значну кількість кешування, навіть якщо ви нічого не зайві. (Один незначний ніт: для використання зразка getImageFromWareHouse, 'H' має бути малим регістром.) Дякую!
Едвін Еванс

1
чудове пояснення :)
XtreemDeveloper

Чи можете ви пояснити метод getImage (), зокрема, що він робить для розміру зображення та як це відбувається. Я не розумію, наприклад, чому ви знову викликаєте функцію всередині себе і як вона працює.
Грейшак

1
Оновлення за те, if(cache == null)що вирішило мою проблему! :)
ЗМ. Гарсія

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" />

10
Чому ви декодуєте JPEG, а потім повторно кодуєте його? Вам краще подати URL-адресу в байтовий масив, а потім використати цей байтовий масив для створення вашої Bitmap та виписати у файл. Кожен раз, коли ви декодуєте та перекодуєте JPEG, якість зображення погіршується.
CommonsWare

2
Справедливий пункт, був більше для швидкості ніж нічого. Хоча, якби збережений як байтовий масив, а вихідний файл не був JPEG, чи не потрібно було б файл конвертувати? "decodeByteArray" з SDK Повертається "Розшифрована растрова карта, або нульова, якщо дані зображення не вдалося розшифрувати", тому це змушує мене думати, що її завжди декодує дані зображення, тому це не потребуватиме повторного кодування знову?
Ljdawson

Якщо говорити про ефективність, чи не буде це ефективно, якщо замість передачі FileOutputStream ми передамо BufferedOutputStream?
Самух

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

3
Це посилання все ще не працює. Я написав подібну бібліотеку під назвою Android-ImageManager github.com/felipecsl/Android-ImageManager
Феліпе Ліма

9

Я спробував SoftReferences, вони занадто агресивно повертаються в андроїд, щоб я відчував, що їх немає сенсу використовувати


2
Погоджено - SoftReferences відновлюється дуже швидко на тестованих пристроях
esilver

3
Google самі підтвердили, що GC Dalvik є дуже агресивним щодо збирання SoftReferences. Вони рекомендують використовувати LruCacheзамість них.
кака

9

Як запропонував Thunder Rabbit, ImageDownloader є найкращим для роботи. Я також виявив незначну зміну класу у:

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

Основна відмінність між ними полягає в тому, що ImageDownloader використовує систему кешування Android, а модифікована використовує внутрішнє та зовнішнє сховище як кешування, зберігаючи кешовані зображення на невизначений термін або поки користувач не видалить їх вручну. Автор також згадує сумісність Android 2.1.


7

Це хороший улов Джо. У наведеному вище прикладі коду є дві проблеми - одна - об'єкт відповіді не є примірником Bitmap (коли моя URL-адреса посилається на jpg, як-от http: \ website.com \ image.jpg, його a

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

Подумайте про використання бібліотеки Universal 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 Loader Image викличе багато витоків пам'яті. Я підозрюю, що це трапляється, оскільки він використовує одиночні кнопки в коді (див. "GetInstance ()" у прикладі). Завантаживши багато зображень, а потім кілька разів повернувши екран, мій додаток весь час виходив з ладу, оскільки OutOfMemoryErrors в UIL. Це чудова бібліотека, але це загальновідомий факт, що НІКОЛИ не слід використовувати одинаків, особливо не в Android ...
Geert Bellemans

1
ВИКОРИСТУВАЙТЕ одинаків, коли ви вмієте! :)
Renetik

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


чи можна використовувати lrucache спільно з httpresponsecache
iOSAndroidWindowsMobileAppsDev


1

Я певний час боровся з цим; відповіді за допомогою SoftReferences занадто швидко втратять свої дані. Відповіді, які дозволяють створити запит на запит RequestCache, були занадто безладними, плюс я ніколи не міг знайти повного прикладу.

Але ImageDownloader.java для мене чудово працює. Він використовує HashMap до тих пір, поки не буде досягнуто потужність або поки не настане час очікування очищення, а потім переміщаються в SoftReference, використовуючи тим самим найкращі з обох світів.



0

Ще пізніше відповідаю, але я написав диспетчер зображень Android, який керує кешами прозоро (пам'ять та диск). Код знаходиться на Github https://github.com/felipecsl/Android-ImageManager


1
Я додав це до ListView, і, здається, це не дуже добре справляється. Чи є якась спеціальна реалізація для ListViews?

0

Пізня відповідь, але я подумав, що я повинен додати посилання на свій сайт, тому що я написав підручник, як зробити кеш зображень для андроїда: http://squarewolf.nl/2010/11/android-image-cache/ Оновлення: the Сторінку було знято в автономному режимі, оскільки джерело застаріло. Я приєднуюся до @elenasys в її пораді щодо використання запалювання .

Тож усім людям, які натрапляють на це питання і не знайшли рішення: сподіваюся, вам сподобається! = 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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.