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重新加载图片就可以得到目标图片的大小。

public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
        int reqWidth, int reqHeight) {

    // First decode with inJustDecodeBounds=true to check dimensions
    final BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeResource(res, resId, options);

    // Calculate inSampleSize
    options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);

    // Decode bitmap with inSampleSize set
    options.inJustDecodeBounds = false;
    return BitmapFactory.decodeResource(res, resId, options);
}

public static int calculateInSampleSize(
            BitmapFactory.Options options, int reqWidth, int reqHeight) {
    // Raw height and width of image
    final int height = options.outHeight;
    final int width = options.outWidth;
    int inSampleSize = 1;

    if (height > reqHeight || width > reqWidth) {

        final int halfHeight = height / 2;
        final int halfWidth = width / 2;
//图片实际大小每次对半减,直到长和宽同时小于等于我们需要的长和宽
        // Calculate the largest inSampleSize value that is a power of 2 and keeps both
        // height and width larger than the requested height and width.
        while ((halfHeight / inSampleSize) > reqHeight
                && (halfWidth / inSampleSize) > reqWidth) {
            inSampleSize *= 2;
        }
    }

    return inSampleSize;
}

Android中的缓存策略

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

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

int maxMemory = (int)(Runtime.getRuntime().maxMemory()/1024);
int cacheSize = maxMemory / 8;
mLruCache = new LruCache<String,Bitmap>(cacheSize) {
    @Override
    protected int sizeOf(String key,Bitmap bitmap) {
        return bitmap.getRowBytes() * bitmap.getHeight() / 1024;
    }

        protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) {
            //do something
        }
}

//获取缓存对象
mLruCache.get(key);
//存储缓存对象
mLruCache.put(key,bitmap);
//移除缓存对象
mLruCache.remote(key);

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

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.添加缓存

DiskLruCache.Editor editor = mDiskLruCache.editor(key);
OutputStream os = editor.newOutputStream(0);
if(downloadUrlToStream(url,os)) {
    editor.commit();
}else {
    editor.abort();
}
mDiskLruCache.flush();

3.缓存查找

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

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

4.删除缓存

ImageLoader的实现

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

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

优化列表的卡顿现象

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