您的位置:首页 > 其它

LruCache内存缓存图片技术精炼详解

2018-04-09 18:40 399 查看

一、前期基础知识储备

对于高效加载图片,参见笔者《Bitmap精炼详解第(一)课:Bitmap解析和加载》,在这篇文章里,笔者讲解了Bitmap的相关理论知识,并且实现了图片的一般加载和高效加载,那么,对于图片的加载还有其他方式吗?在开发中一般为了尽可能避免OOM都会按照如下两种做法:
1)对于图片显示:根据需要显示图片控件的大小对图片进行压缩显示
2)如果图片数量非常多:则会使用LruCache类等缓存机制,将所有图片占据的内容维持在一个范围内
笔者在第一篇文章中实现了第一种方式加载图片,那么本篇文章将实现第二种很实用的加载方式——缓存
(1)为什么要使用缓存技术?
当你需要在界面上加载一大堆图片的时候,情况就会变得复杂起来。在很多情况下,(比如使用ListView,GridView 或者 ViewPager 这样的组件),屏幕上显示的图片可以通过滑动屏幕等事件不断地增加,如果采用的是普通的加载方式,最终会导致OOM。
为了保证内存的使用始终维持在一个合理的范围,通常会把被移除屏幕的图片进行回收处理。此时垃圾回收器也会认为你不再持有这些图片的引用,从而对这些图片进行GC操作。用这种思路来解决问题是非常好的,可是为了能让程序快速运行,在界面上迅速地加载图片,你又必须要考虑到某些图片被回收之后,用户又将它重新滑入屏幕这种情况。这时重新去加载一遍刚刚加载过的图片无疑是性能的瓶颈,这个时候,使用内存缓存技术可以很好的解决这个问题,它可以让组件快速地重新加载和处理图片。
内存缓存技术对那些大量占用应用程序宝贵内存的图片提供了快速访问的方法。其中最核心的类是LruCache类,这个类非常合适用来缓存图片。
(2)使用缓存前的考虑
1)缓存空间的大小,多少合适?2)不同分辨率的手机如何适配,需不需要根据用户的手机来判断缓存的空间?
3)图片被访问的频率有多高?会不会有一些图片的访问频率比其它图片要高?
4)图片的尺寸和大小,还有每张图片会占据多少内存空间?
实际上,并没有一个指定的缓存大小可以满足所有的应用程序,这是由你决定的。你应该去分析程序内存的使用情况,然后制定出一个合适的解决方案。一个太小的缓存空间,有可能造成图片频繁地被释放和重新加载,这并没有好处。而一个太大的缓存空间,则有可能还是会引起 OOM 的异常。

二、上代码,具体实现

上LruCache类的官方文档:
A cache that holds strong references to alimited number of values. Each time a value is accessed, it is moved to thehead of a queue. When a value is added to a full cache, the value at the end ofthat queue is evicted and may become eligible for garbage collection. If yourcached values hold resources that need to be explicitly released, override entryRemoved(boolean,K, V, V).
If a cache miss should be computed ondemand for the corresponding keys, override create(K). This simplifies thecalling code, allowing it to assume a value will always be returned, even whenthere's a cache miss.
By default, the cache size is measured inthe number of entries. Override sizeOf(K, V) to size the cache in differentunits.
由官方文档,我们知道,LruCache持有的是强引用的类型,这符合谷歌的推荐。
下面,我们来具体实现,实现一次缓存图片的加载,分4步走:
分配缓存的大小&衡量每张图片的大小;private LruCache<String, Bitmap> mMemoryCache;

@Override
protected void onCreate(Bundle savedInstanceState) {
// 获取到可用内存的最大值,使用内存超出这个值会引起OutOfMemory异常。
// LruCache通过构造函数传入缓存值,以KB为单位。
int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
// 使用最大可用内存值的1/8作为缓存的大小。
int cacheSize = maxMemory / 8;
mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap bitmap) {
// 重写此方法来衡量每张图片的大小,默认返回图片数量。
return bitmap.getByteCount() / 1024;
}
};
}在这个例子当中,使用了系统分配给应用程序的八分之一内存来作为缓存大小。在中高配置的手机当中,这大概会有4兆(32/8)的缓存空间。一个全屏幕的GridView 使用4张 800x480分辨率的图片来填充,则大概会占用1.5兆的空间(800*480*4)。因此,这个缓存大小可以存储2.5页的图片。
将图片存储到LruCache中&从LruCache中取出图片;/**
* 将一张图片存储到LruCache中。
*
* @param key
* LruCache的键,这里传入图片的URL地址。
* @param bitmap
* LruCache的键,这里传入从网络上下载的Bitmap对象。
*/
public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
if (getBitmapFromMemoryCache(key) == null) {
mMemoryCache.put(key, bitmap);
}
}

/**
* 从LruCache中获取一张图片,如果不存在就返回null。
*
* @param key
* LruCache的键,这里传入图片的URL地址。
* @return 对应传入键的Bitmap对象,或者null。
*/
public Bitmap getBitmapFromMemoryCache(String key) {
return mMemoryCache.get(key);
} ③ImageView加载图片;/**
* 给ImageView设置图片。首先从LruCache中取出图片的缓存,设置到ImageView上。如果LruCache中没有该图片的缓存,
* 就给ImageView设置一张默认图片。
*
* @param imageUrl
* 图片的URL地址,用于作为LruCache的键。
* @param imageView
* 用于显示图片的控件。
*/
private void setImageView(String imageUrl, ImageView imageView) {
Bitmap bitmap = getBitmapFromMemoryCache(imageUrl);
if (bitmap != null) {
imageView.setImageBitmap(bitmap);
} else {
imageView.setImageResource(R.drawable.empty_photo);
}
} 当向 ImageView 中加载一张图片时,首先会在 LruCache 的缓存中进行检查。如果找到了相应的键值,则会立刻更新ImageView ,否则开启一个后台线程来加载这张图片。
缓存新加载的图片到LruCache中&移除缓存//缓存新加载的图片到LruCache中
class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
// 在后台加载图片。
@Override
protected Bitmap doInBackground(Integer... params) {
final Bitmap bitmap = decodeSampledBitmapFromResource(
getResources(), params[0], 100, 100);
addBitmapToMemoryCache(String.valueOf(params[0]), bitmap);
return bitmap;
}

//移除缓存
public synchronized void removeImageCache(String key) {
if (key != null) {
if (mMemoryCache != null) {
Bitmap bm = mMemoryCache.remove(key);
if (bm != null)
bm.recycle();
}
}
}
总结:使用LruCache类的缓存技术与使用Map软引用方式相比,前者更加推荐,因为从 Android 2.3 (API Level9)开始,垃圾回收器会更倾向于回收持有软引用或弱引用的对象,这让软引用和弱引用变得不再可靠。
 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: