Android图像缓存


Answers:


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

提供与浏览器共享的内存和Flash-ROM缓存。

grr。我希望有人告诉我,在我编写自己的缓存管理器之前。


1
哇,这是一种非常优雅的方法,非常感谢。它绝不会比我自己的简单缓存管理器慢,而且现在我不需要在SD卡文件夹上进行整理。
凯文(Kevin)阅读

11
connection.getContent()总是为我返回InputStream,我在做什么错?
Tyler Collier

3
如果现在我也可以在缓存的内容上设置过期日期,那么我的生活会变得更加轻松:)
Janusz

11
@Scienceprodigy不知道BitmapLoader是什么,当然不在我所知道的任何标准android库中,但这至少使我朝着正确的方向发展。Bitmap response = BitmapFactory.decodeStream((InputStream)connection.getContent());
Stephen Fuhry 2011年

6
请确保在下面看到Joe的回答,以了解
Keith

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

(我将其发布在评论中,但显然我没有足够的业障。)


这里是文件
Telémako

2
当您使用时HttpResponseCache,您可能会发现HttpResponseCache.getHitCount()返回的0。我不确定,但是我认为这是因为您请求的Web服务器在这种情况下不使用缓存头。要使缓存正常工作,请使用connection.addRequestProperty("Cache-Control", "max-stale=" + MAX_STALE_CACHE);
Almer 2013年

1
Google Codesearch链接已失效(再次?),请更新该链接。
Felix D.

另外,我不确定现在是否已解决此问题。由于某种原因,使用304时,从服务器返回304会挂起HUC,.getContent()因为304响应没有RFC标准的关联响应主体。
TheRealChx101 '19

27

将它们转换为位图,然后将其存储在Collection(HashMap,List等)中,或者可以将它们写入SD卡。

使用第一种方法将它们存储在应用程序空间中时,您可能希望将它们包装在java.lang.ref.SoftReference周围,特别是如果它们的数量很大(以便在危机期间将其垃圾回收)。但这可能会导致重新加载。

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

将它们写入SD卡将不需要重新加载;只是用户权限。


如何在SD或手机存储器上写入图像?
d-man

要将图像保存在SD卡上:您可以使用正常的文件I / O操作将从远程服务器读取的图像流提交到内存,或者如果将图像转换为Bitmap对象,则可以使用Bitmap.compress()方法。
Samuh,2009年

@ d-man我建议先将其写入磁盘,然后获取Uri可以传递给的路径引用ImageView以及其他自定义视图。因为每次compress,您都会失去质量。当然,这仅对有损算法正确。此方法还允许您甚至存储文件的哈希,并在下次通过If-None-MatchETag标头从服务器请求文件时使用它。
TheRealChx101 '19

@ TheRealChx101是否可以帮助您理解下次您通过If-None-Match和ETag标头从服务器请求文件时的含义,我基本上是在寻找一种解决方案,应保留图像以使用本地缓存用于定义期限,或者如果无法实现,则只要URL的内容发生更改,它就应该以最新的形式反映在应用程序中并进行缓存。
CoDe19年

@CoDe现在访问此链接,android.jlelse.eu /…
TheRealChx101,19年

27

用于LruCache有效地缓存图像。您可以LruCacheAndroid开发者网站了解

我已经使用以下解决方案在Android中下载和缓存图片。您可以按照以下步骤操作:

步骤1: 将Class命名为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:从您ActivityAdapter

注意:如果要从ActivityClass的URL加载图像。使用的第二个构造函数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()您可以在应用程序的第一个活动中使用此语句。初始化缓存后,如果您使用ImagesCache实例,则无需每次都对其进行初始化。

我从不擅长解释事物,但希望这对初学者如何使用缓存LruCache及其用法有所帮助:)

编辑:

现在,天有被称为非常著名的图书馆Picasso,并Glide可以在Android应用程序非常有效地用于加载图像。试试这个非常简单实用的库Picasso(适用于Android)Glide(适用于Android)。您无需担心缓存图像。

Picasso允许在您的应用程序中轻松加载图像-通常只需一行代码!

像Picasso一样,Glide可以加载和显示来自许多来源的图像,同时在进行图像处理时还可以注意缓存并保持较低的内存影响。它已被正式的Google应用程序使用(例如Google I / O 2015的应用程序),并且与Picasso一样受欢迎。在本系列中,我们将探索Glide与毕加索的区别和优势。

您也可以访问博客,了解Glide和Picasso之间区别


3
出色的答案和解释!我认为这是最好的解决方案,因为它可以在脱机时使用Android LruCache。我发现edrowland的解决方案即使在Joe的加入下也无法在飞机模式下工作,这需要更多的整合努力。顺便说一句,即使您不执行任何其他操作,Android或网络似乎都提供了大量的缓存。(一个小技巧:对于示例用法getImageFromWareHouse,'H'应该是小写字母以匹配。)谢谢!
Edwin Evans

1
很好的解释:)
XtreemDeveloper 2014年

您能否解释一下getImage()方法,特别是它对图像大小的作用及其发生方式。例如,我不明白为什么您要在内部再次调用该函数以及它是如何工作的。
Greyshack 2015年

1
if(cache == null)解决我的问题的人投票!:)
先生。加西亚

1
另请参阅最后的“我的编辑答案”。我已经提到过当今大多数开发人员使用的著名库。尝试那些毕加索:square.github.io/picasso和Glide:futurestud.io/blog/glide-getting-started
Zubair Ahmed

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

不要忘记将Internet权限添加到清单中:

<uses-permission android:name="android.permission.INTERNET" />

10
为什么要解码JPEG然后重新编码?最好将URL下载到字节数组,然后使用该字节数组创建位图并写出到文件。每次解码和重新编码JPEG时,图像质量都会变差。
CommonsWare

2
公平点,更多的是速度,然后是其他。虽然,如果将其保存为字节数组并且源文件不是JPEG,是否仍需要转换文件?SDK中的“ decodeByteArray”返回“已解码的位图,如果无法解码图像数据,则返回null”,所以这让我认为它始终在解码图像数据,因此不需要再次重新编码吗?
Ljdawson

说到效率,如果不是通过FileOutputStream而是通过BufferedOutputStream来提高效率吗?
Samuh,2009年

1
我不建议将图像缓存到SD卡。卸载应用程序后,图像将不会被删除,从而使SD卡中充满了无用的垃圾。将图像保存到应用程序的缓存目录是首选IMO
james

现在的APK限制为50mb,对于开发人员而言,缓存到SD卡可能是唯一的方法。
Ljdawson

13

我会考虑使用droidfu的图片缓存。它同时实现了内存中和基于磁盘的图像缓存。您还将获得一个利用ImageCache库的WebImageView。

以下是droidfu和WebImageView的完整说明:http ://brainflush.wordpress.com/2009/11/23/droid-fu-part-2-webimageview-and-webgalleryadapter/


自2010年以来,他就重构了代码。这是根链接:github.com/kaeppler/droid-fu
esilver 2012年

3
该链接仍然无效。我写了一个类似的库,叫做Android-ImageManager github.com/felipecsl/Android-ImageManager
Felipe Lima

9

我尝试过SoftReferences,但由于它们在android中过于激进,所以我觉得没有必要使用它们


2
同意-在我测试过的设备上,SoftReferences的回收非常快
esilver

3
Google本身已确认Dalvik的GC在收集SoftReferences方面非常积极。他们建议LruCache改为使用它们。
卡卡2012年

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)时,

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

其次,正如Joe指出的,没有配置响应缓存就不会发生缓存。Android开发人员只能滚动自己的缓存。这是一个这样做的示例,但是它仅缓存在内存中,这实际上并不是完整的解决方案。

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

URLConnection缓存API的描述如下:

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

考虑使用通用图像装载机库谢尔盖Tarasevich。它带有:

  • 多线程图像加载。它可以让您定义线程池的大小
  • 图像缓存在设备文件系统和SD卡上的内存中。
  • 听取加载进度和加载事件的可能性

Universal Image Loader允许使用以下缓存配置对下载的图像进行详细的缓存管理

  • 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);

本示例使用default UsingFreqLimitedMemoryCache


大量使用时,Universal Image Loader将导致大量内存泄漏。我怀疑这是因为它在代码中使用了单例(请参见示例中的“ getInstance()”)。加载大量图像然后旋转屏幕几次后,由于UIL中的OutOfMemoryErrors,我的应用始终崩溃。这是一个伟大的图书馆,但它是一个众所周知的事实泰德你永远不应该在Android中使用单身,尤其是不...
海尔特Bellemans

1
当您知道如何使用单例!:)
Renetik

3

实际上对我有用的是在Main类上设置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结合使用
iOSAndroidWindowsWindowsMo​​bileAppsDev


1

我已经为此努力了一段时间。使用SoftReferences的答案将很快丢失其数据。建议实例化RequestCache的答案太乱了,而且我再也找不到完整的示例。

但是ImageDownloader.java对我来说效果很好。它使用HashMap,直到达到容量或清除超时为止,然后将事物移至SoftReference,从而充分利用了两者的优势。





By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.