面试必会技术-Bitmap内存管理

2019-05-13  本文已影响0人  大苏打6815
Bitmap异变功能

我们假设从网络上加载N张图片,获取一百个bitmap,那么就要开辟一百个内存块。
bitmap如果具有异变功能的话,他所在的这块内存块就可以复用。
// BitmapFactory.Options options=new BitmapFactory.Options();
// //如果要复用,需要设计成异变
// options.inMutable=true;
以后在使用的过程中,就可以使用相同的一块内存了,只要下一张图片小于等于就能装下
//Bitmap bitmap=BitmapFactory.decodeResource(getResources(),R.mipmap.wyz_p,options);
// for(int i=0;i<100;i++){
// options.inBitmap=bitmap;
// bitmap=BitmapFactory.decodeResource(getResources(),R.mipmap.wyz_p,options);
// }

但是在一个APP里,光靠这么几行代码,肯定是不行的(目前先用Glide,讲的是个思想)上面的代码就先注释掉。下一步就写一个更简易的框架,让我们更理解内存管理

加载图片的步骤

如果我们去网上申请图片,第一个步骤就是去内存缓存里面去找,第二个步骤就是去磁盘里面去找,如果磁盘还找不到,那么就去网络中找。内存缓存和磁盘缓存都可以用LRU去缓存,这种策略叫最近最少使用策略。这种算法的具体写法在以后的架构学习当中会去写,现在先理解它的意思。

内存缓存

最近最远策略,LRU缓存就是一个一个的节点,用一个双向链表把它缓存起来,假设这个LRU缓存最大能放四张图 比如 1、2、3、4
那么如果第5张图片进来的时候,就会把最后一张删掉,最近使用的一张就对移到整个队列的第一个 就成了 5、1、2、3,假设我们用的时候,是在缓存里面直接用,假设用的是2号,那么新的排序为2、5、1、3。

内存缓存,我们安卓是自带这样的API的

内存缓存在缓存的过程当中,通过LRU会把一些缓存给清除出来,比如我内存最多加载10张图片,现在有15张图片,那么多余的5张就会被清理出来,那么我们是不是可以新建一个复用池,把这些清理出来的这额外5张又给缓存起来呢?这样我们下次再次用这五张图片的内用的时候,在内存中找不到的时候,就去复用池找,如果有可以从复用池取出来。

注意复用池里面的这些东西的管理是在3.0以下是在native层,3.0之后-8.0之前是在java堆处理,8.0之后又用native层了,可能现在市面上APP现在占用内存都挺大,移到native层可以提高响应速度。

如果复用池里面的一些东西长时间没有使用,有被清除动作的时候,比如被GC扫描一次,我们需要一个弱引用把它保存下来,然后我要通知我们的程序,现在已经有内存是被释放了,我就要通知native层把这块控件去释放。在这个引用队列(ReferenceQuenu)里面直接bitmap.recycle去释放,目的是为了提高一定的响应速度,GC扫两次才会被彻底回收的哦,我这里扫一次就能得到监听(后续代码),然后手动recycle.
安卓API19以前,如果要复用,新的bitmap的宽高一定要和之前的相同,并且没有缩放,19之后,只要小于等于之前的图片的内存大小,就可以复用,不然就只能重新开辟内存空间。获取复用池中的内容,一定要判断是不是要比他原来的要小,小才能用。

磁盘缓存(网络下载三个类)

如果要做磁盘缓存的话,是要下载一个第三方库,谷歌推荐的一种方式。https://github.com/JakeWharton/DiskLruCache 记得下载release版本,解压之后把里面三个文件源码拿出来。那么我们就做一个缓存机制,不用太多的封装,同时也能使用inbitmap来复用。

加载到图片的时候,我们会在内存和磁盘里面各存储一份,我们加载图片的时候,先去内存里面找,内存里面没有,再去复用池里面找(注意复用池里面找的并不是图片,而是内存),拿到这个内存再去磁盘里面找,如果磁盘还是没有,就从网络下载,这就是我们常常说的三级缓存,当然也可以说成四级缓存。

核心代码
/**
 * 管理内存中的图片
 */
public class ImageCache {

    private static ImageCache instance;
    private Context context;
    private LruCache<String,Bitmap> memoryCache;
    private DiskLruCache diskLruCache;//第三方库

    BitmapFactory.Options options=new BitmapFactory.Options();//内存复用的

    /**
     * 定义一个复用沲
     */
    public static Set<WeakReference<Bitmap>> reuseablePool;


    public static ImageCache getInstance(){
        if(null==instance){
            synchronized (ImageCache.class){
                if(null==instance){
                    instance=new ImageCache();
                }
            }
        }
        return instance;
    }

    //引用队列
    ReferenceQueue referenceQueue;
    Thread clearReferenceQueue;
    boolean shutDown;

    private ReferenceQueue<Bitmap> getReferenceQueue(){
        if(null==referenceQueue){
            //当弱用引需要被回收的时候,会进到这个队列中
            referenceQueue=new ReferenceQueue<Bitmap>();
            //单开一个线程,去扫描引用队列中GC扫到的内容,交到native层去释放
            clearReferenceQueue=new Thread(new Runnable() {
                @Override
                public void run() {
                    while(!shutDown){
                        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();
        }
        return referenceQueue;
    }

    //dir是用来存放图片文件的路径
    public void init(Context context,String dir){
        this.context=context.getApplicationContext();

        //复用池
        reuseablePool=Collections.synchronizedSet(new HashSet<WeakReference<Bitmap>>());

        ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
        //获取程序最大可用内存 单位是M
        int memoryClass=am.getMemoryClass();
        //参数表示能够缓存的内存最大值  单位是byte,一般是程序的八分之一M,但是下面是以字节为单位的
        memoryCache=new LruCache<String,Bitmap>(memoryClass/8*1024*1024){
            /**
             * @return value占用的内存大小
             */
            @Override
            protected int sizeOf(String key, Bitmap value) {
                //19之前   必需同等大小,才能复用  inSampleSize=1
                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()){//如果是设置成能复用的内存块,拉到java层来管理
                    //3.0以下   Bitmap   native
                    //3.0以后---8.0之前  java
                    //8。0开始      native
                    //把这些图片放到一个复用沲中
                    reuseablePool.add(new WeakReference<Bitmap>(oldValue,referenceQueue));
                }else{
                    //oldValue就是移出来的对象
                    oldValue.recycle();
                }


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

       getReferenceQueue();
    }
    public void putBitmapToMemeory(String key,Bitmap bitmap){
        memoryCache.put(key,bitmap);
    }
    public Bitmap getBitmapFromMemory(String key){
        return memoryCache.get(key);
    }
    public void clearMemoryCache(){
        memoryCache.evictAll();
    }

    //获取复用池中的内容
    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;
    }


    //磁盘缓存的处理
    /**
     * 加入磁盘缓存
     */
    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){
                memoryCache.put(key,bitmap);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally{
            if(null!=snapshot){
                snapshot.close();
            }
        }
        return bitmap;
    }
}
我们要在主Activity中调用初始化方法
ImageCache.getInstance().init(this,Environment.getExternalStorageDirectory()+"/dn");
在Adaper里面加载图片的地方这样处理(示例为listview)
  @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder holder;
        if (null == convertView) {
            convertView = LayoutInflater.from(context).inflate(R.layout.item, parent, false);
            holder = new ViewHolder(convertView);
            convertView.setTag(holder);
        } else {
            holder = (ViewHolder) convertView.getTag();
        }

        //第一次优化
//        Bitmap bitmap = ImageResize.resizeBitmap(context, R.mipmap.wyz_p,
//                80, 80, false);

        Bitmap bitmap=ImageCache.getInstance().getBitmapFromMemory(String.valueOf(position));
        if(null==bitmap){
            //如果内存没数据,就去复用池找
            Bitmap reuseable=ImageCache.getInstance().getReuseable(60,60,1);
            //reuseable能复用的内存
            //从磁盘找
            bitmap = ImageCache.getInstance().getBitmapFromDisk(String.valueOf(position),reuseable);
            //如果磁盘中也没缓存,就从网络下载
            if(null==bitmap){
                bitmap=ImageResize.resizeBitmap(context,R.mipmap.wyz_p,80,80,false,reuseable);
                ImageCache.getInstance().putBitmapToMemeory(String.valueOf(position),bitmap);
                ImageCache.getInstance().putBitMapToDisk(String.valueOf(position),bitmap);
                Log.i("jett","从网络加载了数据");
            }else{
                Log.i("jett","从磁盘中加载了数据");
            }

        }else{
            Log.i("jett","从内存中加载了数据");
        }



        holder.iv.setImageBitmap(bitmap);
        return convertView;
    }













上一篇下一篇

猜你喜欢

热点阅读