APP性能优化-长图加载

前言

常见内存泄露及优化方案

讲解流程:

1.业务需求
2.完整显示,精度要求不高处理办法
3.精度要求高,不需要完成显示处理
4.总结

1.业务需求

1.1完整显示,对精度要求不高,图片本身就很大
1.2对精度需求比较高,不需要完整显示

2.完整显示,精度要求不高处理办法

1.根据显示设备本身大小进行缩放
2.降低精度加载(改变图片模式RGB565)
3.修改图片格式(png改成webp,jpg)

3.精度要求高,不需要完成显示处理(区域加载)

3.1BitmapRegionDecoder

其实从名字我们也可以看出:指定Bitmap区域进行解码,没错它主要用于显示图片的某一块矩形区域,如果需要显示某个图片的指定区域,那么这个类非常合适。

它的用法也非常简单,既然是显示图片的某一块区域,那么至少需要两个方法:1、设置图片,2、设置显示区域。

接下来通过自定义一个可以加载巨图的View展开说明:

3.2、设置图片

    /**
     * 由使用者输入一张图片
     */
    public void setImage(InputStream is){
        //先读取原图片的信息   高,宽
        mOptions.inJustDecodeBounds=true;
        BitmapFactory.decodeStream(is,null,mOptions);
        mImageWidth=mOptions.outWidth;
        mImageHeight=mOptions.outHeight;
        //开启复用
        mOptions.inMutable=true;
        //设置格式成RGB_565
        mOptions.inPreferredConfig=Bitmap.Config.RGB_565;
        mOptions.inJustDecodeBounds=false;

        //创建一个区域解码器
        try {
            mDecoder=BitmapRegionDecoder.newInstance(is,false);
        } catch (IOException e) {
            e.printStackTrace();
        }


        requestLayout();
    }

设置inJustDecodeBounds为ture(只解码图片的尺寸)。然后得到图片宽高,并且设置Bitmap是可以被复用的。然后创建BitmapRegionDecoder的实例对象。最后调用requestLayout()方法,reqeustLayout会重新测量我们的布局也就是会执行View的onMeasure()。

3.3指定显示区域

/**
     * 在测量的时候把我们需要的内存区域获取到  存入到mRect中
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //获取测量的view的大小
        mViewWidth=getMeasuredWidth();
        mViewHeight=getMeasuredHeight();

        //确定要加载的图片的区域
        mRect.left=0;
        mRect.top=0;
        mRect.right=mImageWidth;
        //获取一个缩放因子
        mScale=mViewWidth/(float)mImageWidth;
        //高度就根据缩放比进行获取
        mRect.bottom=(int)(mViewHeight/mScale);

    }

    /**
     * 画出内容
     */
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //如果解码器拿不到,表示没有设置过要显示的图片
        if(null==mDecoder){
            return;
        }
        //复用上一张bitmap
        mOptions.inBitmap=bitmap;
        //解码指定的区域
        bitmap=mDecoder.decodeRegion(mRect,mOptions);
        //把得到的矩阵大小的内存进行缩放  得到view的大小
        Matrix matrix=new Matrix();
        matrix.setScale(mScale,mScale);
        //画出来
        canvas.drawBitmap(bitmap,matrix,null);


    }

3.4改变区域完成巨图加载

Scroller + GestureDetector借助手势GestureDetector与Scroller(滑动帮助)来完成这一功能。

附上完整代码

package com.example.administrator.lsn_8_demo;

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

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

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

    private Rect mRect;
    private BitmapFactory.Options mOptions;
    private GestureDetector mGestureDetector;
    private Scroller mScroller;
    private int mImageWidth;
    private int mImageHeight;
    private BitmapRegionDecoder mDecoder;
    private int mViewWidth;
    private int mViewHeight;
    private float mScale;
    private Bitmap bitmap;

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

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

    public BigView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        //指定要加载的区域
        mRect =new Rect();
        //需要复用
        mOptions=new BitmapFactory.Options();
        //手势识别类
        mGestureDetector=new GestureDetector(context,this);
        //设置onTouchListener
        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;
        //设置格式成RGB_565
        mOptions.inPreferredConfig=Bitmap.Config.RGB_565;
        mOptions.inJustDecodeBounds=false;

        //创建一个区域解码器
        try {
            mDecoder=BitmapRegionDecoder.newInstance(is,false);
        } catch (IOException e) {
            e.printStackTrace();
        }


        requestLayout();
    }

    /**
     * 在测量的时候把我们需要的内存区域获取到  存入到mRect中
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //获取测量的view的大小
        mViewWidth=getMeasuredWidth();
        mViewHeight=getMeasuredHeight();

        //确定要加载的图片的区域
        mRect.left=0;
        mRect.top=0;
        mRect.right=mImageWidth;
        //获取一个缩放因子
        mScale=mViewWidth/(float)mImageWidth;
        //高度就根据缩放比进行获取
        mRect.bottom=(int)(mViewHeight/mScale);

    }

    /**
     * 画出内容
     */
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //如果解码器拿不到,表示没有设置过要显示的图片
        if(null==mDecoder){
            return;
        }
        //复用上一张bitmap
        mOptions.inBitmap=bitmap;
        //解码指定的区域
        bitmap=mDecoder.decodeRegion(mRect,mOptions);
        //把得到的矩阵大小的内存进行缩放  得到view的大小
        Matrix matrix=new Matrix();
        matrix.setScale(mScale,mScale);
        //画出来
        canvas.drawBitmap(bitmap,matrix,null);


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


    /**
     * 手按下的回调
     * @param e
     * @return
     */
    @Override
    public boolean onDown(MotionEvent e) {
        //如果移动还没有停止,强制停止
        if(!mScroller.isFinished()){
            mScroller.forceFinished(true);
        }
        //继续接收后续事件
        return true;
    }


    /**
     *
     * @param e1   接下
     * @param e2   移动
     * @param distanceX    左右移动时的距离
     * @param distanceY   上下移动时的距离
     * @return
     */
    @Override
    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
        //上下移动的时候,需要改变显示区域   改mRect
        mRect.offset(0,(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);
        }
        invalidate();
        return false;
    }

    /**
     * 处理惯性问题
     * @param e1
     * @param e2
     * @param velocityX   每秒移动的x点
     * @param velocityY
     * @return
     */
    @Override
    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
        //做计算
        mScroller.fling(0,mRect.top,
                0,(int)-velocityY,
                0,0,
                0,mImageHeight-(int)(mViewHeight/mScale));
        return false;
    }
    /*
    使用上一个接口的计算结果
     */
    @Override
    public void computeScroll() {
        if(mScroller.isFinished()){
            return;
        }
        //true 表示当前滑动还没有结束
        if(mScroller.computeScrollOffset()){
            mRect.top=mScroller.getCurrY();
            mRect.bottom=mRect.top+(int)(mViewHeight/mScale);
            invalidate();
        }
    }

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

    }
}

4.总结

1、首先确定大图的用途,精度需求:
a)完整显示,对精度要求不高,图片本身就很大
b)对精度需求比较高,不需要完整显示
2、解决方案
a)针对第一种的处理图片本身,按需加载(根据显示设备本身大小进行缩放),降低精度加载(改变图片模式,如将ARGB8888改成RGB565,ARGB4444),修改图片格式(png改成webp,jpg)
b)第二种的一般采用局部加载,主要要用到的是BitmapRegionDecoder这个类decodeRegion的方法,读取图片指定大小的数据,然后通过移动来动态改变显示区域的图片

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容