Android进阶(12)| 理解Bitmap的加载与Cache

Android进阶(12)| 理解Bitmap的加载与Cache_第1张图片
本节目录

一.Bitmap的高效加载

Bitmap:Bitmap在Android中指的是一张图片,可以实png格式也可以是jpg等其他常见的图片格式。

高效加载的核心思想:高效加载Bitmap主要是使用BitmapFactory.Options来加载所需尺寸的图片。其主要原理就是通过BitmapFactory.Options就可以按一定的采样率来加载缩小后的图片,将缩小后的图片在ImageView中显示,这样就会降低内存占用从而在一定程度上避免OOM。

高效加载过程:只要获得了BitmapFactory.Options的inSampleSize参数,即可以进行高效的加载,其过程如下:

inSampleSize即采样率,当inSampleSize的值为1时,表示采样后的图片大小为图片的原始大小;当inSampleSize的值为2时,则表示采样后的图片大小为原始图片的1/2,而像素数为原来的1/4,以此类推。需要注意的是当inSampleSize被赋予小于1时,则会被系统默认当作1来进行处理,并且inSampleSize在官方建议中应该是为2的指数。

(1)将BitmapFactory.Options的inJustDecodeBounds的参数设置true并加载图片。
(2)从BitmapFactory.Options中取出图片的原始宽高信息,他们对应于outWidth和outHeight参数。
(3)根据采样率的规则并结合目标View所需大小计算出采样率。
(4)将BitmapFactory.Options的inJustDecodeBounds参数设为false,然后重新加载图片。

上述4个过程用代码实现如下:

public static Bitmap decodeSampleFromResource(Resource res,int resId,int reqWidth,int reqHeight){
    final BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;  //设置为true
    BitmapFactory.decodeResource(res,resId,options);

    options.inSampleSize = calculateInSampleSize(options,reqWidth,reqHeight);
    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 halfHeight = height/2;
        final int halfWidth = width/2;

        while((halfHeight / inSampleSize) >= reqHeight && (halfWidth / inSampleSize) >= reqWidth){
            inSampleSize = inSampleSize*2;  //类似于递归
        }
    }

    return inSampleSize;
}


mImageView.setImageBitmap(  //对ImageView使用
    decodeSampledBitmapFromResource(getResources(),R.id.myImage,100,100)
);

二.Android中的缓存策略

缓存的概括:缓存,即当程序第一次从网上加载资源后,就将其缓存到存储设备上,这样下次再使用这张图片就不用再从网络上获取了。缓存策略一般包含缓存的添加、获取和删除这三类操作。目前最常用的缓存算法是LRU算法,基于该算法的缓存有两种:LruCache(内缓存)和DiskLruCache(存储设备缓存)。

1.LruCache

概念:LruCache是一个泛型类,它内部采用一个LinkedHashMap以强引用的方式存储外界的缓存对象,其提供了get和put方法来完成缓存的获取和添加操作。当缓存满时,LruCache会首先移除较早的缓存对象,再将新的缓存加入进去。LruCache是线程安全的。

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

public class LruCache{  //构造方法
    private final LinkedHashMap map;
}

LruCache的使用:
(1)初始化

int maxMemory = (int)(Runtime.getRuntime().maxMemory()/1024);
int cacheSize = maxMemory/8;
mMemoryCache = new LruCache(cacheSize){
    @Override
    protected int sizeOf(String key,Bitmap bitmap){  //计算缓存对象的大小
        return bitmap.getRowBytes()* bitmap.getHeight()/1024;  //转换单位
    }
};

(2)获取缓存对象

mMemoryCache.get(key)

(3)添加一个缓存对象

mMemoryCache.put(key,bitmap)

2.DiskLruCache

DiskLruCache的创建:DiskLruCache并不能通过构造方法来创建,它提供了open方法用于创建自身:

public static DiskLruCache open(File directory,int appVersion,int valueCount,long maxSize)
/*第一个参数:磁盘缓存在文件系统中的存储路径
第二个参数:应用版本号
第三个参数:单个节点所对应的数据的个数,默认值为1
第四个参数:缓存的总大小,超过这个大小就会自动清除一些缓存*/

DiskLruCache的典型创建过程如下:

private static final long DISK_CACHE_SIZE=1024*1024*50;  //50MB
File diskCacheDir = getDiskCacheDir(mContext,"bitmap");
if(!diskCacheDir.exists()){
    diskCacheDir.mkdirs();
}
mDiskLruCache = DiskLruCache.open(diskCacheDir,1,1,DISK_CACHE_SIZE);

DiskLruCache的缓存添加:往DiskLruCache中添加缓存,首先是需要获取到图片url所对应的key:

private String hashKeyFormUrl(String url){
    String cacheKey;
    try{
        final MessageDigest mDigest = MessageDigest.getInstance("MD5");  //将url中的md5的值作为key
        mDigest.update(url.getBytes());
        cacheKey = bytesToHexString(mDigest.digest());
    }cache(NoSuchAlgorithmException e){
        cacheKey = String.valueOf(url.hashCode());
    }
    return cacheKey;
}



private String bytesToHexString(byte[] bytes){
    StringBuilder sb = new StringBuilder();
    for(int i=0;i

在将url转换为key之后,就可以获取Editor对象,如下所示:

String key = hashKeyFormUrl(url);
DiskLruCache.Editor editor = mDiskLruCache.edit(key);
if(editor != null){
    OutputStream outputStream = editor.newOutputStream(DISK_CACHE_INDEX);  //得到一个文件输出流
}

在获得了文件输出流之后,我们就可以通过这个文件输出流将文件写入到文件系统当中,最后只需要通过Editor的commit()来提交写入操作,如果这个过程发生了异常,可以通过Editor的abrot()来回退整个操作:

OutputStream outputStream = editor.newOutputStream(DISK_CACHE_INDEX);
if(downloadUrlToStream(url,outputStream)){  //没发生异常,则直接提交
    editor.commit();
}
else{  //发生异常,进行回退
    editor.abrot();
}
mDiskLruCache.flush();

DiskLruCache的缓存查找:缓存查找也需要将url转换为key,然后通过DiskLruCache的get方法得到一个Snapshot对象,通过该对象即可得到缓存的文件输入流,之后就可以根据文件输入流得到Bitmap对象了:

Bitmap bitmap = null;
String key = hashKeyFormUrl(url);
DiskLruCache.Snapshot snapShot = mDiskLruCache.get(key);
if(DiskLruCache != null){
    FileInputStream fileInPutStream = (FileInputStream)snapShot.getInputStream(DISK_CACHE_INDEX);
    FileDescriptor fileDescriptor = fileInPutStream.getFD();
    bitmap = mImageResizer.decodeSampledBitmapFromFileDescriptior(fileDescriptor,reqWidth,reqHeight);
    if(bitmap != null){
        addBitmapToMemoryCache(key,bitmap);
    }
}

在上述代码中,我们需要注意的是在得到图片(文件)的输入流之后,一般不会直接在家原始的图片,而是通过BitmapFactory.decodeFileDescriptor的方法来对图片进行缩放加载。

你可能感兴趣的:(Android进阶(12)| 理解Bitmap的加载与Cache)