Android超長圖加載與subsampling scale image view實現分析

Android中的圖片加載一直是很重要的一塊,也是很使人頭疼的一塊,動不動就出現OOM。因此咱們有fresco等優秀的第三方框架,什麼三級緩存,一行代碼就幫咱們輕鬆實現。但當面對超級長超級大分辨率尺寸的圖時,就顯得無能爲力了,若是直接加載到內存中就又會出現OOM。git

1.BitmapRegionDecoder

實現長圖大圖的加載,最關鍵的類就是BitmapRegionDecoder他能夠實現對圖片的局部加載。github

mDecoder=BitmapRegionDecoder.newInstance(image,false);

            //這裏不能用option來獲取圖片的寬和高,由於通過BitmapRegionDecoder類處理事後的inputstream不能在獲取的其信息,自動返回-1
            imageHeight=mDecoder.getHeight();
            imageWidth=mDecoder.getWidth();
            bmp = mDecoder.decodeRegion(mRect, option);

複製代碼

這裏注意通過BitmapRegionDecoder類處理事後的inputstream不能再用option獲取其信息,會自動返回-1。當建立decoder對象後其實並無將圖片加載到內存中,只有調用了bmp = mDecoder.decodeRegion(mRect, option);以後纔將這個mrect矩形的圖片局部加載到內存中。canvas

本身實現

那麼既然Android有這麼方便的類,那咱們豈不是很簡單就能夠本身實現啦!因此參考 鴻洋_的Android 高清加載巨圖方案 拒絕壓縮圖片 這篇博客,咱們能夠本身實現一個簡易的加載長圖框架:數組

  • 自定義一個view,重寫他的ondraw()onTouchEvent()
  • 建立GestureDetector.OnGestureListener的實現類和scroller去接管觸摸事件,在move時記錄滑動距離,重寫computeScroll()去輔助滑動
  • 初始化咱們的局部加載類BitmapRegionDecoder,當屏幕滑動到哪,記錄其座標到rect裏而後直接mDecoder.decodeRegion(mRect, option);調用 invalidate()去ondraw()
  • 同時注意設置option.inBitmap開啓圖片的複用,進一步減小內存佔用
  • 另外一個減少內存開銷的就是設置合適的採樣率,根據控件的大小對圖片進行合適的採樣壓縮
int insamplesize=1;
           while (imageWidth>1.6*width) {
                 imageWidth /= 2;
                 insamplesize*=2;
           }
           option.inMutable=true;
           option.inSampleSize=insamplesize;
複製代碼
protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (mDecoder!=null) {
                option.inBitmap=bmp;
                matrix.setScale(scale,scale);
                bmp = mDecoder.decodeRegion(mRect, option);
                canvas.drawBitmap(bmp,matrix,bitmapPaint);
            Log.i("TAG", "onDraw: "+bmp.getByteCount());
           }
    }
複製代碼

注意:這裏記錄一個小小的坑:當用matrix進行圖片的放大時,必定必定要設置畫筆Paint,設置抗鋸齒等優化,設與不設的差距真的挺大的緩存

private Paint bitmapPaint;

        bitmapPaint = new Paint();
        bitmapPaint.setAntiAlias(true);
        bitmapPaint.setFilterBitmap(true);
        bitmapPaint.setDither(true);
複製代碼

結論

這樣一個簡易的圖片加載框架就實現了,完美的避免了OOM,由於不用把圖片完整的加載到內存中!可是,通過實測,這種方法在加載分辨率比較小的大圖時滑動仍是挺絲滑的,但一遇到分辨率再大一點的,就會感覺到明顯的滑動的卡頓,因而我把目光轉向了subsampling scale image view這個目前應該是最流行的開源框架bash

2.subsampling scale imag實現原理

這裏以原理實現爲主,不想貼不少代碼,具體可本身下載閱讀github地址數據結構

1.ImageSource

subsampling scale image view經過這個類ImageSource去獲取圖片,因此咱們的圖片資源都須要經過ImageSource去加載,支持從assets,文件,流中加載,從源碼上看他其實就是一個工具類,用於方便加載各個路徑的文件框架

2.fullImageSampleSize

.fullImageSampleSamplSize由private int calculateInSampleSize(float scale)計算出,這個值應該是咱們首先應該理解的。
他決定了圖片是否須要用BitmapRegionDecoder進行區域加載。若是他的計算結果等於1,則表示這張圖的分辨率還不夠大,不須要進行切割進行區域加載,因此這種狀況下是最簡單的,直接將圖加載進入,放大縮小,移動,都是經過Matrix來實現的,因此接下來就來講一下Matrix工具

3.Matrix

matrix,矩陣,不少關於圖片的功能都能經過他來作一些十分的變換來實現(惋惜當年線代沒學好。。。)好比圖片個人位移,放縮,旋轉等等。subsampling scale image view也用了matrix來實現圖片的放縮和位移,主要方法是
matrix.setPolyToPoly(srcArray, 0, dstArray, 0, 4); 有兩個數組srA,rray和dstArray dstarray數組決定了圖片在屏幕的位置,而大圖的移動滑動就是經過他來實現的性能

4.Tile

private static class Tile這個內部類就是切片類,subsampling scale image view中最重要的一個數據結構。

private static class Tile {

        private Rect sRect;
        private int sampleSize;
        private Bitmap bitmap;
        private boolean loading;
        private boolean visible;

        // Volatile fields instantiated once then updated before use to reduce GC.
        private Rect vRect;
        private Rect fileSRect;

    }

複製代碼

他的屬性也很簡單就是用來存儲圖片的一段切片信息,各類rect和bitmap和一個samplesiz其中須要區分一下各個rect

  • srect和filesrect實際上是保存這個切片的原始大小區域,也就調用是mDecoder.decodeRegion(mRect, option)區域加載時傳入的rect
  • vRect描述繪製在view畫布中的實際位置,也就是說圖片放大後的上下滑動就是經過改變這個rect結合matrix.setPolyToP()來實現的
protected void onDraw(Canvas canvas) {
                            ......
                          if (matrix == null) { matrix = new Matrix(); }
                            matrix.reset();
                            setMatrixArray(srcArray, 0, 0, tile.bitmap.getWidth(), 0, tile.bitmap.getWidth(), tile.bitmap.getHeight(), 0, tile.bitmap.getHeight());
                            if (getRequiredRotation() == ORIENTATION_0) {
                                setMatrixArray(dstArray, tile.vRect.left, tile.vRect.top, tile.vRect.right, tile.vRect.top, tile.vRect.right, tile.vRect.bottom, tile.vRect.left,  
                                }...
                                ...
                                
                            matrix.setPolyToPoly(srcArray, 0, dstArray, 0, 4);
                            canvas.drawBitmap(tile.bitmap, matrix, bitmapPaint);

                            ......
    }
複製代碼
5.三個task

TilesInitTask,TileLoadTask ,BitmapLoadTask
subsampling scale image view內部又建立了三個繼承自AsyncTask的task用來在後臺加載decode圖從而不阻礙ui主線程,更加流暢。 因此當fullImageSampleSize==1時,就直接用BitmapLoadTask解碼整個圖片不需切割,當期大於1時,就須要用TileLoadTask區域解碼分割後的圖片

Map<Integer, List<Tile>> tileMap

最後介紹這個框架的核心,就是這個map
咱們知道,圖片放得越大,所須要的像素分辨率就要越高才能匹配,要否則就會很模糊。相反,若是圖片縮得很小,就不須要很高的分辨率,多了就浪費了。而Android中就能夠根據 option.inSampleSize來對圖片進行採樣壓縮,減少分辨率。
因此,根據這個原理,subsampling scale image view將其根據須要計算出不一樣的採樣率,當作key,而後根據不一樣的採樣率進行切割,生成List<Tile>
放大的時候,subsampling scale image view會選取合適的採樣率後獲取到List<Tile>而後進行解碼,而且,他只會解碼顯示的部分,也就是til.visiable爲true時纔會解碼。否者將其回收。

綜上就是subsampling scale image view的大體實現原理

對比

對比本身實現的和subsampling scale image view,後者在大圖的切片方面作得更好只將大圖切成若干片,在判斷是否可見,若是可見就加載到內存中,否者回收;滑動時只改變矩陣的值進行簡單的位移變換,進一步提高了流暢度,並且根據不一樣放縮比例選擇合適的採樣率,進一步減小內存佔用。本身實現的每滑動一次就要從新解碼繪製好幾回,因此後者性能更高,值得學習。

相關文章
相關標籤/搜索