Bitmap内存压缩与管理

Android中有很多优秀的图片加载框架,比如Glide,Fresco,Picasoo,ImageLoader等。这些框架在性能上和使用上虽然有着差异,也有着各自框架的优缺点,但是在基本原理上都大同小异(Fresco没有研究过源码,具体实现不太清楚)。那就是对Bitmap的处理基本上都使用三级缓存或者四级缓存。可能很多人刚开始学Android的时候就知道或者手写过三级缓存,metoo。但是最近在复习的时候却发现了一些新的东西,所以做个笔记,做一个分享。

一、内存缓存

第一级缓存是内存缓存,在加载图片的时候,我们先从内存中查找是否存在要展示的图片。代码如下

//定义一个复用池
public static Set<WeakReference<Bitmap>> reuseablePool;
//定义LruCache对象,Android中内置的api,双向链表结构,最近最少使用算法
private LruCache<String,Bitmap> memoryCache;
//引用队列
ReferenceQueue referenceQueue;
Thread clearReferenceQueue;


//在init方法中对reuseablePool和memoryCache进行初始化
public void init(Context context){
    //复用池初始化
    reuseablePool=Collections.synchronizedSet(new HashSet<WeakReference<Bitmap>>());
    ActivityManager am =(ActivityManager)context.getSystemService(Context.ACTIVITY_SERVICE);
    //获取程序最大可用内存 单位是M
    int memoryClass=am.getMemoryClass();

    //参数表示能够缓存的内存最大值  单位是byte
    memoryCache = new LruCache<String,Bitmap>(){
        @Override
        protected int sizeOf(String key, Bitmap value) {
             //19之前   必需同等大小,才能复用
             if(Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT){
                 return value.getAllocationByteCount();
             }
             return value.getByteCount();
        }
        //当lru满了,bitmap从lru中移除对象时,会回调
        @Override
        protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) {
            if(oldValue.isMutable()){
                reuseablePool.add(new WeakReference<Bitmap>(oldValue,referenceQueue));
            }else{
                oldValue.recycle();
            }
        }
    }
    getReferenceQueue();

}

private void getReferenceQueue(){
        if(null==referenceQueue){
            //当弱用引需要被回收的时候,会进到这个队列中
            referenceQueue=new ReferenceQueue<Bitmap>();
            //单开一个线程,去扫描引用队列中GC扫到的内容,交到native层去释放
            clearReferenceQueue=new Thread(new Runnable() {
                @Override
                public void run() {
                    while(true){
                        try {
                            //remove是阻塞式的
                            Reference<Bitmap> reference=referenceQueue.remove();
                            Bitmap bitmap=reference.get();
                            if(null!=bitmap && !bitmap.isRecycled()){
                                bitmap.recycle();
                            }
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            });
            clearReferenceQueue.start();
        }
    }

关于上面的reusablePool我简单说明一下:

         reuseablePool复用池是我新发现的一个步骤。从上面的代码可以看出,当memoryCache满了,会调用entryRemoved方法根据最近最少使用算法进行移除数据,而移除的数据会放入到这个复用池中。从reuseablePool的初始化,可以看出,所添加的对象都是虚引用。被jvm回收的几率会很大,理论上被jvm第一次扫描的时候会添加到referenceQueue中,第二次扫描的时候才会被回收,而我这里通过轮询的方式进行了Bitmap手动的释放,比jvm自动回收的方式更快。需要注意的是,这个复用池虽然存储的是Bitmap对象,但是其实复用的是Bitmap所占用的内存。Bitmap所占用的内存是否能复用,是通过isMutable这个属性来控制的,true为可以复用,false为不能。Bitmap内存的复用减少了开辟内存和内存回收的效率,加快了运行效率。Bitmap内存的位置,在每个版本都不太一样。3.0之前是放在native层处理;3.0-8.0在java的堆内存中进行管理;8.0之后放在native层管理。而Bitmap内存复用的规则也不太一样。api19之前,新Bitmap的宽高需要和能被复用的Bitmap的宽高相等且inSampleSize等于1;而api19之后,新Bitmap的宽高小于被复用Bitmap即可。

Bitmap内存复用推荐博客

Bitmap内存复用的代码如下:
 

//获取复用池中的内容
    public Bitmap getReuseable(int w,int h,int inSampleSize){
        if(Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB){
            return null;
        }
        Bitmap reuseable=null;
        Iterator<WeakReference<Bitmap>> iterator = reuseablePool.iterator();
        while(iterator.hasNext()){
            Bitmap bitmap=iterator.next().get();
            if(null!=bitmap){
                //可以复用
                if(checkInBitmap(bitmap,w,h,inSampleSize)){
                    reuseable=bitmap;
                    iterator.remove();
                    break;
                }else{
                    iterator.remove();
                }
            }
        }
        return reuseable;

    }

    private boolean checkInBitmap(Bitmap bitmap, int w, int h, int inSampleSize) {
        if(Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT){
            return bitmap.getWidth()==w && bitmap.getHeight()==h && inSampleSize==1;
        }
        if(inSampleSize>=1){
            w/=inSampleSize;
            h/=inSampleSize;
        }
        int byteCount=w*h*getPixelsCount(bitmap.getConfig());
        return byteCount<=bitmap.getAllocationByteCount();
    }

    private int getPixelsCount(Bitmap.Config config) {
        if(config==Bitmap.Config.ARGB_8888){
            return 4;
        }
        return 2;
    }

二、磁盘缓存

磁盘缓存就是当内存中不存在要展示的图片时,从磁盘中查找。我这里使用的磁盘缓存,直接使用了JakeWharton大神写好的DiskLruCache,直接贴代码:

//磁盘缓存的处理
    /**
     * 加入磁盘缓存
     */
    public void putBitMapToDisk(String key,Bitmap bitmap){
        DiskLruCache.Snapshot snapshot=null;
        OutputStream os=null;
        try {
            snapshot=diskLruCache.get(key);
            //如果缓存中已经有这个文件  不理他
            if(null==snapshot){
                //如果没有这个文件,就生成这个文件
                DiskLruCache.Editor editor=diskLruCache.edit(key);
                if(null!=editor){
                    os=editor.newOutputStream(0);
                    bitmap.compress(Bitmap.CompressFormat.JPEG,50,os);
                    editor.commit();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(null!=snapshot){
                snapshot.close();
            }
            if(null!=os){
                try {
                    os.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    /**
     * 从磁盘缓存中取
     */
    public Bitmap getBitmapFromDisk(String key,Bitmap reuseable){
        DiskLruCache.Snapshot snapshot=null;
        Bitmap bitmap=null;
        try {
            snapshot=diskLruCache.get(key);
            if(null==snapshot){
                return null;
            }
            //获取文件输入流,读取bitmap
            InputStream is=snapshot.getInputStream(0);
            //解码个图片,写入
            options.inMutable=true;
            options.inBitmap=reuseable;
            bitmap=BitmapFactory.decodeStream(is,null,options);
            if(null!=bitmap){
                putBitmapToMemeory(key,bitmap);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally{
            if(null!=snapshot){
                snapshot.close();
            }
        }
        return bitmap;
    }

DisLruCache的初始化

 //valueCount:表示一个key对应valueCount个文件
       try {
           diskLruCache = DiskLruCache.open(new File(dir), BuildConfig.VERSION_CODE, 1, 10 * 1024 * 1024);
       }catch(Exception e){
           e.printStackTrace();
       }

关于Bitmap内存管理的内容我就讲完了,重点还是在Bitmap内存的复用上面,很大程度的提高了手机的运行性能。

欢迎提问!欢迎纠错!

猜你喜欢

转载自blog.csdn.net/qq_32019367/article/details/90175900