在 你应用程序的UI界面加载一张图片是一件很简单的事情,可是当你须要在界面上加载一大堆图片的时候,状况就变得复杂起来。在不少状况下,(好比使用 ListView, GridView 或者 ViewPager 这样的组件),屏幕上显示的图片能够经过滑动屏幕等事件不断地增长,最终致使OOM。java
为了保证内存的使用始终维持在一个合理的范围,一般会把被移除屏幕的图片进行回收处理。此时垃圾回收器也会认为你再也不持有这些图片的引用,从而对这些图片 进行GC操做。用这种思路来解决问题是很是好的,但是为了能让程序快速运行,在界面上迅速地加载图片,你又必需要考虑到某些图片被回收以后,用户又将它重 新滑入屏幕这种状况。这时从新去加载一遍刚刚加载过的图片无疑是性能的瓶颈,你须要想办法去避免这个状况的发生。android
这个时候,使用内存缓存技术能够很好的解决这个问题,它可让组件快速地从新加载和处理图片。下面咱们就来看一看如何使用内存缓存技术来对图片进行缓存,从而让你的应用程序在加载不少图片的时候能够提升响应速度和流畅性。算法
内存缓存技术对那些大量占用应用程序宝贵内存的图片提供了快速访问的方法。其中最核心的类是LruCache (此类在android-support-v4的包中提供) 。这个类很是适合用来缓存图片,它的主要算法原理是把最近使用的对象用强引用存储在 LinkedHashMap 中,而且把最近最少使用的对象在缓存值达到预设定值以前从内存中移除。
缓存
在过去,咱们常常会使用一种很是流行的内存缓存技术的实现,即软引用或弱引用 (SoftReference or WeakReference)。可是如今已经再也不推荐使用这种方式了,由于从 Android 2.3 (API Level 9)开始,垃圾回收器会更倾向于回收持有软引用或弱引用的对象,这让软引用和弱引用变得再也不可靠。另外,Android 3.0 (API Level 11)中,图片的数据会存储在本地的内存当中,于是没法用一种可预见的方式将其释放,这就有潜在的风险形成应用程序的内存溢出并崩溃。
ide
为了可以选择一个合适的缓存大小给LruCache, 有如下多个因素应该放入考虑范围内,例如:函数
你的设备能够为每一个应用程序分配多大的内存?性能
设备屏幕上一次最多能显示多少张图片?有多少图片须要进行预加载,由于有可能很快也会显示在屏幕上?url
你的设备的屏幕大小和分辨率分别是多少?一个超高分辨率的设备(例如 Galaxy Nexus) 比起一个较低分辨率的设备(例如 Nexus S),在持有相同数量图片的时候,须要更大的缓存空间。spa
图片的尺寸和大小,还有每张图片会占据多少内存空间。.net
图片被访问的频率有多高?会不会有一些图片的访问频率比其它图片要高?若是有的话,你也许应该让一些图片常驻在内存当中,或者使用多个LruCache 对象来区分不一样组的图片。
你能维持好数量和质量之间的平衡吗?有些时候,存储多个低像素的图片,而在后台去开线程加载高像素的图片会更加的有效。
并无一个指定的缓存大小能够知足全部的应用程序,这是由你决定的。你应该去分析程序内存的使用状况,而后制定出一个合适的解决方案。一个过小的缓存空 间,有可能形成图片频繁地被释放和从新加载,这并无好处。而一个太大的缓存空间,则有可能仍是会引发 java.lang.OutOfMemory 的异常。
下面是一个使用 LruCache 来缓存图片的例子:
[java] view plaincopy
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;
}
};
}
public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
if (getBitmapFromMemCache(key) == null) {
mMemoryCache.put(key, bitmap);
}
}
public Bitmap getBitmapFromMemCache(String key) {
return mMemoryCache.get(key);
}
在 这个例子当中,使用了系统分配给应用程序的八分之一内存来做为缓存大小。在中高配置的手机当中,这大概会有4兆(32/8)的缓存空间。一个全屏幕的 GridView 使用4张 800x480分辨率的图片来填充,则大概会占用1.5兆的空间(800*480*4)。所以,这个缓存大小能够存储2.5页的图片。
当向 ImageView 中加载一张图片时,首先会在 LruCache 的缓存中进行检查。若是找到了相应的键值,则会马上更新ImageView ,不然开启一个后台线程来加载这张图片。
[java] view plaincopy
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 还要把新加载的图片的键值对放到缓存中。
[java] view plaincopy
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;
}
}