安卓性能优化 四: bitmap 优化

230 阅读5分钟

内存大小的计算

ARGB_8888: A:表示透明度 R:表示红色 G:表示绿色 B:表示蓝色 每个占8bit 共占4byte

RGB_565占2byte

内存大小占用:

RGB_565 = width * height * 2 ARGB_8888 = width * height * 4

同一张图片 放到不同的dpi文件夹下加载到内存里面的占用大小是不一样的

对于一个adapter加载图片对比:

原始加载:

Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.icon_mv_w);

对于内存查看 是17.8M

图片压缩优化之后:

对于内存查看 是5.4M

Bitmap bitmap = ImageResize.resizeBitmap(context, R.drawable.icon_mv, 80, 80, false);
public class ImageResize {

    /**
     * 返回压缩图片
     *
     * @param context
     * @param id
     * @param maxW
     * @param maxH
     * @param hasAlpha
     * @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();
        // 设置为true后,再去解析,就只解析 out 参数
        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;
        // 复用, inMutable 为true 表示易变
        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;
    }
}

缓存优化之后:

Bitmap bitmap = ImageCache.getInstance().getBitmapFromMemory(String.valueOf(i)); Log.e("leo", "使用内存缓存" + bitmap); if (bitmap == null) { Bitmap reusable = ImageCache.getInstance().getReusable(60, 60, 1); Log.e("leo", "使用复用缓存" + reusable);

 bitmap = ImageCache.getInstance().getBitmapFromDisk(String.valueOf(i), reusable);
    Log.e("leo", "使用磁盘缓存" + reusable);

    if (bitmap == null) {
        // 网络获取
        bitmap = ImageResize.resizeBitmap(context, R.drawable.icon_mv, 80, 80, false, reusable);
        //放入内存
        ImageCache.getInstance().putBitmap2Memory(String.valueOf(i), bitmap);
        //放入磁盘
        ImageCache.getInstance().putBitmap2Disk(String.valueOf(i), bitmap);
    }

}
public class ImageCache {

    private static ImageCache instance;

    /**
     * 单例类
     *
     * @return
     */
    public static ImageCache getInstance() {
        if (instance == null) {
            synchronized (ImageCache.class) {
                if (instance == null) {
                    instance = new ImageCache();
                }
            }
        }
        return instance;
    }

    private LruCache<String, Bitmap> lruCache;
    private Set<WeakReference<Bitmap>> reusablePool;
    private DiskLruCache diskLruCache;


    public void init(Context context, String dir) {

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

        // 内存大小
        ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
        int memoryClass = am.getMemoryClass();

        //memoryClass * 1024 * 1024 / 8
        lruCache = new LruCache<String, Bitmap>(memoryClass * 1024 * 1024 / 8) {
            // 返回的一张图片大小
            @Override
            protected int sizeOf(String key, Bitmap value) {
                if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) {
                    return value.getAllocationByteCount();
                }
                return value.getByteCount();
            }

            @Override
            protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) {
                if (oldValue.isMutable()) {
                    // 3.0 bitmap 缓存 native
                    // <8.0  bitmap 缓存 java
                    // 8.0 native
                    reusablePool.add(new WeakReference<Bitmap>(oldValue, getReferenceQueue()));
                }

                oldValue.recycle();
            }
        };
        try {
            diskLruCache = DiskLruCache.open(new File(dir), BuildConfig.VERSION_CODE, 1, 10 * 1024 * 1024);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 放入磁盘缓存
     */
    public void putBitmap2Disk(String key, Bitmap bitmap) {
        DiskLruCache.Snapshot snapshot = null;
        OutputStream os = null;
        try {
            snapshot = diskLruCache.get(key);
            if (snapshot == null) {
                DiskLruCache.Editor edit = diskLruCache.edit(key);
                if (edit != null) {
                    os = edit.newOutputStream(0);
                    bitmap.compress(Bitmap.CompressFormat.JPEG, 50, os);
                    edit.commit();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (snapshot != null) {
                snapshot.close();
            }
            if (os != null) {
                try {
                    os.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 从磁盘缓存获取bitmap
     *
     * @param key
     * @param reusable
     * @return
     */
    public Bitmap getBitmapFromDisk(String key, Bitmap reusable) {
        DiskLruCache.Snapshot snapshot = null;
        Bitmap bitmap = null;
        try {
            snapshot = diskLruCache.get(key);
            if (snapshot == null) {
                return null;
            }
            InputStream is = snapshot.getInputStream(0);

            BitmapFactory.Options options = new BitmapFactory.Options();
            options.inMutable = true;
            options.inBitmap = reusable;
            bitmap = BitmapFactory.decodeStream(is, null, options);
            if (bitmap != null) {
                lruCache.put(key, bitmap);
            }

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (snapshot != null) {
                snapshot.close();
            }
        }
        return bitmap;
    }

    private ReferenceQueue<Bitmap> referenceQueue;
    boolean shutDown;

    private ReferenceQueue<Bitmap> getReferenceQueue() {
        if (referenceQueue == null) {
            referenceQueue = new ReferenceQueue<>();
            new Thread(new Runnable() {
                @Override
                public void run() {
                    while (!shutDown) {
                        try {
                            Reference<? extends Bitmap> remove = referenceQueue.remove();
                            Bitmap bitmap = remove.get();
                            if (bitmap != null && !bitmap.isRecycled()) {
                                bitmap.recycle();
                            }

                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }).start();
        }
        return referenceQueue;
    }

    /**
     * 把bitmap 放入内存
     *
     * @param key
     * @param bitmap
     */
    public void putBitmap2Memory(String key, Bitmap bitmap) {
        lruCache.put(key, bitmap);
    }

    /**
     * 从内存中取出bitmap
     *
     * @param key
     * @return
     */
    public Bitmap getBitmapFromMemory(String key) {
        return lruCache.get(key);
    }

    public void clearMemory() {
        lruCache.evictAll();
    }


    /**
     * 3.0 之前不能复用
     * 3.0-4.4 宽高一样,inSampleSize = 1
     * 4.4 只要小于等于就行了
     *
     * @param w
     * @param h
     * @param inSampleSize
     * @return
     */
    public Bitmap getReusable(int w, int h, int inSampleSize) {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
            return null;
        }
        Bitmap reusable = null;

        Iterator<WeakReference<Bitmap>> iterator = reusablePool.iterator();
        while (iterator.hasNext()) {
            Bitmap bitmap = iterator.next().get();
            if (bitmap != null) {
                if (checkInBitmap(bitmap, w, h, inSampleSize)) {
                    reusable = bitmap;
                    iterator.remove();
                    break;
                }
            } else {
                iterator.remove();
            }
        }

        return reusable;

    }

   /**
     * 校验bitmap 是否满足条件
     *
     * @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 * getBytesPerPixel(bitmap.getConfig());
        // 图片内存 系统分配内存
        return byteCount <= bitmap.getAllocationByteCount();


    }

    /**
     * 通过像素格式计算每一个像素占用多少字节
     *
     * @param config
     * @return
     */
    private int getBytesPerPixel(Bitmap.Config config) {
        if (config == Bitmap.Config.ARGB_8888) {
            return 4;
        }
        return 2;
    }

}

加载长图

BigImageView bigImageView = findViewById(R.id.iv_big);
    InputStream is = null;
    try {
        is = getAssets().open("big.png");

        bigImageView.setImage(is);

    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        if (is != null) {
            try {
                is.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

}

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.BitmapRegionDecoder;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Scroller;

import java.io.IOException;
import java.io.InputStream;

public class BigImageView extends View implements GestureDetector.OnGestureListener, View.OnTouchListener {


    Rect mRect;
    BitmapFactory.Options mOptions;
    int mImageWidth;
    int mImageHeight;
    BitmapRegionDecoder mBitmapRegionDecoder;
    private GestureDetector mGestureDetector;
    private Scroller mScroller;

    public BigImageView(Context context) {
        this(context, null, 0);
    }

    public BigImageView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public BigImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        mRect = new Rect();
        mOptions = new BitmapFactory.Options();

        //手势
        mGestureDetector = new GestureDetector(context, this);
        // 将触摸事件交给手势处理
        setOnTouchListener(this);
        // 滑动帮助
        mScroller = new Scroller(context);

    }

    public void setImage(InputStream is) {

        mOptions.inJustDecodeBounds = true;
        BitmapFactory.decodeStream(is, null, mOptions);

        mImageWidth = mOptions.outWidth;
        mImageHeight = mOptions.outHeight;

        mOptions.inMutable = true;
        mOptions.inPreferredConfig = Bitmap.Config.RGB_565;

        mOptions.inJustDecodeBounds = false;

        try {
            // false 不共享 图片源
            mBitmapRegionDecoder = BitmapRegionDecoder.newInstance(is, false);
        } catch (IOException e) {
            e.printStackTrace();
        }

        requestLayout();
    }

    int mViewHeight;
    int mViewWidth;
    float mScale;

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        mViewHeight = getMeasuredHeight();
        mViewWidth = getMeasuredWidth();

        if (mBitmapRegionDecoder == null) {
            return;
        }

        mRect.left = 0;
        mRect.top = 0;
        mRect.right = mImageWidth;

        // 缩放因子
        mScale = mViewWidth / (float) mImageWidth;

        // x * mscale = mViewHeight
        mRect.bottom = (int) (mViewHeight / mScale);

        // 第一种方式优化
//        mOptions.inSampleSize = calcuteInSampleSize(mImageWidth, mImageHeight, mViewWidth, mViewHeight);

        // 第二种方式优化
//        float temp = 1.0f / mScale;
//        if (temp > 1) {
//            mOptions.inSampleSize = (int) Math.pow(2, (int) (temp));
//        } else {
//            mOptions.inSampleSize = 1;
//        }


        Log.e("Leo", "============缩放后=========");
        Log.e("Leo", "inSampleSize = " + mOptions.inSampleSize);
        Log.e("Leo", "mScale = " + mScale);
        Log.e("Leo", "图片宽 = " + mImageWidth + ",高 = " + mImageHeight);
        Log.e("Leo", "view 宽 = " + mViewWidth + ",高 = " + mViewHeight);

    }

    /**
     * @param w    图片宽
     * @param h    图片高
     * @param maxW View 宽
     * @param maxH View 高
     * @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;
    }

    Bitmap bitmap = null;

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        if (mBitmapRegionDecoder == null) {
            return;
        }

        mOptions.inBitmap = bitmap;
        bitmap = mBitmapRegionDecoder.decodeRegion(mRect, mOptions);
        Log.e("leo", "图片大小 " + bitmap.getByteCount());// 没有优化:44338752,1.优化:2770200,2优化:692064

        Matrix matrix = new Matrix();
        matrix.setScale(mScale, mScale);
//        matrix.setScale(mScale * mOptions.inSampleSize, mScale * mOptions.inSampleSize);

        canvas.drawBitmap(bitmap, matrix, null);
    }

    @Override
    public boolean onDown(MotionEvent e) {
        // 如果滑动还没有停止 强制停止
        if (!mScroller.isFinished()) {
            mScroller.forceFinished(true);
        }
        //继续接收后续事件
        return true;
    }

    @Override
    public void onShowPress(MotionEvent e) {

    }

    @Override
    public boolean onSingleTapUp(MotionEvent e) {
        return false;
    }


    @Override
    public void onLongPress(MotionEvent e) {

    }

    @Override
    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
        //改变加载图片的区域
        mRect.offset(0, (int) distanceY);

        //bottom大于图片高了, 或者 top小于0了
        if (mRect.bottom > mImageHeight) {
            mRect.bottom = mImageHeight;
            mRect.top = mImageHeight - (int) (mViewHeight / mScale);
        }
        if (mRect.top < 0) {
            mRect.top = 0;
            mRect.bottom = (int) (mViewHeight / mScale);
        }

        // 重绘
        invalidate();
        return false;
    }


    @Override
    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
        /**
         * startX: 滑动开始的x坐标
         * velocityX: 以每秒像素为单位测量的初始速度
         * minX: x方向滚动的最小值
         * maxX: x方向滚动的最大值
         */
        mScroller.fling(0, mRect.top, 0, (int) -velocityY, 0, 0,
                0, mImageHeight - (int) (mViewHeight / mScale));
        return false;
    }

    /**
     * 获取计算结果并且重绘
     */
    @Override
    public void computeScroll() {
        //已经计算结束 return
        if (mScroller.isFinished()) {
            return;
        }
        //true 表示当前动画未结束
        if (mScroller.computeScrollOffset()) {
            mRect.top = mScroller.getCurrY();
            mRect.bottom = mRect.top + (int) (mViewHeight / mScale);
            invalidate();
        }
    }

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        // 事件交给手势处理
        return mGestureDetector.onTouchEvent(event);
    }
}