Android图片缓存技术

一、压缩图片

BitmapFactory这个类提供了多个解析方法(decodeByteArray, decodeFile, decodeResource等)用于创建Bitmap对象,我们应该根据图片的来源选择合适的方法。比如SD卡中的图片可以使用decodeFile方法,网络上的图片可以使用decodeStream方法,资源文件中的图片可以使用decodeResource方法。每一种解析方法都提供了一个可选的BitmapFactory.Options参数,将这个参数的inJustDecodeBounds属性设置为true就可以让解析方法禁止为bitmap分配内存。这样在不加载图片的情况下就可以得到图片的一些属性值,这些属性值存放在BitmapFactory.Options中。

BitmapFactory.Options options = new BitmapFactory.Options(); 
// 声明图片不加载入内存
options.inJustDecodeBounds = true; 
// 解析图片,主要为了获取到图片的属性,会存放到options中
BitmapFactory.decodeResource(getResources(), R.id.myimage, options); 
// 获取图片属性
int imageHeight = options.outHeight; 
int imageWidth = options.outWidth; 
String imageType = options.outMimeType; 

上面就提供了一种压缩的思路,两次解析图片
1、声明options.inJustDecodeBounds = true来解析,主要为了获取到图片的属性,这样就可以计算压缩比例
2、声明options.inJustDecodeBounds = flase来解析,从第一步计算出压缩比例,在第二步就可以进行压缩加载了,这个时候就可以载入内存了。

public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,  
        int reqWidth, int reqHeight) {  
    // 第一次解析将inJustDecodeBounds设置为true,来获取图片大小 
    final BitmapFactory.Options options = new BitmapFactory.Options();  
    options.inJustDecodeBounds = true;  
    BitmapFactory.decodeResource(res, resId, options);  
    // 调用上面定义的方法计算inSampleSize值,赋值压缩比例
    options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);  
    // 使用获取到的inSampleSize值再次解析图片 
    options.inJustDecodeBounds = false;  
    // 第二次解析就直接加入进入内存
    return BitmapFactory.decodeResource(res, resId, options);  
}  


public static int calculateInSampleSize(BitmapFactory.Options options,  
        int reqWidth, int reqHeight) {  
    // 源图片的高度和宽度 
    final int height = options.outHeight;  
    final int width = options.outWidth;  
    int inSampleSize = 1;  
    if (height > reqHeight || width > reqWidth) {  
        // 计算出实际宽高和目标宽高的比率 
        final int heightRatio = Math.round((float) height / (float) reqHeight);  
        final int widthRatio = Math.round((float) width / (float) reqWidth);  
        // 选择宽和高中最小的比率作为inSampleSize的值,这样可以保证最终图片的宽和高 
        // 一定都会大于等于目标的宽和高。 
        inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;  
    }  
    return inSampleSize;  
}  

二、软引用

private Map<String, SoftReference<Bitmap>> imageMap 
                                           = new HashMap<String, SoftReference<Bitmap>>();

public Bitmap loadBitmap(final String imageUrl) {
    // 查看内存是否存在该图片
    SoftReference<Bitmap> reference = imageMap.get(imageUrl);
    if(reference != null) {
        if(reference.get() != null) {
            return reference.get();
        }
    }

    //如果内存不存在该图片,从网络异步获取,代码省略
    获取到之后,就把该图片放入缓存
    imageMap.put(imageUrl, new SoftReference<Bitmap>(bitmap));
    return bitmap;
}

但是现在已经不再推荐使用这种方式了,因为从 Android 2.3 (API Level 9)开始,垃圾回收器会更倾向于回收持有软引用或弱引用的对象,这让软引用和弱引用变得不再可靠。另外,Android 3.0 (API Level 11)中,图片的数据会存储在本地的内存当中,因而无法用一种可预见的方式将其释放,这就有潜在的风险造成应用程序的内存溢出并崩溃。

三、LruCache

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、DiskLruCache

LruCache只是管理了内存中图片的存储与释放,如果图片从内存中被移除的话,那么又需要从网络上重新加载一次图片,这显然非常耗时。对此,又一套硬盘缓存的解决方案:DiskLruCache

1、获取DiskLruCache实例

DiskLruCache mDiskLruCache = null;  
try {  
    File cacheDir = getDiskCacheDir(context, "bitmap");  
    if (!cacheDir.exists()) {  
        cacheDir.mkdirs();  
    }  
    // 获取DiskLruCache的实例
    第一个参数指定的是数据的缓存地址
    //第二个参数指定当前应用程序的版本号
    //第三个参数指定同一个key可以对应多少个缓存文件,基本都是传1
    //第四个参数指定最多可以缓存多少字节的数据。
    mDiskLruCache = DiskLruCache.open(cacheDir, getAppVersion(context), 1, 10 * 1024 * 1024);  
} catch (IOException e) {  
    e.printStackTrace();  
}  

2、写操作

String imageUrl = "http://ixxx.jpg";  
String key = hashKeyForDisk(imageUrl);  

DiskLruCache.Editor editor = mDiskLruCache.edit(key);  
// 将网络获取的内容写入outputStream,即写入本地
if (editor != null) {  
    OutputStream outputStream = editor.newOutputStream(0);  
    if (downloadUrlToStream(imageUrl, outputStream)) {  
        editor.commit();  
    } else {  
        editor.abort();  
    }  
}  
mDiskLruCache.flush();  
} catch (IOException e) {  
e.printStackTrace();  
}  

3、读操作

String key = hashKeyForDisk(imageUrl);  
DiskLruCache.Snapshot snapShot = mDiskLruCache.get(key);  
if (snapShot != null) {  
    InputStream is = snapShot.getInputStream(0);  
    Bitmap bitmap = BitmapFactory.decodeStream(is);  
    mImage.setImageBitmap(bitmap);  
} 

参考文章:
Android高效加载大图、多图解决方案,有效避免程序OOM

Android DiskLruCache完全解析,硬盘缓存的最佳方案

你可能感兴趣的:(android,图片缓存)