Android 性能优化 04---Bitmap优化03(大图加

2022-04-02  本文已影响0人  沪漂意哥哥

一.问题描述

当我们决定是把原图加载进内存还是压缩图加载进内存的时候,有几点是需要考虑的:

二.BitmapFactory

它提供了4类方法,分别是:

三.BitmapFactory.Options

那有什么办法可以避免呢。这时候BitmapFactory.Options就要上场了,将它的属性inJustDecodeBounds设置为true就可以让解析方法不给Bitmap分配内存,也就能防止OOM,返回值也不是实际的bitmap,而是null,但是我们还是可以查询图片的相关信息比如宽、高。

BitmapFactory.Options bmOptions = new BitmapFactory.Options();
 // 值设为true那么将不返回实际的bitmap,也不给其分配内存空间这样就避免内存溢出了。但是允许我们查询图片的信息这其中就包括图片大小信息
 bmOptions.inJustDecodeBounds = true;
 BitmapFactory.decodeFile(filePath, bmOptions);
 int photoW = bmOptions.outWidth;
 int photoH = bmOptions.outHeight;

四.区域解码(分块加载)

在这里,简单说一下区域解码。假设我们有一张非常大的照片,例如它的分辨率是4000 * 3000。那么,在常规的的手机屏幕(1080 * 1920)上,如果不做压缩处理,我们的图片很明显是显示不开的。因此,我们想到了这样的方案:让图片支持滑动,滑动到哪里,加载哪一部分。如下图片,我们要在手机上按照1:1的比例高清展示出来,那么1080*1920的区域,大概只能让他显示黑框中的区域。那其他的区域就需要我们滑动去加载。滑动到哪里,就展示哪一块区域。

五.BitmapRegionDecoder

1、使用
(1)创建BitmapRegionDecoder
使用区域解码,那么我们首先需要创建一个BitmapRegionDecoder对象。只需要调用newInstance方法,传入一个InputStream和一个boolean值。如下所示:

mDecoder = BitmapRegionDecoder.newInstance(is, false);

2)解码Bitmap
调用decodeRegion方法解码Bitmap,需要传入一块区域,以及参数,代码如下:

Bitmap bitmap = mDecoder.decodeRegion(mRect, mDecodeOptions);

六.自定义大图加载View

package com.luisliuyi.demo.optimize.bitmap03;

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.util.AttributeSet;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.View;
import android.widget.Scroller;

import androidx.annotation.Nullable;

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

public class LYBigView extends View implements View.OnTouchListener, GestureDetector.OnGestureListener,GestureDetector.OnDoubleTapListener {

    //图片缩放因子
    private float mScale;

    //需要显示的区域
    private Rect mRect;

    //区域解码器
    private BitmapRegionDecoder mDecode;

    private BitmapFactory.Options mOptions;

    //图片的宽度
    private int mImageWidth;
    //图片的高度
    private int mImageHeight;
    //控件的宽度
    private int mViewWidth;
    //控件的高度
    private int mViewHeight;

    private GestureDetector mGestureDetector;
    //缩放功能
    ScaleGestureDetector mScaleGestureDetector;

    private float originalScale;
    //需要展示的图片,是被复用的
    private Bitmap mBitmap;

    //滑动帮助类
    private Scroller mScroller;

    private Matrix matrix = new Matrix();

    public LYBigView(Context context) {
        super(context);
    }

    public LYBigView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    private void init(Context context) {
        mRect = new Rect();
        mOptions = new BitmapFactory.Options();
        mGestureDetector = new GestureDetector(context,this);
        mScaleGestureDetector = new ScaleGestureDetector(context, new ScaleGesture());
        mScroller = new Scroller(context);
        setOnTouchListener(this);
    }

    @Override
    public boolean onTouch(View v, MotionEvent motionEvent) {
        mGestureDetector.onTouchEvent(motionEvent);
        mScaleGestureDetector.onTouchEvent(motionEvent);
        return true;
    }

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

    @Override
    public void onShowPress(MotionEvent e) {

    }

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

    @Override
    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
        mRect.offset((int)distanceX,(int)distanceY);
        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);
        }

        if(mRect.left < 0) {
            mRect.left = 0;
            mRect.right = (int)(mViewWidth/mScale);
        }
        if(mRect.right > mImageWidth) {
            mRect.right = mImageWidth;
            mRect.left = mImageWidth-(int)(mViewWidth/mScale);
        }
        invalidate();
        return false;
    }

    @Override
    public void onLongPress(MotionEvent e) {

    }

    @Override
    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
        mScroller.fling(mRect.left,mRect.top,(int)velocityX,-(int)velocityY,0,
                mImageWidth-(int)(mViewWidth/mScale),0,mImageHeight-(int)(mViewHeight/mScale));
        return false;
    }

    @Override
    public void computeScroll() {
        super.computeScroll();
        if(mScroller.isFinished()){
            return;
        }
        if (mScroller.computeScrollOffset()) {
            mRect.top = mScroller.getCurrY();
            mRect.bottom = mRect.top+(int)(mViewHeight/mScale);
            invalidate();
        }
    }

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

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

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

    // 处理缩放的回调事件
    class ScaleGesture extends ScaleGestureDetector.SimpleOnScaleGestureListener{
        @Override
        public boolean onScale(ScaleGestureDetector detector) {
            float scale = mScale;
            scale +=  detector.getScaleFactor()-1;
            if(scale <= originalScale){
                scale = originalScale;
            }else if(scale > originalScale*2){
                scale = originalScale*2;
            }
            mRect.right = mRect.left + (int)(mViewWidth/scale);
            mRect.bottom = mRect.top+(int)(mViewHeight/scale);
            mScale = scale;
            invalidate();
            return super.onScale(detector);
        }
    }

    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 {
            mDecode = BitmapRegionDecoder.newInstance(is, false);
        } catch (IOException e) {
            e.printStackTrace();
        }
        //刷新
        requestLayout();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        mViewWidth = getMeasuredWidth();
        mViewHeight = getMeasuredHeight();
        mRect.left = 0;
        mRect.top = 0;
        mRect.right = Math.min(mImageWidth, mViewWidth);
        mRect.bottom = Math.min(mImageHeight,mViewHeight);
        // 再定义一个缩放因子
        originalScale = mViewWidth/(float)mImageWidth;
        mScale = originalScale;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (null == mDecode) {
            return;
        }
        mOptions.inBitmap = mBitmap;
        matrix.setScale(mScale, mScale);
        mBitmap = mDecode.decodeRegion(mRect, mOptions);
        canvas.drawBitmap(mBitmap, matrix, null);
    }
} 

七.代码地址

学习方案:

https://gitee.com/luisliuyi/android-optimize-bitmap03.git

成熟方案:

https://gitee.com/luisliuyi/android-optimize-bitmap04.git
上一篇下一篇

猜你喜欢

热点阅读