Android内存优化————加载长图

项目中总会遇到加载长图的需求,图片的长度可能是手机长度的很多倍,也就是需要通过滑动来查看图片。比较简单的实现方式就是使用ScrollView来加载长图,但是这样做有一个很严重的问题,就是内存消耗严重。我这里有一张长图,宽高为440*10260,大小为477KB,使用ScrollView加载的话,总内存消耗为97M,是相当恐怖的。而使用优化后的自定View加载长图,内存消耗为46M,极大的减少了内存的优化,效果非常明显。我简单说一下实现的过程

1.创建自定义View——BigImageView

对BigImageView的构造器进行简单的修改,并且初始化相关的属性
 

public class BigImageView extends View implements
        View.OnTouchListener, GestureDetector.OnGestureListener{
    private Rect mRect;//长图中要加载的区域
    private BitmapFactory.Options mOptions;
    private GestureDetector mGestureDetector;//手势的检测器
    private Scroller mScroller;//滚动的帮助类

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

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

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

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

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

    @Override
    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
        return false;
    }

    @Override
    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
        return false;
    }

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

虽然实现的方法比较多,但是有很多都方法都用不到,比如onShowPress,onSingleTapUp,onLongPress。

上面的代码基本上都很好理解,而mRect这个属性我简单画图说明一下。

mRect就是图中蓝色框的部分。mRect和手机的尺寸(黑色框)比例是一样;mRect的宽度和长图的宽度是一样的。展示的时候,需要把mRect区域根据缩放比例,展示到手机的屏幕既可以。

2.设置输入图片的入口

创建了一个属性mDecoder,用来进行图片的解码

/**
     * 输入一张图片
     */
    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();
    }

3.覆写onMeasure方法

获取了BigImageView的宽高,可以和上一步获取的图片的宽高算出缩放比例。并且设置mRect的上下左右值,用来展示长图的哪部分

/**
     * 在测量的时候把我们需要的内存区域获取到  存入到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);
    }

4.覆写onScroll方法

主要是两个功能:(1)每次滑动,mRect展示的区域都会改变(2)滑动到顶点和底部的处理

/**
     *
     * @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;
    }

5.覆写onDraw

在画布上画出展示的图片

/**
     * 画出内容
     */
    @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);
    }

6.惯性滑动处理

onFling方法里的内容主要就是计算惯性滑动的值

computeScroll主要就是惯性滑动行为

需要注意的是mScroller.fling中的第四的参数,是负值

/**
     * 处理惯性问题
     * @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();
        }
    }

7.手机按下停止滑动处理

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

8.调用

BigImageView1 bigView=findViewById(R.id.bigView);
        InputStream is=null;
        try{
            //加载图片
            is=getAssets().open("big.png");
            bigView.setImage(is);
        }catch(Exception e){
            e.printStackTrace();
        }finally {
            if(is!=null){
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

长图加载的内容主要就是这些,注意细节处理,其实很容易理解。我这里也只是介绍了简单的长图加载,关于横向长图,双指缩放等功能可以自己完善~

demo地址

有问题欢迎留言,欢迎提问,欢迎纠错!
 

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