APP性能優(yōu)化-長圖加載

前言

常見內(nèi)存泄露及優(yōu)化方案

講解流程:

1.業(yè)務(wù)需求
2.完整顯示,精度要求不高處理辦法
3.精度要求高,不需要完成顯示處理
4.總結(jié)

1.業(yè)務(wù)需求

1.1完整顯示,對精度要求不高,圖片本身就很大
1.2對精度需求比較高,不需要完整顯示

2.完整顯示,精度要求不高處理辦法

1.根據(jù)顯示設(shè)備本身大小進(jìn)行縮放
2.降低精度加載(改變圖片模式RGB565)
3.修改圖片格式(png改成webp,jpg)

3.精度要求高,不需要完成顯示處理(區(qū)域加載)

3.1BitmapRegionDecoder

其實(shí)從名字我們也可以看出:指定Bitmap區(qū)域進(jìn)行解碼,沒錯(cuò)它主要用于顯示圖片的某一塊矩形區(qū)域,如果需要顯示某個(gè)圖片的指定區(qū)域,那么這個(gè)類非常合適。

它的用法也非常簡單,既然是顯示圖片的某一塊區(qū)域,那么至少需要兩個(gè)方法:1、設(shè)置圖片,2、設(shè)置顯示區(qū)域。

接下來通過自定義一個(gè)可以加載巨圖的View展開說明:

3.2、設(shè)置圖片

    /**
     * 由使用者輸入一張圖片
     */
    public void setImage(InputStream is){
        //先讀取原圖片的信息   高,寬
        mOptions.inJustDecodeBounds=true;
        BitmapFactory.decodeStream(is,null,mOptions);
        mImageWidth=mOptions.outWidth;
        mImageHeight=mOptions.outHeight;
        //開啟復(fù)用
        mOptions.inMutable=true;
        //設(shè)置格式成RGB_565
        mOptions.inPreferredConfig=Bitmap.Config.RGB_565;
        mOptions.inJustDecodeBounds=false;

        //創(chuàng)建一個(gè)區(qū)域解碼器
        try {
            mDecoder=BitmapRegionDecoder.newInstance(is,false);
        } catch (IOException e) {
            e.printStackTrace();
        }


        requestLayout();
    }

設(shè)置inJustDecodeBounds為ture(只解碼圖片的尺寸)。然后得到圖片寬高,并且設(shè)置Bitmap是可以被復(fù)用的。然后創(chuàng)建BitmapRegionDecoder的實(shí)例對象。最后調(diào)用requestLayout()方法,reqeustLayout會重新測量我們的布局也就是會執(zhí)行View的onMeasure()。

3.3指定顯示區(qū)域

/**
     * 在測量的時(shí)候把我們需要的內(nèi)存區(qū)域獲取到  存入到mRect中
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //獲取測量的view的大小
        mViewWidth=getMeasuredWidth();
        mViewHeight=getMeasuredHeight();

        //確定要加載的圖片的區(qū)域
        mRect.left=0;
        mRect.top=0;
        mRect.right=mImageWidth;
        //獲取一個(gè)縮放因子
        mScale=mViewWidth/(float)mImageWidth;
        //高度就根據(jù)縮放比進(jìn)行獲取
        mRect.bottom=(int)(mViewHeight/mScale);

    }

    /**
     * 畫出內(nèi)容
     */
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //如果解碼器拿不到,表示沒有設(shè)置過要顯示的圖片
        if(null==mDecoder){
            return;
        }
        //復(fù)用上一張bitmap
        mOptions.inBitmap=bitmap;
        //解碼指定的區(qū)域
        bitmap=mDecoder.decodeRegion(mRect,mOptions);
        //把得到的矩陣大小的內(nèi)存進(jìn)行縮放  得到view的大小
        Matrix matrix=new Matrix();
        matrix.setScale(mScale,mScale);
        //畫出來
        canvas.drawBitmap(bitmap,matrix,null);


    }

3.4改變區(qū)域完成巨圖加載

Scroller + GestureDetector借助手勢GestureDetector與Scroller(滑動(dòng)幫助)來完成這一功能。

附上完整代碼

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);
        //指定要加載的區(qū)域
        mRect =new Rect();
        //需要復(fù)用
        mOptions=new BitmapFactory.Options();
        //手勢識別類
        mGestureDetector=new GestureDetector(context,this);
        //設(shè)置onTouchListener
        setOnTouchListener(this);


        //滑動(dòng)幫助
        mScroller=new Scroller(context);

    }

    /**
     * 由使用者輸入一張圖片
     */
    public void setImage(InputStream is){
        //先讀取原圖片的信息   高,寬
        mOptions.inJustDecodeBounds=true;
        BitmapFactory.decodeStream(is,null,mOptions);
        mImageWidth=mOptions.outWidth;
        mImageHeight=mOptions.outHeight;
        //開啟復(fù)用
        mOptions.inMutable=true;
        //設(shè)置格式成RGB_565
        mOptions.inPreferredConfig=Bitmap.Config.RGB_565;
        mOptions.inJustDecodeBounds=false;

        //創(chuàng)建一個(gè)區(qū)域解碼器
        try {
            mDecoder=BitmapRegionDecoder.newInstance(is,false);
        } catch (IOException e) {
            e.printStackTrace();
        }


        requestLayout();
    }

    /**
     * 在測量的時(shí)候把我們需要的內(nèi)存區(qū)域獲取到  存入到mRect中
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //獲取測量的view的大小
        mViewWidth=getMeasuredWidth();
        mViewHeight=getMeasuredHeight();

        //確定要加載的圖片的區(qū)域
        mRect.left=0;
        mRect.top=0;
        mRect.right=mImageWidth;
        //獲取一個(gè)縮放因子
        mScale=mViewWidth/(float)mImageWidth;
        //高度就根據(jù)縮放比進(jìn)行獲取
        mRect.bottom=(int)(mViewHeight/mScale);

    }

    /**
     * 畫出內(nèi)容
     */
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //如果解碼器拿不到,表示沒有設(shè)置過要顯示的圖片
        if(null==mDecoder){
            return;
        }
        //復(fù)用上一張bitmap
        mOptions.inBitmap=bitmap;
        //解碼指定的區(qū)域
        bitmap=mDecoder.decodeRegion(mRect,mOptions);
        //把得到的矩陣大小的內(nèi)存進(jìn)行縮放  得到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);
    }


    /**
     * 手按下的回調(diào)
     * @param e
     * @return
     */
    @Override
    public boolean onDown(MotionEvent e) {
        //如果移動(dòng)還沒有停止,強(qiáng)制停止
        if(!mScroller.isFinished()){
            mScroller.forceFinished(true);
        }
        //繼續(xù)接收后續(xù)事件
        return true;
    }


    /**
     *
     * @param e1   接下
     * @param e2   移動(dòng)
     * @param distanceX    左右移動(dòng)時(shí)的距離
     * @param distanceY   上下移動(dòng)時(shí)的距離
     * @return
     */
    @Override
    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
        //上下移動(dòng)的時(shí)候,需要改變顯示區(qū)域   改mRect
        mRect.offset(0,(int)distanceY);
        //處理移動(dòng)時(shí)已經(jīng)移到了兩個(gè)頂端的問題
        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   每秒移動(dòng)的x點(diǎn)
     * @param velocityY
     * @return
     */
    @Override
    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
        //做計(jì)算
        mScroller.fling(0,mRect.top,
                0,(int)-velocityY,
                0,0,
                0,mImageHeight-(int)(mViewHeight/mScale));
        return false;
    }
    /*
    使用上一個(gè)接口的計(jì)算結(jié)果
     */
    @Override
    public void computeScroll() {
        if(mScroller.isFinished()){
            return;
        }
        //true 表示當(dāng)前滑動(dòng)還沒有結(jié)束
        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.總結(jié)

1、首先確定大圖的用途,精度需求:
a)完整顯示,對精度要求不高,圖片本身就很大
b)對精度需求比較高,不需要完整顯示
2、解決方案
a)針對第一種的處理圖片本身,按需加載(根據(jù)顯示設(shè)備本身大小進(jìn)行縮放),降低精度加載(改變圖片模式,如將ARGB8888改成RGB565,ARGB4444),修改圖片格式(png改成webp,jpg)
b)第二種的一般采用局部加載,主要要用到的是BitmapRegionDecoder這個(gè)類decodeRegion的方法,讀取圖片指定大小的數(shù)據(jù),然后通過移動(dòng)來動(dòng)態(tài)改變顯示區(qū)域的圖片

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容