Android开发艺术探索:Bitmap的加载和Cache

Bitmap的高效加载

官方文档

加载Bitmap主要使用的是Android系统提供的BitmapFactory,提供了四种方法,decodeFile、decodeResource、decodeStream、decodeByteArray。
高效加载Bitmap就是利用BitmapFactory.Options来加载所需要的尺寸图片,利用采样率参数inSampleSize,当inSampleSize为1是,采样后的图片为原始大小,当采样率为2时,宽高都为原来的1/2,像素数为原来的1/4,内存大小为原来的1/4。imSampleSize小于1,无缩放效果。
加载图片步骤:
  将BitmapFactory.Options的inJustDecodeBounds参数设为true,并加载图片,就可以得到BitmpaFactory.Options里图片的原始宽高信息,对应于outWidth和outHeight,根据需要的图片大小和原始宽高计算采样与,再将BitmapFactory.Options.inJustDecodeBounds设置为false重新加载图片就可以得到目标图片的大小。

  1. public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
  2. int reqWidth, int reqHeight) {
  3. // First decode with inJustDecodeBounds=true to check dimensions
  4. final BitmapFactory.Options options = new BitmapFactory.Options();
  5. options.inJustDecodeBounds = true;
  6. BitmapFactory.decodeResource(res, resId, options);
  7. // Calculate inSampleSize
  8. options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
  9. // Decode bitmap with inSampleSize set
  10. options.inJustDecodeBounds = false;
  11. return BitmapFactory.decodeResource(res, resId, options);
  12. }
  13. public static int calculateInSampleSize(
  14. BitmapFactory.Options options, int reqWidth, int reqHeight) {
  15. // Raw height and width of image
  16. final int height = options.outHeight;
  17. final int width = options.outWidth;
  18. int inSampleSize = 1;
  19. if (height > reqHeight || width > reqWidth) {
  20. final int halfHeight = height / 2;
  21. final int halfWidth = width / 2;
  22. //图片实际大小每次对半减,直到长和宽同时小于等于我们需要的长和宽
  23. // Calculate the largest inSampleSize value that is a power of 2 and keeps both
  24. // height and width larger than the requested height and width.
  25. while ((halfHeight / inSampleSize) > reqHeight
  26. && (halfWidth / inSampleSize) > reqWidth) {
  27. inSampleSize *= 2;
  28. }
  29. }
  30. return inSampleSize;
  31. }

Android中的缓存策略

常用的缓存算法是LRU(Least Recently Used),最近最少使用算法,当缓存满是,优先淘汰最近最少使用的缓存对象。Android中主要用LruCache实现内存缓存,DiskLruCache实现磁盘缓存。

强应用:直接的对象引用
软引用:当一个对象只有软引用存在是,系统内存不足时此对象就会被gc回收
弱引用:当一个对象只有弱引用是,此对象随时被gc回收

  1. int maxMemory = (int)(Runtime.getRuntime().maxMemory()/1024);
  2. int cacheSize = maxMemory / 8;
  3. mLruCache = new LruCache<String,Bitmap>(cacheSize) {
  4. @Override
  5. protected int sizeOf(String key,Bitmap bitmap) {
  6. return bitmap.getRowBytes() * bitmap.getHeight() / 1024;
  7. }
  8. protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) {
  9. //do something
  10. }
  11. }
  12. //获取缓存对象
  13. mLruCache.get(key);
  14. //存储缓存对象
  15. mLruCache.put(key,bitmap);
  16. //移除缓存对象
  17. mLruCache.remote(key);

sizeOf用于计算缓存对象大小,这里的大小单位和总容量一直,这里是KB。LruCache源码

1.创建缓存

  1. public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize)

directory:可写的缓存目录,可以使用SD卡上面的缓存目录/sdcard/Android/data/package_name/cache,使用该目录的好处就是当APK卸载之后,就会自动删除目录
valueCount:单个节点所对应的缓存个数,一般为1即可
maxSize:缓存大小,单位字节

2.添加缓存

  1. DiskLruCache.Editor editor = mDiskLruCache.editor(key);
  2. OutputStream os = editor.newOutputStream(0);
  3. if(downloadUrlToStream(url,os)) {
  4. editor.commit();
  5. }else {
  6. editor.abort();
  7. }
  8. mDiskLruCache.flush();

3.缓存查找

  1. DiskLruCache.Snapshot snapShot = mDiskLruCache.get(key);
  2. if(snapShot != null) {
  3. FileInputStream fis = (FileInputStream)snapShot.getInputStrean(0);
  4. FileDescriptor fd = fis.getFD();
  5. //...
  6. }

FileInputStream是一种有序的文件流,decodeStream会影响文件流的属性,导致第二次decodeStream为空,可以通过BitmapFactory.decodeFileDescriptor方法来加载一张缩放后的图片。

4.删除缓存

ImageLoader的实现

一个优秀的ImageLoader应该具备的功能:

文中的例子使用线程池和Handler来实现的,示例代码

优化列表的卡顿现象

列表滑动的时候停止加载图片,停止下来在加载图片,可以给ListView或者GridView设置setOnScrillListener,在OnScrollListener的onScrollStateChanged方法中判断列表是否处于滑动状态。