性能优化-Bitmap内存管理

  本文使用LruCache、DiskLruCache进行Bitmap的内存管理。LruCache是Android自带的,DiskLruCache是Jake Wharton大神的作品:https://github.com/JakeWharton/DiskLruCache

  做Bitmap内存管理,首先要用到BitmapFactory.Options,它是BitmapFactory类的静态内部类,主要使用Options相关参数对Bitmap进行设置。如Options.inBitmap用于设置加载图片时进行内存复用,如下图1、2所示:
性能优化-Bitmap内存管理_第1张图片
性能优化-Bitmap内存管理_第2张图片
Bitmap内存管理的项目使用见以下代码:
activity_main.xml :


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

item.xml :


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

示例中的item只展示一类图片
性能优化-Bitmap内存管理_第3张图片

因为涉及到DiskLruCache的使用,因此需要去DiskLruCache代码库将源码拷入项目中。
性能优化-Bitmap内存管理_第4张图片
ImageResize.java :

package com.example.administrator.lsn7_demo.utils;

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

/**
 * Bitmap加载优化
 */
public class ImageResize {

    /**
     * 缩放Bitmap
     *
     * @param context  上下文
     * @param imgId    图片的资源id
     * @param maxW     允许的最大宽度
     * @param maxH     允许的最大高度
     * @param hasAlpha 图片是否有Alpha通道
     * @param reusable 复用的Bitmap
     * @return 返回缩放后的Bitmap
     */
    public static Bitmap resizeBitmap(Context context, int imgId, int maxW, int maxH, boolean hasAlpha, Bitmap reusable) {
        Resources resources = context.getResources();
        BitmapFactory.Options options = new BitmapFactory.Options();

        //不加载到内存,方便计算图片宽高,从而得到inSampleSize
        options.inJustDecodeBounds = true;
        //预加载
        BitmapFactory.decodeResource(resources, imgId, options);

        //拿到图片宽高
        int w = options.outWidth;
        int h = options.outHeight;
        //设置采样率(根据宽、高进行缩放,设置缩放系数)
        options.inSampleSize = calcuteInSampleSize(w, h, maxW, maxH);

        if (!hasAlpha) {
            /*
            图片质量压缩,即解码率压缩。
            RGB_565占2个字节,ARGB_8888占4个字节
            */
            options.inPreferredConfig = Bitmap.Config.RGB_565;
        }

        //加载进内存
        options.inJustDecodeBounds = false;

        //可变的bitmap
        options.inMutable = true;
        //设置成能复用,可实现第二张图片复用第一张图片的内存
        options.inBitmap = reusable;

        return BitmapFactory.decodeResource(resources, imgId, 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;
    }

}

ImageCache.java :

package com.example.administrator.lsn7_demo.utils;

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.BuildConfig;
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;

/**
 * Bitmap内存管理(内存、复用池、磁盘、网络)
 */
public class ImageCache {

    private static volatile ImageCache instance;

    private Context context;

    private BitmapFactory.Options options = new BitmapFactory.Options();
    /**
     * 内存
     */
    private LruCache<String, Bitmap> memoryLruCache;
    /**
     * 磁盘
     */
    private DiskLruCache diskLruCache;
    /**
     * 复用沲
     */
    private static Set<WeakReference<Bitmap>> reuseablePool;

    /**
     * 引用队列
     */
    private ReferenceQueue<Bitmap> referenceQueue;
    private Thread clearRefQueueThread;
    private boolean shutDown;


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


    /**
     * 初始化方法,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
        memoryLruCache = 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) {
                //如果是设置成能复用的内存块,拉到java层来管理
                if (oldValue.isMutable()) {
                    //3.0以下   Bitmap   native
                    //3.0以后---8.0之前  java
                    //8。0开始      native
                    //把这些图片放到一个复用沲中
                    reuseablePool.add(new WeakReference<Bitmap>(oldValue, referenceQueue));
                } else {
                    //oldValue就是移出来的对象
                    oldValue.recycle();
                }
            }
        };

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

        getReferenceQueue();
    }


    /**
     * 获得引用队列
     */
    private ReferenceQueue<Bitmap> getReferenceQueue() {
        if (null == referenceQueue) {
            //当弱引用需要被回收的时候,会进到这个队列中
            referenceQueue = new ReferenceQueue<Bitmap>();

            //单开一个线程,去扫描引用队列中GC扫到的内容,交到native层去释放
            clearRefQueueThread = new Thread(new Runnable() {
                @Override
                public void run() {
                    while (!shutDown) {
                        try {
                            /*
                            remove是阻塞式的,
                            Removes the next reference object in this queue, blocking until one becomes available
                             */
                            Reference<Bitmap> reference = (Reference<Bitmap>) referenceQueue.remove();
                            Bitmap bitmap = reference.get();

                            if (null != bitmap && !bitmap.isRecycled()) {
                                bitmap.recycle();
                            }
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            });

            clearRefQueueThread.start();
        }

        return referenceQueue;
    }


    public void putBitmapToMemeory(String key, Bitmap bitmap) {
        memoryLruCache.put(key, bitmap);
    }

    public Bitmap getBitmapFromMemory(String key) {
        return memoryLruCache.get(key);
    }

    public void clearmemoryLruCache() {
        memoryLruCache.evictAll();
    }

    /**
     * 获取复用池中的内容
     * @param w 图片宽
     * @param h 图片高
     * @param inSampleSize 采样率
     * @return 复用图片
     */
    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;
    }

    /**
     * @param bitmap       图片
     * @param w            图片宽
     * @param h            图片高
     * @param inSampleSize 采样率
     * @return 图片是否可复用
     */
    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();
    }

    /**
     * 图片质量压缩,即解码率压缩。
     * RGB_565占2个字节,ARGB_8888占4个字节
     *
     * @param config 解码率
     * @return 占用字节大小
     */
    private int getPixelsCount(Bitmap.Config config) {
        if (config == Bitmap.Config.ARGB_8888) {
            return 4;
        }

        return 2;
    }


    /**
     * 加入磁盘缓存
     *
     * @param key    要存入磁盘中的索引
     * @param bitmap 要存入的图片
     */
    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);
                    //图片压缩格式为jpeg,50表示中度压缩(0-100之间)
                    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();
                }
            }
        }
    }

    /**
     * 从磁盘缓存中取
     *
     * @param key       磁盘存储索引
     * @param reuseable 复用图片
     * @return 磁盘中取出的图片
     */
    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) {
                //加入内存缓存
                memoryLruCache.put(key, bitmap);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (null != snapshot) {
                snapshot.close();
            }
        }

        return bitmap;
    }

}

MainActivity.java :

package com.example.administrator.lsn7_demo;

import android.graphics.Bitmap;
import android.os.Bundle;
import android.os.Environment;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.widget.ListView;

import com.example.administrator.lsn7_demo.utils.ImageCache;

/**
 * Bitmap内存管理(内存、复用池、磁盘、网络)
 */
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

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

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

        //假设是从网络上来的
        /*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);
        }*/
    }

    void i(Bitmap bitmap) {
        Log.i("jett", "图片" + bitmap.getWidth() + "x" + bitmap.getHeight() + " 内存大小是:" + bitmap.getByteCount());
    }

}

MyAdapter.java :

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;

import com.example.administrator.lsn7_demo.utils.ImageCache;
import com.example.administrator.lsn7_demo.utils.ImageResize;


public class MyAdapter extends BaseAdapter {
    private Context context;

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

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

    @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);


        //图片优化方式二
        Bitmap bitmap = ImageCache.getInstance().getBitmapFromMemory(String.valueOf(position));
        if (null == bitmap) {
            //如果内存没数据,就去复用池找。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);

                //然后将下载的Bitmap分别放入内存、磁盘
                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;
    }

    static class ViewHolder {
        ImageView iv;

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

}

最后,别忘了添加权限

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

运行效果:
性能优化-Bitmap内存管理_第5张图片

项目地址:
https://github.com/keyyoo/Android_Advanced_Series/tree/master/%E4%B8%93%E9%A2%983-%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96/Lsn7_Bitmap%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86

你可能感兴趣的:(性能优化)