Android 图像缓存

Posted

技术标签:

【中文标题】Android 图像缓存【英文标题】:Android image caching 【发布时间】:2010-12-29 01:08:53 【问题描述】:

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

【问题讨论】:

【参考方案1】:

现在的重点是:使用系统缓存。

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

提供内存和闪存缓存,与浏览器共享。

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

【讨论】:

哇,这是一种非常优雅的方法,非常感谢。它丝毫不比我自己的简单缓存管理器慢,而且现在我不需要在 SD 卡文件夹上做家务。 connection.getContent() 总是为我返回一个 InputStream,我做错了什么? 如果我现在也可以为缓存的内容设置过期日期,我的生活会轻松很多 :) @Scienceprodigy 不知道 BitmapLoader 是什么,当然不在我所知道的任何标准 android 库中,但它至少引导我朝着正确的方向前进。 Bitmap response = BitmapFactory.decodeStream((InputStream)connection.getContent()); 请务必在下面查看 Joe 的回答,了解您需要采取哪些额外步骤才能使缓存正常工作【参考方案2】:

关于上面优雅的connection.setUseCaches 解决方案:遗憾的是,如果不付出一些额外的努力,它将无法工作。您需要使用ResponseCache.setDefault 安装ResponseCache。否则,HttpURLConnection 将默默地忽略 setUseCaches(true) 位。

详情见FileResponseCache.java顶部的cmets:

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

(我会在评论中发布此内容,但我显然没有足够的 SO 业力。)

【讨论】:

这里是file 当您使用 HttpResponseCache 时,您可能会发现 HttpResponseCache.getHitCount() 返回 0。我不确定,但我认为这是因为您请求的网络服务器不使用缓存标头案子。要使缓存正常工作,请使用connection.addRequestProperty("Cache-Control", "max-stale=" + MAX_STALE_CACHE); Google codesearch 链接已失效(再次?),请更新链接。 另外,我不确定此行为现在是否已修复。由于某种原因,从服务器返回 304 会在使用 .getContent() 方法时挂起 HUC,因为 304 响应没有 RFC 标准的关联响应主体。【参考方案3】:

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

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

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

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

【讨论】:

我们如何将图像写入sd或手机内存? 将图像保存在 SD 卡上:您可以使用普通文件 I/O 操作将从远程服务器读取的图像流提交到内存,或者如果您已将图像转换为位图对象,则可以使用位图.compress() 方法。 @d-man 我建议先将文件写入磁盘,然后获取Uri 路径引用,您可以将其传递给ImageView 和其他自定义视图。因为每次你compress,你都会失去质量。当然,这仅适用于有损算法。此方法还允许您存储文件的哈希值,并在下次通过 If-None-MatchETag 标头从服务器请求文件时使用它。 @TheRealChx101 能否请您帮助理解您的意思下次您通过 If-None-Match 和 ETag 标头从服务器请求文件时 i>,我基本上是在寻找解决方案,其中图像应该保留在定义的时间段内使用本地缓存,或者如果无法实现,那么每当 URL 的内容发生变化时,它应该在应用程序中反映最新的内容并缓存它. @CoDe 现在访问此链接,android.jlelse.eu/…【参考方案4】:

使用LruCache 有效地缓存图像。您可以从Android Developer site 阅读有关LruCache 的信息

我使用以下解决方案在 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 步:您的 ActivityAdapter 的使用情况

注意:如果你想从Activity类的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() 您可以在应用程序的第一个 Activity 中使用此语句。初始化缓存后,如果您使用的是 ImagesCache 实例,则无需每次都对其进行初始化。

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

编辑:

现在有非常著名的库被称为PicassoGlide,它们可以用来在android 应用程序中非常有效地加载图像。试试这个非常简单实用的库Picasso for android 和Glide For Android。您无需担心缓存图像。

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

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

您也可以访问difference between Glide and Picasso的博客

【讨论】:

出色的答案和解释!我认为这是最好的解决方案,因为它在离线时工作并使用 Android LruCache。我发现 edrowland 的解决方案在飞行模式下无法工作,即使添加了 Joe 需要更多的努力来集成。顺便说一句,即使您什么都不做,Android 或网络似乎也提供了大量的缓存。 (一个小问题:对于 getImageFromWareHouse 的示例用法,“H”应该是小写以匹配。)谢谢! 您能否解释一下 getImage() 方法,特别是它对图像大小的作用以及它是如何发生的。例如,我不明白为什么您要再次在其内部调用该函数以及它是如何工作的。 @Greyshack。基本上LruCache 具有键值对,每当您获得图像网址时,getImage() 都会从网址下载图像。图像的 Url 将是 LruCachekey 和 Bitmap 将是值,如果您仔细查看 DownloadImageTask 您可以设置 desiredWidthdesiredHeight 值您设置较小的宽度和高度您将看到的图像质量越低。 支持if(cache == null) 解决了我的问题! :) 另见最后我编辑的答案。我已经提到了现在大多数开发人员使用的著名库。试试那些毕加索:square.github.io/picasso 和滑翔:futurestud.io/blog/glide-getting-started【参考方案5】:

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

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

【讨论】:

为什么要解码 JPEG 然后重新编码?最好将 URL 下载到字节数组,然后使用该字节数组创建位图并写入文件。每次对 JPEG 进行解码和重新编码时,图像质量都会变差。 公平点,更多的是速度而不是任何东西。虽然,如果保存为字节数组并且源文件不是 JPEG,那么文件是否不需要转换? SDK中的“decodeByteArray”返回“解码的位图,如果无法解码图像数据,则返回null”所以这让我认为它总是在解码图像数据,所以这不需要再次重新编码吗? 说到效率,不传FileOutputStream,而是传BufferedOutputStream,岂不是更高效? 我不建议将图像缓存到您的 SD 卡。卸载应用程序后,图像不会被删除,导致 sd 卡被无用的垃圾填满。将图像保存到应用程序的缓存目录是首选 IMO 现在 APK 限制为 50mb,缓存到 SD 卡可能是开发者的唯一途径。【参考方案6】:

我会考虑使用 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 该链接仍然无效。我写了一个类似的库,叫做 Android-ImageManager github.com/felipecsl/Android-ImageManager【参考方案7】:

我已经尝试过 SoftReferences,它们在 android 中被过度回收,我觉得使用它们没有意义

【讨论】:

同意 - 在我测试过的设备上,SoftReferences 很快就会被回收 Google 自己证实,Dalvik 的 GC 非常积极地收集 SoftReferences。他们建议改用LruCache【参考方案8】:

正如 Thunder Rabbit 所建议的,ImageDownloader 是最适合这项工作的。我还在以下位置发现了类的细微变化:

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

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

【讨论】:

【参考方案9】:

这是乔的一个很好的收获。上面的代码示例有两个问题 - 一个 - 响应对象不是 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

我仍然认为这是走这条路的好解决方案 - 但您仍然必须编写缓存。听起来很有趣,但我宁愿写功能。

【讨论】:

【参考方案10】:

在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 Level 9) 开始的垃圾 收集器在收集软/弱引用方面更具侵略性 这使得它们相当无效。此外,在 Android 3.0 之前 (API 级别 11),位图的支持数据存储在本地 未以可预测的方式释放的内存,可能 导致应用程序短暂超出其内存限制并崩溃。

【讨论】:

【参考方案11】:

考虑使用Universal Image Loader library by Sergey Tarasevich。它附带:

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

通用图像加载器允许对下载的图像进行详细的缓存管理,具有以下缓存配置:

UsingFreqLimitedMemoryCache:当超过缓存大小限制时,最不频繁使用的位图被删除。 LRULimitedMemoryCache:当超过缓存大小限制时,最近最少使用的位图将被删除。 FIFOLimitedMemoryCache:超过缓存大小限制时使用FIFO规则进行删除。 LargestLimitedMemoryCache:当超过缓存大小限制时,最大的位图被删除。 LimitedAgeMemoryCache: Cached 对象在其年龄超过定义值时被删除。 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()”)。在加载大量图像然后旋转我的屏幕几次后,我的应用程序一直崩溃,因为 UIL 中的 OutOfMemoryErrors。这是一个很棒的库,但众所周知的事实是你永远不应该使用单例,尤其是在 Android 中...... 当你知道如何使用单例时! :)【参考方案12】:

实际上对我有用的是在我的 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 结合使用【参考方案13】:

Google 的 libs-for-android 有一个很好的库来管理图像和文件缓存。

http://code.google.com/p/libs-for-android/

【讨论】:

Deprecated。现在看看 BitmapFun。【参考方案14】:

我已经为此苦苦挣扎了一段时间;使用 SoftReferences 的答案会很快丢失数据。建议实例化 RequestCache 的答案太混乱了,而且我永远找不到完整的例子。

但是ImageDownloader.java 对我来说非常有用。它使用 HashMap 直到达到容量或直到发生清除超时,然后将事情转移到 SoftReference,从而使用两全其美。

【讨论】:

【参考方案15】:

我建议IGNITION这比Droid fu还要好

https://github.com/kaeppler/ignition

https://github.com/kaeppler/ignition/wiki/Sample-applications

【讨论】:

【参考方案16】:

甚至稍后回答,但我写了一个 Android 图像管理器,它可以透明地处理缓存(内存和磁盘)。代码在 Github https://github.com/felipecsl/Android-ImageManager

【讨论】:

我将它添加到 ListView 中,但它似乎处理得不是很好。 ListViews 有什么特殊的实现吗?【参考方案17】:

迟到的答案,但我想我应该添加一个指向我的网站的链接,因为我已经写了一个教程如何为 android 制作图像缓存:http://squarewolf.nl/2010/11/android-image-cache/ 更新: 由于来源已过时,该页面已脱机。我和@elenasys 一起建议使用Ignition。

致所有偶然发现这个问题但尚未找到解决方案的人:希望你们喜欢! =D

【讨论】:

【参考方案18】:

迟到的答案,但我认为这个库对缓存图像有很大帮助: 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

【讨论】:

以上是关于Android 图像缓存的主要内容,如果未能解决你的问题,请参考以下文章

清除滑动图像缓存 Android Studio

如何从 URL 下载图像并缓存 Android (React-Native)

在webview android中缓存图像[重复]

Android 类似浏览器的实现,我在哪里缓存图像

Android照片库识别新添加的图像很慢(缓存问题?)

使用 felipecsl Android-ImageManager 库如何替换缓存的图像