Bitmap的加载和缓存策略

本章主要学习Bitmap的加载和Cache,由于Bitmap的特殊性以及android对单个应用所施加的内存限制,导致加载Bitmap很容易出现内存溢出,因此这里主要探讨下如何高效地加载Bitmap以及其中所使用的的缓存策略.

  • Bitmap的高效加载
  • Android中的缓存策略

一.Bitmap的高效加载

1.核心思想

按一定的采样率把图片缩小后再加载.

2.核心类和核心参数

(1)BitmapFactor提供了四类方法加载图片:

//从文件系统加载一个bitmap对象
decodeFile
//从资源加载一个bitmap对象
decodeResource
//从输入流加载一个bitmap对象
decodeStream
//从字节数组加载一个bitmap对象
decodeByteArray
注意:decodeFile和decodeResource又间接调用了decodeStream方法

(2)BitmapFactory.Options缩放图片

通过BitmapFactory.Options来缩放图片,主要用到了inSampleSize参数,即采样率.

a.inSampleSize的取值

应总是为2的指数
如不为2的指数,那么系统会向下取整并选择一个最接近的2的指数来代替,比如3,系统会选择2来代替

b.inSampleSize的变化

值k为1时,采样后的图片大小为图片的原始大小
值k小于1时,其作用相当于1,无缩放效果
值k大于1时,采样后的图片宽高为原图的1/k,像素为原图的1/k^2,占用内存为原图的1/k^2

(3)inJustDecodeBounds参数

值为true时,BitmapFactory只会解析图片的原始宽高信息,并不会真正地加载图片
值为false时,真正的加载图片到内存

3.获取采样率的流程

(1)将BitmapFactory.Options的inJustDecodeBounds参数设为true,并加载图片.

(2)从BitmapFactory.Options取出原始的宽高信息,他们对应于outWidth和outHeight参数.

(3)根据采样率的规则并结合目标View所需的大小计算出采样率.

(4)将BitmapFactory.Options的inJustDecodeBounds参数设为false,并重新加载图片.

4.代码实现

  /**
     * 对一个Resources的资源文件进行指定长宽来加载进内存, 并把这个bitmap对象返回
     *
     * @param res   资源文件对象
     * @param resId 要操作的图片id
     * @param reqWidth 最终想要得到bitmap的宽度
     * @param reqHeight 最终想要得到bitmap的高度
     * @return 返回采样之后的bitmap对象
     */
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId, int reqWidth, int reqHeight){
        BitmapFactory.Options options = new BitmapFactory.Options();
        //1.设置inJustDecodeBounds=true获取图片尺寸
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeResource(res,resId,options);
        //3.计算缩放比
        options.inSampleSize = calculateInSampleSize(options,reqHeight,reqWidth);
        //4.再设为false,重新从资源文件中加载图片
        options.inJustDecodeBounds =false;
        return BitmapFactory.decodeResource(res,resId,options);
    }

   /**
     *  一个计算工具类的方法, 传入图片的属性对象和想要实现的目标宽高. 通过计算得到采样值
     * @param options 要操作的原始图片属性
     * @param reqWidth 最终想要得到bitmap的宽度
     * @param reqHeight 最终想要得到bitmap的高度
     * @return 返回采样率
     */
private static int calculateInSampleSize(BitmapFactory.Options options, int reqHeight, int reqWidth) {
        //2.height、width为图片的原始宽高
        int height = options.outHeight;
        int width = options.outWidth;
        int inSampleSize = 1;
        if(height>reqHeight||width>reqWidth){
            int halfHeight = height/2;
            int halfWidth = width/2;
            //计算缩放比,是2的指数
            while((halfHeight/inSampleSize)>=reqHeight&&(halfWidth/inSampleSize)>=reqWidth){
                inSampleSize*=2;
            }
        }    
        return inSampleSize;
    }

    //加载一个像素为100*100的ImageView
    mImageView.setImageBitmap(decodeSampledBitmapFromResource(getResources(),R.mipmap.ic_launcher,100,100);

二.缓存策略

1.概述

使用缓存的目的是为了避免过多的流量消耗,缓存策略主要包含缓存的添加,获取和删除这三类操作,如何对这些缓存进行操作就是一种策略,不同的策略对应着不同的缓存算法,目前常用的一种缓存算法是LRU(Least Recently Used),LRU是近期最少使用算法,他的核心思想是当缓存满时,会优先淘汰那些近期最少使用的缓存对象.采用LRU算法的缓存有两种:LruCache和DiskLruCache.

(1)LruCache

LruCache用于实现内存缓存.它是一个泛型类.内部采用一个LinkedHashMap以强引用的方式存储外界的缓存对象,其提供了get和put方法来完成缓存的获取和添加操作,当缓存满时,LruCacche会移除较早使用的缓存对象,然后再添加新的缓存对象.这里使用了强引用,因此这里要明白一下强引用,软应用和若引用的区别:

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

具体的实现:

a.计算当前可用内存的大小

b.分配LruCache缓存容量

c.创建LruCache对象并传入LruCache的缓存容量,复写sizeOf方法,计算缓存对象的大小.

d.通过put,get和remove方法.实现缓存的添加,获取和删除.

        //获取当前进程可用内存大小
        int maxMemory = (int)(Runtime.getRuntime().maxMemory())/1024;
        //获取缓存的总容量
        int cacheSize = maxMemory/8;
        //创建LruCache对象
        mLruCache = new LruCache<String,Bitmap>(cacheSize){
            @Override
            protected int sizeOf(String key, Bitmap bitmap) {
                //计算缓存对象的大小
                return bitmap.getRowBytes()*bitmap.getHeight()/1024;
            }
        };
        
        //添加缓存对象
        mLruCache.put("test1",bitmap1);
        mLruCache.put("test2",bitmap2);
        //获取缓存对象
        mLruCache.get("test1");
        //删除缓存对象
        mLruCache.remove("test2");

(2)DisLruCache

DisLruCache用于实现存储设备缓存,即磁盘缓存.,它通过将缓存对象写入文件系统从而实现缓存的效果.

使用步骤:

a.设置DisLruCache的容量.

b.设置缓存目录.

c.通过open的方法,创建DisLruCache对象.

d.利用Editor,SnapShop和remove实现数据的添加,获取和删除.

e.调用flush将数据写入磁盘.

//使用DiskLruCach需要添加添加依赖
//implementation 'com.jakewharton:disklrucache:2.0.2'
public class DiskLruCacheUtil {
    
    //设置DiskLruCache的容量
    private static final long DISK_CACHE_SIZE = 1024*1024*50;
    private static final int DISK_CACHE_INDEX = 0;
    private Context mContext;
    private DiskLruCache mDisLruCache;
    private DiskLruCache.Editor mEdit;
    
    public void initDiskLruCache(Context context){
        mContext= context;
        try {
        //设置缓存目录
        File diskCacheDir = getDisCacheDir(mContext,"bitmap");
        if (!diskCacheDir.exists()) {
            diskCacheDir.mkdirs();
        }
            //参数1传入存储路径
            //参数2传入应用的版本号,一般设置为1,当版本号改变会清空之前的所有的缓存文件,在实际开发中,版本号改变,很多情况下缓存文件仍然有效
            //参数3传入单个节点对应的数据的个数,一般设置为1即可
            //参数4传入缓存的总大小
            mDisLruCache = DiskLruCache.open(diskCacheDir, 1, 1, DISK_CACHE_SIZE);
        } catch (IOException e) {
            e.printStackTrace();
        }
    
    }
    
    private File getDisCacheDir(Context context, String fileName) {
        String cachePath;
        if (Environment.MEDIA_MOUNTED.contains(Environment.getExternalStorageState()) || !Environment.isExternalStorageRemovable()) {
            //当内存卡存在或不可移除时
            cachePath = context.getExternalCacheDir().getPath();
        } else {
            cachePath = context.getCacheDir().getPath();
        }
        
        return new File(cachePath+File.separator+fileName);
    }
    
    //DisLruCache添加数据
    public void addDisLruCacheData(String url,String data){
        //这里以缓存图片为例,因为url可能有特殊字符,这里采用url的MD5值作为key
        String key = hashKeyFromUrl(url);
        try {
            //获取Editor对象
            mEdit = mDisLruCache.edit(key);
            //创建输出流
            OutputStream outputStream = mEdit.newOutputStream(DISK_CACHE_INDEX);
            //写入数据
            outputStream.write(data.getBytes());
            //提交写操作
            mEdit.commit();
            //写入磁盘
            mDisLruCache.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
       
    }
    
    //DisLruCache查找数据
    public String getDisLruCacheData(String url){
        //通过url获取key
        StringBuffer sb = new StringBuffer();
        String key = hashKeyFromUrl(url);
        try {
            //获取Snapshot对象
            DiskLruCache.Snapshot snapshot = mDisLruCache.get(key);
            //创建输入流
            InputStream inputStream = snapshot.getInputStream(DISK_CACHE_INDEX);
            //读取数据
            int len;
            byte[] bytes = new byte[1024];
            if ((len = inputStream.read(bytes)) != -1) {
                sb.append(bytes);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return sb.toString();
    }
    
    //DisLruCache删除数据
    public void deleteDisLruCacheData(String url) {
        String key = hashKeyFromUrl(url);
        //删除缓存
        try {
            mDisLruCache.remove(key);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
    private String hashKeyFromUrl(String url) {
        String cacheKey;
        try {
            MessageDigest mDigest = MessageDigest.getInstance("MD5");
            mDigest.update(url.getBytes());
            cacheKey = bytesToHexString(mDigest.digest());
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
            cacheKey = String.valueOf(url.hashCode());
        }
        return cacheKey;
    }
    
    private String bytesToHexString(byte[] bytes) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < bytes.length; i++) {
            String hex = Integer.toHexString(0xFF & bytes[i]);
            if (hex.length() == 1) {
                sb.append('0');
            }
            sb.append(hex);
        }
        return sb.toString();
    }
    
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值