【转】http://www.jxmfkj.com/archives/189
假设你开发了一个聊天程序,它的好友列表中显示从网络获取的好友头像。可是如果用户发现每次进入好友列表的时候,程序都要重新下载头像才能进行显示,甚至当把列表滑动到底部再重新滑动回顶部的时候,刚才已经加载完成了的头像竟然又变成了空白图片开始重新加载,这将是一种糟糕的用户体验。为了解决这种问题,你需要使用高速缓存技术——Cache。
什么是Cache?
Cache,高速缓存,原意是指计算机中一块比内存更高速容量更小的存储器。更广义地说,Cache指对于最近使用过的信息的可高速读取的存储块。而本文要讲的Cache技术,指的就是将最近使用过的Bitmap缓存在手机的内存与磁盘中,来实现再次使用Bitmap时的瞬时加载,以节省用户的时间和手机流量。
下面将针对Android中的两种Cache类型Memory Cache和Disk Cache分别进行介绍。样例代码取自Android开发者站。
1/2:Memory Cache(内存中的Cache)
Memory Cache使用内存来为应用程序提供Cache。由于内存的读写速度非常快,所以我们应该优先使用它(相对于下面将介绍的Disk Cache来说)。
Android中提供了LruCache类来进行Memory Cache的管理(该类是在Android 3.1时推出的,但我们可以使用android -support-v4.jar的兼容包来对低版本的手机提供支持)。
提示:有人习惯使用SoftReference和WeakReference来做Memory Cache,但谷歌官方不建议这么做。因为自从Android2.3之后,Android中的GC变得更加积极,导致这种做法中缓存的Bitmaps非常容易被回收掉;另外,在Android3.0之前,Bitmap的数据是直接分配在native memory中,它的释放是不受dalvik控制的,因此更容易导致内存的溢出。如果你喜欢简单粗暴的总结,那就是:反正不要用这种方法来管理Memory Cache。
下面我们看一段为Bitmap设置LruCache的代码:
01 | private LruCache<String, Bitmap> mMemoryCache; |
04 | protected void onCreate(Bundle savedInstanceState) { |
06 | // 获取虚拟机可用内存(内存占用超过该值的时候,将报OOM异常导致程序崩溃)。最后除以1024是为了以kb为单位 |
07 | final int maxMemory = ( int ) (Runtime.getRuntime().maxMemory() / 1024 ); |
09 | // 使用可用内存的1/8来作为Memory Cache |
10 | final int cacheSize = maxMemory / 8 ; |
12 | mMemoryCache = new LruCache<String, Bitmap>(cacheSize) { |
14 | protected int sizeOf(String key, Bitmap bitmap) { |
15 | // 重写sizeOf()方法,使用Bitmap占用内存的kb数作为LruCache的size |
16 | return bitmap.getByteCount() / 1024 ; |
22 | public void addBitmapToMemoryCache(String key, Bitmap bitmap) { |
23 | if (getBitmapFromMemCache(key) == null ) { |
24 | mMemoryCache.put(key, bitmap); |
28 | public Bitmap getBitmapFromMemCache(String key) { |
29 | return mMemoryCache.get(key); |
提示:在以上代码中,我们使用了可用内存的1/8来提供给Memory Cache,我们简单分析一下这个值。一个普通屏幕尺寸、hdpi的手机的可用内存为32M,那么他的Memory Cache为32M/8=4M。通常hdpi的手机为480*800像素,它一个全屏Bitmap占用内存为480*800*4B=1536400B≈1.5M。那么4M的内存为大约2.5个屏幕大小的bitmap提供缓存。同理,一个普通尺寸、xhdpi大小的720*1280的手机可以为大约2.2个屏幕大小的bitmap提供缓存。
当一个ImageView需要设置一个bitmap的时候,LruCache会进行检查,如果它已经缓存了相应的bitmap,它就直接取出来并设置给这个ImageView;否则,他将启动一个后台线程加载这个Bitmap:
01 | public void loadBitmap( int resId, ImageView imageView) { |
02 | final String imageKey = String.valueOf(resId); |
04 | final Bitmap bitmap = getBitmapFromMemCache(imageKey); |
06 | mImageView.setImageBitmap(bitmap); |
08 | mImageView.setImageResource(R.drawable.image_placeholder); |
09 | BitmapWorkerTask task = new BitmapWorkerTask(mImageView); |
BitmapWorkerTask在加载完成后,通过前面的addBitmapToMemoryCache()方法把这个bitmap进行缓存:
01 | class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> { |
05 | protected Bitmap doInBackground(Integer... params) { |
06 | final Bitmap bitmap = decodeSampledBitmapFromResource( |
07 | getResources(), params[ 0 ], 100 , 100 )); |
08 | addBitmapToMemoryCache(String.valueOf(params[ 0 ]), bitmap); |