Android图片缓存

d-m*_*man 140 android caching image

如何从网上下载后缓存图像?

小智 174

现在的妙语是:使用系统缓存.

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

提供与浏览器共享的内存和闪存ROM.

GRR.我希望有人在我写自己的缓存管理器之前告诉过我.

  • `connection.getContent()`总是为我返回一个InputStream,我做错了什么? (11认同)
  • @Scienceprodigy不知道那个BitmapLoader是什么,当然不是我所知道的任何标准的android库,但它至少让我朝着正确的方向发展.`位图响应= BitmapFactory.decodeStream((InputStream)connection.getContent());` (11认同)
  • 请务必在下面看到Joe的答案,了解为缓存工作所需采取的额外步骤 (6认同)
  • 如果我现在还可以为缓存的内容设置一个到期日期,那么我的生活会更加轻松:) (3认同)

Joe*_*Joe 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

(我会在评论中发布这个,但我显然没有足够的SO业力.)

  • Google codesearch链接已死,请更新链接. (6认同)
  • 当您使用`HttpResponseCache`时,您可能会发现`HttpResponseCache.getHitCount()`返回0.我不确定,但我认为这是因为您请求的Web服务器在这种情况下不使用缓存头.无论如何要使缓存工作,请使用`connection.addRequestProperty("Cache-Control","max-stale ="+ MAX_STALE_CACHE);`. (2认同)

Sam*_*muh 27

将它们转换为位图,然后将它们存储在Collection(HashMap,List等)中,或者将它们写在SD卡上.

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

HashMap<String,SoftReference<Bitmap>> imageCache =
        new HashMap<String,SoftReference<Bitmap>>();
Run Code Online (Sandbox Code Playgroud)

在SD卡上写它们不需要重新加载; 只是一个用户权限.


Zub*_*med 26

用于LruCache有效地缓存图像.您可以LruCacheAndroid开发者网站上阅读

我在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();
        }       
    }

}
Run Code Online (Sandbox Code Playgroud)

第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;
    }
Run Code Online (Sandbox Code Playgroud)

第3步:使用您的ActivityAdapter

注意:如果要从ActivityClass中加载来自url的图像.使用第二个构造函数DownloadImageTask,但如果要显示Adapter使用的构造函数DownloadImageTask(例如,您有一个图像,ListView并且您正在从'Adapter'设置图像)

使用活动:

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);
}
Run Code Online (Sandbox Code Playgroud)

从适配器使用:

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);
}
Run Code Online (Sandbox Code Playgroud)

注意:

cache.initializeCache()您可以在应用程序的第一个Activity中使用此语句.一旦初始化了缓存,如果使用ImagesCache实例,则每次都不需要初始化缓存.

我从不善于解释事情,但希望这将有助于初学者如何缓存使用LruCache及其使用:)

编辑:

现在,天有被称为非常著名的图书馆PicassoGlide可用于在Android应用非常有效地加载图像.试试这个非常简单实用的Picasso for androidGlide For Android库.您无需担心缓存图像.

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

与毕加索一样,Glide可以加载和显示来自多个来源的图像,同时还可以在进行图像处理时处理缓存并保持较低的内存影响.它已被官方Google应用程序(例如Google I/O 2015的应用程序)使用,并且与Picasso一样受欢迎.在本系列中,我们将探讨Glide相对于毕加索的差异和优势.

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

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

Ljd*_*son 17

要下载图像并保存到存储卡,您可以这样做.

//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));
Run Code Online (Sandbox Code Playgroud)

不要忘记向您的清单添加Internet权限:

<uses-permission android:name="android.permission.INTERNET" />
Run Code Online (Sandbox Code Playgroud)

  • 你为什么解码JPEG然后重新编码?您可以更好地将URL下载到字节数组,然后使用该字节数组创建Bitmap并写入文件.每次解码和重新编码JPEG时,图像质量都会变差. (10认同)
  • 公平的一点,更多的是速度,然后什么.虽然,如果保存为字节数组并且源文件不是JPEG,那么文件是否需要转换?SDK中的"decodeByteArray"返回"已解码的位图,如果图像数据无法解码,则为null",这样我就会认为它总是解码图像数据,所以这不需要再次重新编码吗? (2认同)

esi*_*ver 13

我会考虑使用droidfu的图像缓存.它实现了内存和基于磁盘的映像缓存.您还可以获得利用ImageCache库的WebImageView.

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

  • 该链接仍然无效.我写了一个名为Android-ImageManager的类似库https://github.com/felipecsl/Android-ImageManager (3认同)

2cu*_*ech 9

我已经尝试过SoftReferences,他们在android中过于积极地回收,我觉得没有必要使用它们

  • 谷歌自己也证实,Dalvik的GC在收集"SoftReference"方面非常积极.他们建议使用他们的'LruCache`. (3认同)
  • 同意 - SoftReferences在我测试的设备上很快被回收 (2认同)

EZF*_*rag 9

正如Thunder Rabbit所说,ImageDownloader是最好的工作.我还发现课程略有变化:

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

两者之间的主要区别在于ImageDownloader使用Android缓存系统,而修改后的缓存使用内部和外部存储作为缓存,无限期地保留缓存的图像或直到用户手动删除它.作者还提到了Android 2.1的兼容性.


Pet*_*ale 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

我仍然认为这是一个很好的解决方案 - 但是你还是要写一个缓存.听起来很有趣,但我更喜欢写功能.


sha*_*afi 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);
}
Run Code Online (Sandbox Code Playgroud)

以前SoftReferences是一个很好的选择,但不再是,从官方页面引用:

注意:过去,流行的内存缓存实现是SoftReference或WeakReference位图缓存,但不建议这样做.从Android 2.3(API级别9)开始,垃圾收集器更积极地收集软/弱引用,这使得它们相当无效.此外,在Android 3.0(API Level 11)之前,位图的后备数据存储在本机内存中,而该内存未以可预测的方式释放,可能导致应用程序短暂超出其内存限制并崩溃.