为什么要做缓存?      

在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) 
    { @Overrideprotected 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. @Overrideprotected 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. @Overrideprotected 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; } @Overridepublic void onCreate(Bundle savedInstanceState) 
    { super.onCreate(savedInstanceState); setRetainInstance(true); }
}

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

转自:链接

转载于:https://www.cnblogs.com/YangBinChina/p/4618415.html

Android的图片缓存ImageCache(转)相关推荐

  1. Android之图片缓存管理

    如果每次加载同一张图片都要从网络获取,那代价实在太大了.所以同一张图片只要从网络获取一次就够了,然后在本地缓存起来,之后加载同一张图片时就从缓存中加载就可以了.从内存缓存读取图片是最快的,但是因为内存 ...

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

    from: http://www.csdn.net/article/2015-10-21/2825984 [CSDN现场报道]10月14日-16日," 2015移动开发者大会 · 中国&qu ...

  3. Android三级图片缓存框架思路【附练习Demo】

    缓存的简介 现在android应用中不可避免的要使用图片,有些图片是可以变化的,需要每次启动时从网络拉取,这种场景在有广告位的应用以及纯图片应用(比如百度美拍)中比较多.
        现在有一个问 ...

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

    一. 四大图片缓存基本信息 Universal ImageLoader 是很早开源的图片缓存,在早期被很多应用使用. Picasso 是 Square 开源的项目,且他的主导者是 JakeWharto ...

  5. Android的图片缓存处理

    Android的bitmap Bitmap:称为位图文件,扩展名可以是.bmp或者.dib. Bitmap图片定义为由像素点组成,每个像素点可以由多种色彩表示 下面是四种色彩格式 ARGB_8888: ...

  6. Android中图片缓存、显示框架Glide的介绍与使用

    1 介绍 Glide是一个快速高效的Android图片加载库,注重于平滑的滚动.Glide提供了易用的API,高性能.可扩展的图片解码管道(decode pipeline),以及自动的资源池技术. G ...

  7. Android获取图片缓存及清除

    封装一个类 package com.jianong.xmtt.util;import android.content.Context; import android.os.Environment;im ...

  8. android自定义图片缓存,适用于Android的本地图像缓存解决方案:Squ...

    更新于2018年9月:几年后,我需要与本地图像缓存解决方案几乎相同的东西.这一次,UIL尚未积极开发.我比较了流行的库,结论很简单:只需使用Glide.它功能强大且可配置.多年前我不得不分叉并对UIL ...

  9. android webview 图片缓存,WebView 图片离线缓存(含图片)

    自打去年十一来到掘金,就想着有一点一定会做 WebView 离线缓存,作为一个阅读类 app,不敢想象在没有离线缓存的情况下是怎么撑了这么久的

最新文章

  1. dr优先级默认_当配置一个CISCO的路由器时,缺省的DR和BDR优先级是()。
  2. 告别Heatmap!人体姿态估计表征新方法SimDR
  3. python读取文件第n行-Python读取文件最后n行的方法
  4. 为什么Go没有三元运算符
  5. storm 动态设置并发度
  6. Java当中 报错 没有有任何类型 的外层实例可访问
  7. C# 字符串逗号分隔存到List 数组(互相转换)
  8. NOI提高级:排序算法之归并排序、快速排序
  9. discuz php 扩展环境 不支持,配置php扩展memcache
  10. 英特尔、阿里巴巴全方位深化技术创新,共同引领数智未来
  11. 在安装mysql出现的错误以及解决方法
  12. Sueetie源代码发布【 推荐 】
  13. 韩荣温控器nx2使用说明书_如何查看您的Ecobee温控器的使用历史记录
  14. 寻找故障检测相关论文的期刊
  15. xp iis访问元数据库失败
  16. 支付宝当面付网站对接支付教程
  17. 【N32G457 】基于RT-Thread和N32G457 数码管时钟
  18. 【花雕小实验01】使用万用表测量OPT101模拟光照传感器模块
  19. Oracle11gR2(二)-图形安装
  20. 【bugku CTF】MISC杂项:很普通的数独(isccctf)、啊哒、隐写、隐写2、多种方法解决、easy_crypto 、聪明的小羊、ping

热门文章

  1. IEEE深度对话Facebook人工智能负责人Yann LeCun:让深度学习摆脱束缚
  2. hihoCoder #1467 : 2-SAT·hihoCoder音乐节
  3. 为什么是错的?????
  4. 常见排序算法(C实现)
  5. java如何实现乌龟爬行_乌龟是怎样爬行的
  6. jsp springmvc 视图解析器_SpringMVC学习笔记
  7. winmerge 注意事项
  8. 使用 CORBA 和 Java IDL
  9. vue路由切换和用location切换url的区别
  10. 第一天开始学习使用git中遇到的问题