Android的图片缓存ImageCache(转)

为什么要做缓存?      

在UI界面加载一张图片时很简单,然而如果需要加载多张较大的图像,事情就会变得更加复杂。在许多情况下(如ListView、GridView或ViewPager等的组件),屏幕上的图片的总数伴随屏幕的滚动会大大增加,且基本上是无限的。

为了使内存使用保持在稳定范围内,防止出现OOM,这些组件会在子view画出屏幕后,对其进行资源回收,并重新显示新出现的图片,垃圾回收机制会释放掉不再显示的图片的内存空间。但是这样频繁地处理图片的加载和回收不利于操作的流畅性,而内存或者磁盘的Cache就会帮助解决这个问题,实现快速加载已加载的图片。

在缓存上,主要有两种级别的Cache:LruCache和DiskLruCache。 前者是基于内存的,后者是基于磁盘的。

如何在内存中做缓存?

通过内存缓存可以快速加载缓存图片,但会消耗应用的内存空间。LruCache类(通过兼容包可以支持到sdk4)很适合做图片缓存,它通过LinkedHashMap保持图片的强引用方式存储图片,当缓存空间超过设置定的限值时会释放掉早期的缓存。

注:在过去,常用的内存缓存实现是通过SoftReference或WeakReference,但不建议这样做。从Android2.3(API等级9)垃圾收集器开始更积极收集软/弱引用,这使得它们相当无效。此外,在Android 3.0(API等级11)之前,存储在native内存中的可见的bitmap不会被释放,可能会导致应用程序暂时地超过其内存限制并崩溃。

为了给LruCache设置合适的大小,需要考虑以下几点因素:

  • 你的应用中空闲内存是多大?
  • 你要在屏幕中一次显示多少图片? 你准备多少张图片用于显示?
  • 设备的屏幕大小与density 是多少?超高屏幕density的设备(xhdpi)像Galaxy Nexus 比 Nexus S (hdpi)这样的设备在缓存相同的图片时需要更大的Cache空间。
  • 图片的大小和属性及其需要占用多少内存空间?
  • 图片的访问频率是多少? 是否比其他的图片使用的频率高?如果这样你可能需要考虑将图片长期存放在内存中或者针对不同类型的图片使用不同的缓存策略。
  • 如何平衡质量与数量,有事你可能会存储一些常用的低质量的图片用户显示,然后通过异步线程加载高质量的图片。

图片缓存方案没有固定的模式使用所有的的应用,你需要根据应用的具体应用场景进行分析,选择合适的方案来做,缓存太小不能发挥缓存的优势,太大可能占用过多的内存,降低应用性能,或者发生内存溢出异常,

下面是一个使用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);
}

注意:在这个例子中,应用八分之一的内存分配给图片缓存,在普通/hdpi设备中大约为4MB(32/8)。GirdView全屏时在800x480分辨率的设备中需要1.5M图片空间(800*480*4 bytes),这样就可以在内存中缓存2.5屏的图片。

运用LruCache向ImageView添加图片时首先先检查图片是否存在,如果在直接更行ImageView,否则通过后台线程加载图片:

public void loadBitmap(int resId, ImageView imageView) 
{
    final String imageKey = String.valueOf(resId); 

    final Bitmap bitmap = getBitmapFromMemCache(imageKey);
    if (bitmap != null) 
    {
        imageView.setImageBitmap(bitmap);
    } 
    else 
    {
        imageView.setImageResource(R.drawable.image_placeholder);
        BitmapWorkerTask task = new BitmapWorkerTask(imageView);
        task.execute(resId);
    }
}

BitmapWorkerTask需要将将加载的图片添加到缓存中:

class BitmapWorkerTask extends AsyncTask 
{
    ...
    // Decode image in background.
    @Override
    protected Bitmap doInBackground(Integer... params) 
    {
        final Bitmap bitmap = decodeSampledBitmapFromResource(getResources(), params[0], 100, 100));
        addBitmapToMemoryCache(String.valueOf(params[0]), bitmap);
        return bitmap;
    }
    ...
}

如何使用磁盘缓存?

内存缓存对访问最近使用的图片时很高效,但是你不能保证它一直会在缓存中。像GirdView这样大数据量的组件很容易充满内存缓存。你的应用可能会被“来电”打断,在后台时可能会被杀掉,内存缓存就会失效,一旦用户重新回到应用中时,你需要重新处理每个图片。

在这种情况下我们可以运用磁盘缓存存储已处理的图片,当图片不再内存中时,减少重新加载的时间,当然从磁盘加载图片时要比内存中慢,需要在后台线程中做,因为磁盘的读取时间是未知的。

注意:如果你经常访问图片,ContentProvider应该是存储图片的好地方,如:Gallery图片管理应用。

下面是一个简单的DiskLruCache实现。然而推荐的实现DiskLruCache方案请参考Android4.0中(libcore/luni/src/main/java/libcore/io/DiskLruCache.java)源码。本文使用的是之前版本中的简单实现(Quick Search中是另外的实现).

显示是简单实现DiskLruCache更新后的例子:

private DiskLruCache mDiskCache;
private static final int DISK_CACHE_SIZE = 1024 * 1024 * 10; // 10MB
private static final String DISK_CACHE_SUBDIR = "thumbnails"; 

@Override
protected void onCreate(Bundle savedInstanceState) 
{
    ...
    // Initialize memory cache
    ...
    File cacheDir = getCacheDir(this, DISK_CACHE_SUBDIR);
    mDiskCache = DiskLruCache.openCache(this, cacheDir, DISK_CACHE_SIZE);
    ...
} 

class BitmapWorkerTask extends AsyncTask 
{
    ...
    // Decode image in background.
    @Override
    protected Bitmap doInBackground(Integer... params) 
    {
        final String imageKey = String.valueOf(params[0]); 

        // Check disk cache in background thread
        Bitmap bitmap = getBitmapFromDiskCache(imageKey); 

        if (bitmap == null) 
        { 
            // Not found in disk cache
            // Process as normal
            final Bitmap bitmap = decodeSampledBitmapFromResource(getResources(), params[0], 100, 100));
        } 

        // Add final bitmap to caches
        addBitmapToCache(String.valueOf(imageKey, bitmap); 

        return bitmap;
    }
    ...
} 

public void addBitmapToCache(String key, Bitmap bitmap) 
{
    // Add to memory cache as before
    if (getBitmapFromMemCache(key) == null) 
    {
        mMemoryCache.put(key, bitmap);
    } 

    // Also add to disk cache
    if (!mDiskCache.containsKey(key)) 
    {
        mDiskCache.put(key, bitmap);
    }
} 

public Bitmap getBitmapFromDiskCache(String key) 
{
    return mDiskCache.get(key);
} 

// Creates a unique subdirectory of the designated app cache directory. Tries to use external but if not mounted, falls back on internal storage.
public static File getCacheDir(Context context, String uniqueName) 
{
    // Check if media is mounted or storage is built-in, if so, try and use external cache dir otherwise use internal cache dir
    final String cachePath = (Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED || 
                             !Environment.isExternalStorageRemovable()) ?
                             context.getExternalCacheDir().getPath() : 
                             context.getCacheDir().getPath(); 

    return new File(cachePath + File.separator + uniqueName);
}

内存缓存检查在UI线程中做,磁盘缓存的检查在后台线程中。硬盘操作不应在UI线程中。图片处理完成后应将其加入正在使用的内存、磁盘缓存中。

如何处理配置的改变?

应用运行中配置改变时,如屏幕方向改变时为了应用新的配置Android会销毁重新运行当前的Activity,此时,为了给用户快速、平缓的用户体验你可能不想重新加载图片。

多亏你运行了缓存技术,缓存可以通过 setRetainInstance(true))传递给新的Activity,在Activity重启后,你可以通过附着的Fragment重新使用已存在的缓存,这样就可以快速加载到ImageView中了。

下面是一个当配置改变时用Fragment重用已有的缓存的例子:

private LruCache mMemoryCache; 

@Override
protected void onCreate(Bundle savedInstanceState) 
{
    ...
    RetainFragment mRetainFragment =
            RetainFragment.findOrCreateRetainFragment(getFragmentManager());
    mMemoryCache = RetainFragment.mRetainedCache;
    if (mMemoryCache == null) 
    {
        mMemoryCache = new LruCache(cacheSize) 
        {
            ... // Initialize cache here as usual
        }
        mRetainFragment.mRetainedCache = mMemoryCache;
    }
    ...
} 

class RetainFragment extends Fragment 
{
    private static final String TAG = "RetainFragment";
    public LruCache mRetainedCache; 

    public RetainFragment() {} 

    public static RetainFragment findOrCreateRetainFragment(FragmentManager fm) 
    {
        RetainFragment fragment = (RetainFragment) fm.findFragmentByTag(TAG);
        if (fragment == null) 
        {
            fragment = new RetainFragment();
        }
        return fragment;
    } 

    @Override
    public void onCreate(Bundle savedInstanceState) 
    {
        super.onCreate(savedInstanceState);
        setRetainInstance(true);
    }
}

为了测试,在使用和未使用Fragment的情况下,强制旋转屏幕,你会发现从保留内存缓存加载图片时几乎没有滞后。

转自:链接

时间: 2024-10-24 09:01:11

Android的图片缓存ImageCache(转)的相关文章

Android 三大图片缓存原理、特性对比

这是我在 MDCC 上分享的内容(略微改动),也是源码解析第一期发布时介绍的源码解析后续会慢慢做的事. 从总体设计和原理上对几个图片缓存进行对比,没用到他们的朋友也可以了解他们在某些特性上的实现. 上篇关于选择开源项目的好处及如何选择开源项目可见:开源项目使用及选型. 一. 四大图片缓存基本信息 Universal ImageLoader 是很早开源的图片缓存,在早期被很多应用使用. Picasso 是 Square 开源的项目,且他的主导者是 JakeWharton,所以广为人知. Glide

android之图片缓存的探究

在用户界面(UI)加载一张图片时很简单,然而,如果你需要加载多张较大的图像,事情就会变得更加复杂,.在许多情况下(如与像的ListView GridView或ViewPager的组件),屏幕上的图片的总数伴随屏幕上滚动的骤然增加,且基本上是无限的.为使内存使用保持在稳定范围内,这些组件会在子view在屏幕中消失后,对其进行资源回收,垃圾回收机制会释放掉已加载的图片内存空间,所以建议你不要保持图片的常引用,这样做很好,为了保证页面的流畅性和响应速度,你可能不愿意在页面返回时频繁处理加载过图片.通过

【MDCC 2015】开源选型之Android三大图片缓存原理、特性对比

摘要:这是快的打车移动端架构师.Android 开源项目源码解析codeKK发起人 吴更新(@Trinea)在MDCC上分享的内容,从总体设计和原理上对几个图片缓存进行对比,没用到它们的朋友也可以了解它们在某些特性上的实现. [CSDN现场报道]10月14日-16日," 2015移动开发者大会 · 中国"(Mobile Developer Conference China 2015,简称MDCC 2015)在北京新云南皇冠假日酒店隆重举行.本次大会由全球最大中文IT社区CSDN和中国最

Android四大图片缓存(Imageloader,Picasso,Glide,Fresco)原理、特性对比

四大图片缓存基本信息 Universal ImageLoader 是很早开源的图片缓存,在早期被很多应用使用. Picasso 是 Square 开源的项目,且他的主导者是 JakeWharton,所以广为人知. Glide 是 Google 员工的开源项目,被一些 Google App 使用,在去年的 Google I/O 上被推荐,不过目前国内资料不多. Fresco 是 Facebook 在今年上半年开源的图片缓存,主要特点包括:(1) 两个内存缓存加上 Native 缓存构成了三级缓存

Android ImageCache图片缓存,使用简单,支持预取,支持多种缓存算法,支持不同网络类型,扩展性强

本文主要介绍一个支持图片自动预取.支持多种缓存算法的图片缓存的使用及功能.图片较大需要SD卡保存情况推荐使用ImageSDCardCache. 与Android LruCache相比主要特性:(1).  使用简单   (2). 轻松获取及预取新图片  (3).  可选择多种缓存算法(FIFO.LIFO.LRU.MRU.LFU.MFU等13种)或自定义缓存算法   (4).  省流量性能佳(有且仅有一个线程获取图片)   (5).  支持不同类型网络处理  (6).  可根据系统配置初始化缓存 

【Java/Android性能优5】 Android ImageCache图片缓存,使用简单,支持预取,支持多种缓存算法,支持不同网络类型,扩展性强

本文转自:http://www.trinea.cn/android/android-imagecache/ 主要介绍一个支持图片自动预取.支持多种缓存算法.支持二级缓存.支持数据保存和恢复的图片缓存的使用.功能及网友反馈的常见问题解答. 与Android LruCache相比主要特性:(1). 使用简单  (2). 轻松获取及预取新图片  (3). 包含二级缓存  (4). 可选择多种缓存算法(FIFO.LIFO.LRU.MRU.LFU.MFU等 13种)或自定义缓存算法  (5). 可方便的保

Android:ViewPager扩展详解——带有导航的ViewPagerIndicator(附带图片缓存,异步加载图片)

大家都用过viewpager了, github上有对viewpager进行扩展,导航风格更加丰富,这个开源项目是ViewPagerIndicator,很好用,但是例子比较简单,实际用起来要进行很多扩展,比如在fragment里进行图片缓存和图片异步加载. 下面是ViewPagerIndicator源码运行后的效果,大家也都看过了,我多此一举截几张图: 下载源码请点击这里 ===========================================华丽的分割线==============

Android图片缓存分析(一)

Android中写应用时,经常会遇到加载图片的事,由于很多图片是网络上下载获取的,当我们进页面时,便会去网络下载图片,一两次可能没啥问题,但如果同一张图片每次都去网络拉取,不仅速度慢,更影响用户体验,同时会浪费用户的流量. 基于此,很多人便想到了图片缓存的方法. 现在比较普遍的图片缓存主要有以下几个步骤: 一.从缓存中获取图片 二.如果缓存中未获取图片,则从存储卡中获取 三.如果存储卡中未获取图片,则从网络中获取 一.从缓存中获取图片 我们知道,Android中分配给每个应用的内存空间是有限的,

Android 图片缓存设计

1.简介 大家都知道,在我们Android 开发的过程中,对于图片的处理,是非常重要的,而对于我们如果每次都重网络去拉去图片,那样会造成,现在android应用中不可避免的要使用图片,有些图片是可以变化的,需要每次启动时从网络拉取,这种场景在有广告位的应用以及纯图片应用(比如淘宝,qq的照片墙)中比较多. 现在有一个问题:假如每次启动的时候都从网络拉取图片的话,势必会消耗很多流量.在当前的状况下,对于非wifi用户来说,流量还是很贵的,一个很耗流量的应用,其用户数量级肯定要受到影响.当然,我想,