Wang Gang performance optimization 7 - Bitmap Memory Management

We created in the layout a ListView, listView a picture of the item only
the first optimization
we create a ListView, when we slide up and down when the item is found native memory has been increasing, as long as the emerging picture will open up new memory address. So we need to re-use these memory blocks. After Android8.0 The memory is on native memory rather than Java heap memory

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
   >
    <ListView
        android:id="@+id/listView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
         />
</FrameLayout>

This is the item layout

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
   >
   <ImageView
       android:id="@+id/iv"
       android:layout_width="80dp"
       android:layout_height="80dp" />
</FrameLayout>

This is MainActivity code

  ListView listView = findViewById(R.id.listView);
        listView.setAdapter(new MyAdapter(this));

This is a lesson of ImageResize, we have to make it function mutation.

package com.example.administrator.lsn7_demo;

import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;


public class ImageResize {

    /**
     *  缩放bitmap
     * @param context
     * @param id
     * @param maxW
     * @param maxH
     * @param reusable增加异变功能
     * @return
     */
    public static Bitmap resizeBitmap(Context context,int id,int maxW,int maxH,boolean hasAlpha,Bitmap reusable){
        Resources resources = context.getResources();
        BitmapFactory.Options options = new BitmapFactory.Options();
        // 只解码出 outxxx参数 比如 宽、高
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeResource(resources,id,options);
        //根据宽、高进行缩放
        int w = options.outWidth;
        int h = options.outHeight;
        //设置缩放系数
        options.inSampleSize = calcuteInSampleSize(w,h,maxW,maxH);
        if (!hasAlpha){
            options.inPreferredConfig = Bitmap.Config.RGB_565;
        }
        options.inJustDecodeBounds = false;
        //设置成能复用
        options.inMutable=true;
        options.inBitmap=reusable;
        return BitmapFactory.decodeResource(resources,id,options);
    }

    /**
     * 计算缩放系数
     * @param w
     * @param h
     * @param maxW
     * @param maxH
     * @return 缩放的系数
     */
    private static int calcuteInSampleSize(int w,int h,int maxW,int maxH) {
        int inSampleSize = 1;
        if (w > maxW && h > maxH){
            inSampleSize = 2;
            //循环 使宽、高小于 最大的宽、高
            while (w /inSampleSize > maxW && h / inSampleSize > maxH){
                inSampleSize *= 2;
            }
        }
        return inSampleSize;
    }
}

We set the picture Adapter

    @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);
        holder.iv.setImageBitmap(bitmap);
        return convertView;
    }

Detailed optimization first time
we are now sliding down ListView, will find its Native layer memory has been growing, but the Java memory has not increased, before Android 8.0, bitmap management is on the Java heap. Now if that memory is directly open in the native layer. Now we write memory management framework necessary to deal with the problem of native memory. Such as memory release must own to deal with. We are now sliding up ListView, or in the memory has been increased, and not released. Because whether you are on a slip or fall, as long as the emerging picture, it will open up a new memory address. You might say the ListView convertView will do optimization, yes, but we use the picture is not all in the ListView. Such as other places we need to update the Bitmap, if we do not do any treatment, these updates will continue to open up the memory block in memory.
How to solve it? We want to use inBitmap property, if we now need to load after three bitmap, the first bitmap we loaded delete the first Bitmap, load the second picture, this time if the second picture better than the first picture big, we on the first picture memory blocks can be reused. Third picture could be so loaded. This is the memory multiplexing
Here we look at how to achieve code reuse memory, which is the same principle Glide. We can put that code added to ImageResize in.

 Bitmap bitmap=BitmapFactory.decodeResource(getResources());
        for(int i=0;i<100;i++){
         bitmap=BitmapFactory.decodeResource(getResources());
        }

We load the code above one hundred pictures, we do not use memory pool that we no longer use memory blocks, we can find native display takes up 20MB of memory in
the following code we use multiplexing techniques Pool

  BitmapFactory.Options options=new BitmapFactory.Options();
        //如果要复用,需要设计成异变
        options.inMutable=true;
        //这样设置图片就具备了异变功能,可以使用inBitmap功能了
        Bitmap bitmap=BitmapFactory.decodeResource(getResources(),R.mipmap.wyz_p,options);
        for(int i=0;i<100;i++){
        //以后使用的时候它会使用相同的一块内存
       // bitmap所指向的内存块是可以复用的。我在加载新图片到这个内存的时候,
       //有了这个选项它就可以用了
            options.inBitmap=bitmap;
                  bitmap=BitmapFactory.decodeResource(getResources(),R.mipmap.wyz_p,options);
        }

The results show now running load 100 pictures need to be less than 20MB of memory

Second optimization
application images for the first time to the memory cache, disk cache to the second, the third time to obtain network. Memory cache and disk cache can use LRU algorithm, the LRU algorithm, all the pictures are a doubly linked list. Which we use a picture, a picture which would put at the head of the list when there are new pictures into when will the new picture into the top of the list, while the list to delete the last picture.
Cache memory we have such an API can be used to make a disk cache will need to download their own library which is Download note Download release.
We unzip the package, find three files in src. Into our project, as shown below
Here Insert Picture Description
Now we can use a disk cache, we own a caching mechanism;
our cache includes memory cache and disk cache, draw me a diagram to explain the memory cache
Here Insert Picture Description
to create a new ImageCache, to manage cache

package com.example.administrator.lsn7_demo;

import android.app.ActivityManager;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Build;
import android.util.LruCache;

import com.example.administrator.lsn7_demo.disk.DiskLruCache;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

/**
 * 管理内存中的图片
 */
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层去释放,我自己调用
            //recycle()方法释放,这比GC要快
            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();

        //复用池,带锁的set集合
        reuseablePool=Collections.synchronizedSet(new HashSet<WeakReference<Bitmap>>());

        ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
        //获取程序最大可用内存 单位是M
        int memoryClass=am.getMemoryClass();
        //这是Android提供好的API,参数表示能够缓存的内存最大值  单位是byte,一般情况下是能使用的内存的1/8
        memoryCache=new LruCache<String,Bitmap>(memoryClass/8*1024*1024){
            /**
             * @return value占用的内存大小
             */
            @Override
            protected int sizeOf(String key, Bitmap value) {
                //1,19之前   复用是必需同等大小,才能复用  inSampleSize=1
                //2,19之后不同,比如第一次使用图片显示的时候,他是a大小,下一次复用内存的时候,只
                //复用了a-10;value.getByteCount()只能得到a-10的内存而不是a.
                //android 19后有一个API可以得到整块内存空间。我们应该返回的内存a,而不是a-10
                if(Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT){
                    //bitmaap在复用的时候19以后与以前不同
                    return value.getAllocationByteCount();
                }
                //取到的是图片大小而不是内存大小
                return value.getByteCount();
            }
            /**
             * 1,当lru满了,bitmap从lru中移除对象时(就是上文提到的链表),会回调
             * 2,假设内存大小是10,并且已经放满,我们现在放入第十一张图片,原来的十张图片就会删除链表的最后一位
             * 删除掉的这张图片就可以使用oldValue进行接收.
             * 3,这些被删除的图片是在native层进行回收的。我们这里可以设计一个复用池,在内存中找不到的可以再
             * native内存中寻找。相当于做了两级缓存。我们可以使用弱引用缓存native层里的东西
             */
            @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{
                    //释放bitmap,图片就找不到啦
                    oldValue.recycle();
                }


            }
        };
          //做磁盘缓存
        //参数:1,缓存的目录,2,app的版本,3,valueCount:表示一个key对应valueCount个文件,4,磁盘缓存的大小
       try {
           diskLruCache = DiskLruCache.open(new File(dir), BuildConfig.VERSION_CODE, 1, 10 * 1024 * 1024);
       }catch(Exception e){
           e.printStackTrace();
       }

       getReferenceQueue();
    }

    /**
     * 往内存中存放图片
     * @param key
     * @param bitmap
     */
    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();
    }

      /**
     * 获取复用池中的内容
     * @param w 图片的宽高,从复用池取东西,宽高只能比原图小
     * @param h
     * @param inSampleSize 必须大于等于1
     * @return
     */
    public Bitmap getReuseable(int w,int h,int inSampleSize){
        //版本在3.0以前
        if(Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB){
            return null;
        }
        Bitmap reuseable=null;
        //从复用池里面迭代
        Iterator<WeakReference<Bitmap>> iterator = reuseablePool.iterator();
        while(iterator.hasNext()){
            //这里是弱引用,所以用get方法
            Bitmap bitmap=iterator.next().get();
            //我们每从复用池拿一个就把它从复用池删除掉,删掉的那个加入到LRU内存中
            if(null!=bitmap){
                //不为空表示可以复用
                //检测是否能用
                if(checkInBitmap(bitmap,w,h,inSampleSize)){
                    reuseable=bitmap;
                    iterator.remove();
                    break;
                }else{
                    iterator.remove();
                }
            }
        }
        return reuseable;

    }

    /**
     * 检测版本是否能用
     * @param bitmap
     * @param w
     * @param h
     * @param inSampleSize
     * @return
     */
    private boolean checkInBitmap(Bitmap bitmap, int w, int h, int inSampleSize) {
        //版本在19以前
        if(Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT){
            return bitmap.getWidth()==w && bitmap.getHeight()==h && inSampleSize==1;
        }
        //19版本以后,进行缩放
        if(inSampleSize>=1){
            w/=inSampleSize;
            h/=inSampleSize;
        }
        //宽X高X像素的字节个数
        int byteCount=w*h*getPixelsCount(bitmap.getConfig());
        //小于bitmap的存储空间就返回为真
        //只有我现在申请的内存块比我图片像素占得更多,才可以复用,才能返回为真
        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){
        //snapshot进行编辑提交
        DiskLruCache.Snapshot snapshot=null;
        OutputStream os=null;
        try {

            snapshot=diskLruCache.get(key);
            //如果缓存中已经有这个文件  不理他
            if(null==snapshot){
                //为空表示这个key不存在,也就是没有这个文件,那就生成这个文件
                DiskLruCache.Editor editor=diskLruCache.edit(key);
                if(null!=editor){
                    //0表示文件从头开始
                    os=editor.newOutputStream(0);
                    //压缩bitmap
                    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;
    }

}

Adapter used

package com.example.administrator.lsn7_demo;

import android.content.Context;
import android.graphics.Bitmap;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;


public class MyAdapter extends BaseAdapter {
    private Context context;

    public MyAdapter(Context context) {
        this.context = context;
    }

    @Override
    public int getCount() {
        return 999;
    }

    @Override
    public Object getItem(int position) {
        return null;
    }

    @Override
    public long getItemId(int position) {
        return 0;
    }

    @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);
        //先从内存中读取,我们就用index作键
        Bitmap bitmap=ImageCache.getInstance().getBitmapFromMemory(String.valueOf(position));
        if(null==bitmap){
            //如果内存没数据,就去复用池找
            //大小一定小于等于原来的,我们原来是80X80,insampleSize大于等于一
            //reuseable能复用的内存快,不是图片
            //复用池里不是图片,是能复用的内存块
            Bitmap reuseable=ImageCache.getInstance().getReuseable(60,60,1);

            //从磁盘找
            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;
    }

    class ViewHolder {
        ImageView iv;

        ViewHolder(View view) {
            iv = view.findViewById(R.id.iv);
        }
    }
}

In the initialization MainActivity

  ImageCache.getInstance().init(this,Environment.getExternalStorageDirectory()+"/dn");

Our first run down until the bottom of the slide, log printout obtained from the network,
we'll slip up, log printout fetched from memory, native memory is also not growing. ,
We close the app and then open, slide down, log printout obtained from the disk
with source code source chapter

Guess you like

Origin blog.csdn.net/qczg_wxg/article/details/90110930