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è)備本身大小進行縮放
2.降低精度加載(改變圖片模式RGB565)
3.修改圖片格式(png改成webp谒养,jpg)

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

3.1BitmapRegionDecoder

其實從名字我們也可以看出:指定Bitmap區(qū)域進行解碼班缎,沒錯它主要用于顯示圖片的某一塊矩形區(qū)域蝴光,如果需要顯示某個圖片的指定區(qū)域,那么這個類非常合適达址。

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

接下來通過自定義一個可以加載巨圖的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)建一個區(qū)域解碼器
        try {
            mDecoder=BitmapRegionDecoder.newInstance(is,false);
        } catch (IOException e) {
            e.printStackTrace();
        }


        requestLayout();
    }

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

3.3指定顯示區(qū)域

/**
     * 在測量的時候把我們需要的內(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;
        //獲取一個縮放因子
        mScale=mViewWidth/(float)mImageWidth;
        //高度就根據(jù)縮放比進行獲取
        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)存進行縮放  得到view的大小
        Matrix matrix=new Matrix();
        matrix.setScale(mScale,mScale);
        //畫出來
        canvas.drawBitmap(bitmap,matrix,null);


    }

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

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);
        //指定要加載的區(qū)域
        mRect =new Rect();
        //需要復(fù)用
        mOptions=new BitmapFactory.Options();
        //手勢識別類
        mGestureDetector=new GestureDetector(context,this);
        //設(shè)置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;
        //開啟復(fù)用
        mOptions.inMutable=true;
        //設(shè)置格式成RGB_565
        mOptions.inPreferredConfig=Bitmap.Config.RGB_565;
        mOptions.inJustDecodeBounds=false;

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


        requestLayout();
    }

    /**
     * 在測量的時候把我們需要的內(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;
        //獲取一個縮放因子
        mScale=mViewWidth/(float)mImageWidth;
        //高度就根據(jù)縮放比進行獲取
        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)存進行縮放  得到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) {
        //如果移動還沒有停止捕透,強制停止
        if(!mScroller.isFinished()){
            mScroller.forceFinished(true);
        }
        //繼續(xù)接收后續(xù)事件
        return true;
    }


    /**
     *
     * @param e1   接下
     * @param e2   移動
     * @param distanceX    左右移動時的距離
     * @param distanceY   上下移動時的距離
     * @return
     */
    @Override
    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
        //上下移動的時候,需要改變顯示區(qū)域   改mRect
        mRect.offset(0,(int)distanceY);
        //處理移動時已經(jīng)移到了兩個頂端的問題
        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;
    }
    /*
    使用上一個接口的計算結(jié)果
     */
    @Override
    public void computeScroll() {
        if(mScroller.isFinished()){
            return;
        }
        //true 表示當(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è)備本身大小進行縮放)嘉冒,降低精度加載(改變圖片模式,如將ARGB8888改成RGB565,ARGB4444),修改圖片格式(png改成webp咆繁,jpg)
b)第二種的一般采用局部加載讳推,主要要用到的是BitmapRegionDecoder這個類decodeRegion的方法,讀取圖片指定大小的數(shù)據(jù)玩般,然后通過移動來動態(tài)改變顯示區(qū)域的圖片

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末银觅,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子坏为,更是在濱河造成了極大的恐慌究驴,老刑警劉巖镊绪,帶你破解...
    沈念sama閱讀 217,826評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件屡立,死亡現(xiàn)場離奇詭異仅颇,居然都是意外死亡,警方通過查閱死者的電腦和手機乔煞,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,968評論 3 395
  • 文/潘曉璐 我一進店門熙侍,熙熙樓的掌柜王于貴愁眉苦臉地迎上來榄鉴,“玉大人,你說我怎么就攤上這事蛉抓∏斐荆” “怎么了?”我有些...
    開封第一講書人閱讀 164,234評論 0 354
  • 文/不壞的土叔 我叫張陵巷送,是天一觀的道長驶忌。 經(jīng)常有香客問我,道長笑跛,這世上最難降的妖魔是什么位岔? 我笑而不...
    開封第一講書人閱讀 58,562評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮堡牡,結(jié)果婚禮上抒抬,老公的妹妹穿的比我還像新娘。我一直安慰自己晤柄,他們只是感情好擦剑,可當(dāng)我...
    茶點故事閱讀 67,611評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著芥颈,像睡著了一般惠勒。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上爬坑,一...
    開封第一講書人閱讀 51,482評論 1 302
  • 那天纠屋,我揣著相機與錄音,去河邊找鬼盾计。 笑死售担,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的署辉。 我是一名探鬼主播族铆,決...
    沈念sama閱讀 40,271評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼哭尝!你這毒婦竟也來了哥攘?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,166評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎逝淹,沒想到半個月后耕姊,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,608評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡栅葡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,814評論 3 336
  • 正文 我和宋清朗相戀三年茉兰,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片妥畏。...
    茶點故事閱讀 39,926評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖安吁,靈堂內(nèi)的尸體忽然破棺而出醉蚁,到底是詐尸還是另有隱情,我是刑警寧澤鬼店,帶...
    沈念sama閱讀 35,644評論 5 346
  • 正文 年R本政府宣布网棍,位于F島的核電站,受9級特大地震影響妇智,放射性物質(zhì)發(fā)生泄漏滥玷。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,249評論 3 329
  • 文/蒙蒙 一巍棱、第九天 我趴在偏房一處隱蔽的房頂上張望惑畴。 院中可真熱鬧,春花似錦航徙、人聲如沸如贷。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,866評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽杠袱。三九已至,卻和暖如春窝稿,著一層夾襖步出監(jiān)牢的瞬間楣富,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,991評論 1 269
  • 我被黑心中介騙來泰國打工伴榔, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留纹蝴,地道東北人。 一個月前我還...
    沈念sama閱讀 48,063評論 3 370
  • 正文 我出身青樓踪少,卻偏偏與公主長得像骗灶,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子秉馏,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,871評論 2 354

推薦閱讀更多精彩內(nèi)容