美文网首页Android开发Android技术知识Android开发
Android进阶(12)| 理解Bitmap的加载与Cache

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

作者: yzbkaka | 来源:发表于2019-06-03 20:26 被阅读7次
    本节目录

    一.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<K,V>{  //构造方法
        private final LinkedHashMap<K,V> map;
    }
    

    LruCache的使用:
    (1)初始化

    int maxMemory = (int)(Runtime.getRuntime().maxMemory()/1024);
    int cacheSize = maxMemory/8;
    mMemoryCache = new LruCache<String,Bitmap>(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<bytes.length;i++){
            String hex = Integer.toHexString(0xFF & bytes[i]);
            if(hex.length() == 1){
                sb.append('0');
            }
            sb.append(hex);
        }
        return sb.toString();
    }
    

    在将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

        本文链接:https://www.haomeiwen.com/subject/jtpcxctx.html