美文网首页
Android 性能优化 04---Bitmap优化03(大图加

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

作者: 沪漂意哥哥 | 来源:发表于2022-04-02 11:04 被阅读0次

    一.问题描述

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

    • 大概的估算一下这张图片占用多少内存
    • 展示图片的控件的实际像素大小。
    • 考虑一下当前设备的屏幕尺寸和屏幕分辨率
      假设使用ImageView进行加载图片,很多时候ImageView没有图片的尺寸那么大,这个时候你把原始图片加载进来再设置给ImageView,是很浪费内存的,而且没必要,因为ImageView没有办法加载出原始的图片。
      第三点和资源的加载机制有关,比如同一张图片放在不同的drawable目录下,通过BitmapFactory获取的宽、高都不尽相同

    二.BitmapFactory

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

    • decodeResource:从资源加载出Bitmap对象。
    • decodeStream:从字节数组加载出Bitmap对象。
    • decodeFile: 从文件加载出Bitmap对象。
    • decodeByteArray: 从字节数组加载出Bitmap对象。
      这些方法都会为Bitmap分配内存,那就有可能发生OOM。

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

    相关文章

      网友评论

          本文标题:Android 性能优化 04---Bitmap优化03(大图加

          本文链接:https://www.haomeiwen.com/subject/vnctsrtx.html