1. 程式人生 > >Android原始碼分析之Glide原始碼分析&基礎版ImageLoader框架

Android原始碼分析之Glide原始碼分析&基礎版ImageLoader框架

1 Glide原始碼分析

  Glide是一款由Bump Technologies開發的圖片載入框架,使得我們可以在Android平臺上以極度簡單的方式載入和展示圖片。本部落格基於Glide 3.7.0版本來進行講解,這個版本的Glide相當成熟和穩定。

1.1 Glide框架

這裡寫圖片描述
  簡單的講就是Glide收到載入及顯示資源的任務,建立Request並將它交給RequestManager,Request 啟動 Engine 去資料來源獲取資源(通過 Fetcher ),獲取到後Transformation處理後交給Target。Glide依賴於 DiskLRUCache、GifDecoder 等開源庫去完成本地快取和 Gif 圖片解碼工作。

1.2 Glide的優點

(1)Glide不僅支援圖片快取,它支援 Gif、WebP、縮圖。甚至是 Video,所以更該當做一個媒體快取。
(2)快取友好。①Glide的記憶體快取有個active的設計,從記憶體快取中取資料時,不像一般的實現用get,而是用remove,再將這個快取資料放到一個value為軟引用的activeResources map中,並計數引用數,在圖片載入完成後進行判斷,如果引用計數為空則回收掉。②記憶體快取更小圖片,將處理後的圖片快取在記憶體快取中,而不是原始圖片以節省大小。③圖片預設使用RGB_565而不是ARGB_888。
(3)Glide與Activity/Fragment生命週期一致

,支援trimMemory。Glide對每個context都保持一個RequestManager,通過FragmentTransaction保持與 Activity/Fragment生命週期一致,並且有對應的trimMemory介面實現可供呼叫。

1.3 重點解析

(1)如何實現Glide與Activity/Fragment生命週期一致。
  SupportRequestManagerFragment 是空的fragment ,用來監聽當前頁面的生命週期,而ActivityFragmentLifecycle 觀察fragment的生命週期(觀察者模式),回撥給Glide

1.4 時序圖

1.5 參考部落格

2 ImageLoader分析

2.1 ImageLoader框架

這裡寫圖片描述
  ImageLoader收到載入及顯示圖片的任務,並將它交給ImageLoaderEngine,ImageLoaderEngine分發任務到具體執行緒池去執行,任務通過Cache及 ImageDownloader獲取圖片,中間可能經過BitmapProcessor和ImageDecoder處理,最終轉換為Bitmap交BitmapDisplayer在ImageAware中顯示。

2.2 優缺點

  比較老的框架,穩定、 載入速度適中。 缺點在於:不支援GIF圖片載入;使用稍微繁瑣; 並且快取機制沒有和http的快取很好的結合, 完全是自己的一套快取機制(快取完整圖片);預設支援ARGB_8888畫素,對記憶體的要求高。

2.3 高效載入大圖避免程式OOM

2.3.1 問題描述

(1)描述
  給定一個1000 x 20000(寬1000px,高20000px)的大圖,如何正常載入顯示且不發生OOM ?大圖連結
(2)原因分析
  在日常開發中,載入圖片時候,遇到高解析度的圖片,如果不做處理的話,會佔用很多記憶體,造成OOM。分析OOM的原因可知,Android系統為應用分配一定大小的堆記憶體,而如果遇到高解析度的圖片時,比如:1000x20000,如果他的配置為ARGB(每個色素佔4Byte),那麼他需要消耗的記憶體大約為1000x20000x4=80MB,這樣就出現了OOM。當然這說的是使用系統原生方法載入圖片方法,多數情況下我們都是使用第三方庫Glide\Picasso,它們對大圖載入做了壓縮和快取處理。但是我們不能僅僅停留在會用階段,要研究清楚如何解決大圖載入OOM問題的原理。
  解決這個問題有3種方法:① 對圖片壓縮處理;②載入大圖拒絕壓縮圖片,使用BitmapRegionDecoder區域性載入圖片;③圖片三級快取。
(3)測試程式碼

public class MainActivity extends AppCompatActivity {
    private SimpleImageLoader imageLoader;
    /**
     * 是否對圖片壓縮處理
     */
    private boolean ivCompressEnable = false;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.list);

        //配置
        ImageLoaderConfig.Builder builder = new ImageLoaderConfig.Builder();
        ImageLoaderConfig config = builder.setThreadCount(3) // 執行緒數量
                .setLoadPolicy(new ReversePolicy())
                 // 根據是否對圖片壓縮處理,設定快取策略(保持基本條件一致)
                .setCachePolicy(ivCompressEnable? new DoubleCache(this) : null)
                 // 是否對圖片壓縮處理
                .setIvCompressEnable(ivCompressEnable)
                .setLoadingImage(R.drawable.loading)
                .setFaildImage(R.drawable.not_found)
                .build();
        //初始化
        imageLoader = SimpleImageLoader.getInstance(config);

        intGridView();
    }

    private void intGridView() {
        GridView listview = (GridView) findViewById(R.id.listview);
        listview.setAdapter(new MyAdapter(this));
    }

    private class MyAdapter extends BaseAdapter {
        private LayoutInflater inflater;
        private Context context;

        MyAdapter(Context context) {
            inflater = LayoutInflater.from(context);
            this.context = context;
        }
        @Override
        public int getCount() {
            return imageThumbUrls.length;
        }
        @Override
        public Object getItem(int position) {
            return imageThumbUrls[position];
        }
        @Override
        public long getItemId(int position) {
            return position;
        }
        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            View item = inflater.inflate(R.layout.item, null);
            ImageView imageView = (ImageView) item.findViewById(R.id.iv);

             /**
              * 直接載入本地大圖
              * 未壓縮-丟擲OOM異常:java.lang.OutOfMemoryError: Failed to allocate a 305209092 byte allocation with 12112306 free bytes and 174MB until OOM
              */
//            imageView.setImageResource(R.drawable.iv_big);

            /**
             * 自定義框架請求圖片
             *
             * ivCompressEnable:true
             * 壓縮-不會丟擲OOM異常:
             *
             * ivCompressEnable:false
             * 未壓縮-丟擲OOM異常:java.lang.OutOfMemoryError: Failed to allocate a 33912132 byte allocation with 13357108 free bytes and 12MB until OOM
             */
            imageLoader.displayImage(imageView, imageThumbUrls[position]);

            return item;
        }
    }

    public final static String[] imageThumbUrls = new String[]{
            "http://renyugang.io/wp-content/uploads/2018/06/485ad.jpg",
            "http://renyugang.io/wp-content/uploads/2018/06/485ad.jpg",
            "http://renyugang.io/wp-content/uploads/2018/06/485ad.jpg",
            "http://renyugang.io/wp-content/uploads/2018/06/485ad.jpg",
            "http://renyugang.io/wp-content/uploads/2018/06/485ad.jpg",
            "http://renyugang.io/wp-content/uploads/2018/06/485ad.jpg",
    };
}

(4)測試結果
①ivCompressEnable:false,丟擲OOM異常,截圖如下:
這裡寫圖片描述
②ivCompressEnable:true,不會丟擲OOM異常

2.3.2 對圖片壓縮處理

(1)解決思路解析
①第一次解析:將BitmapFactory.Options的inJustDecodeBounds設定為true,禁止為bitmap分配記憶體,返回null;
②經過解析,獲取到:圖片的長寬值和MIME型別;
③計算得到ImageView的寬高;
④options.inSampleSize:計算圖片縮放的比例值(圖片原始寬高/ImageView的寬高),取大值;
⑤options.inJustDecodeBounds屬性設定為false,為bitmap分配記憶體;
⑥第二次解析:得到壓縮後的圖片。
(2) 學習方法
ImageLoader原框架分析:Android高效載入大圖有效避免程式OOM
②本框架原始碼分析:BitmapDecoder、LocalLoader、ImageViewHelper類。
(3)原始碼

public Bitmap decodeSampledBitmapFromResource(Resources res,
            int resId, int reqWidth, int reqHeight) {
        // 第一次解析:首先使用inJustDecodeBounds=true,進行解碼以獲取圖片尺寸
        final BitmapFactory.Options options = new BitmapFactory.Options();
        // 只需要讀取圖片寬高資訊,無需將整張圖片載入到記憶體。返回值也不是一個Bitmap物件,而是null
        options.inJustDecodeBounds = true;
        // 從資源中加載出一個Bitmap物件(另外,decodeFile\decodeStream和decodeByteArray分別從檔案系統\輸入流\位元組陣列中載入Bitmap物件)
        BitmapFactory.decodeResource(res, resId, options);

        // 計算inSampleSize值
        options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);

        // 每個畫素2個位元組(預設值ARGB_8888改為RGB_565,節約一半記憶體)
        options.inPreferredConfig = Bitmap.Config.RGB_565;
        // 為bitmap分配記憶體---false
        options.inJustDecodeBounds = false;
        // 使用獲取到的inSampleSize值再次解析圖片
        // 第二次解析:經過第一次讀取現在圖片的大小,就可以決定是把整張圖片/壓縮後的圖片載入一個到記憶體中
        return BitmapFactory.decodeResource(res, resId, options);
    }
   /**
     * 根據控制元件的目標尺寸計算圖片取樣縮放比率
     *
     * @param options
     * @param reqWidth
     * @param reqHeight
     */
    private int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
        if (reqWidth == 0 || reqHeight == 0) {
            return 1;
        }

        //計算縮放的比例,圖片的原始寬高
        int width = options.outWidth;
        int height = options.outHeight;

        int inSampleSize = 1;
        //reqWidth/reqHeight---ImageView的寬/高
        if (width > reqWidth || height > reqHeight) {
            //寬高的縮放比例,圖片原始寬高/ImageView的寬高(四捨五入取整數)
            int heightRatio = Math.round((float) height / (float) reqHeight);
            int widthRatio = Math.round((float) width / (float) reqWidth);

            //取最大值:有的圖是長圖、有的是寬圖
            inSampleSize = Math.max(heightRatio, widthRatio);
        }

        //當inSampleSize為2,圖片的寬與高變成原來的1/2,(options.inSampleSize = 2)
        return inSampleSize;
    }
mImageView.setImageBitmap(decodeSampledBitmapFromResource(getResources(), R.id.image, 100, 100));

2.3.3 圖片壓縮面試題:圖片取樣壓縮,圖片是300*300,控制元件是100*100,inSampleSize值取多少?

(1)按“根據控制元件的目標尺寸計算圖片取樣縮放比率”此經典方法,計算得到的inSampleSize=3。
(2)不同資料夾對圖片最終記憶體大小有影響,以xh和xxh來說明一下,我們一般放入的圖是1:1.5,但是(分別在螢幕密度為2和3)顯示的圖片大小是1:1。這是因為螢幕密度為2的會從xh取圖,螢幕密度為3會從xxh取圖,這樣就可以保證我們圖的大小一致。

(3)實踐驗證
①螢幕密度是3倍的手機從”xxh”中取圖片,此圖片Bitmap的寬高壓縮了inSampleSize=3倍
這裡寫圖片描述
②螢幕密度是3倍的手機從”xh”中取圖片,此圖片Bitmap的寬高=原尺寸*1.5,適配螢幕
這裡寫圖片描述
③螢幕密度是2倍的手機從”xxh”中取圖片,此圖片Bitmap的寬高=原尺寸/1.5,保證圖的大小一致

(4)解答
①螢幕密度是3倍的手機從”xxh”中取圖片,inSampleSize=3
②螢幕密度是3倍的手機從”xh”中取圖片,inSampleSize=4.5
③螢幕密度是2倍的手機從”xh”中取圖片,inSampleSize=3
④螢幕密度是2倍的手機從”xxh”中取圖片,inSampleSize=2

2.3.4 載入大圖拒絕壓縮圖片,使用BitmapRegionDecoder區域性載入圖片

(1)實現思路
①不壓縮,按照原圖尺寸載入,那麼螢幕肯定是不夠大的,並且考慮到記憶體的情況,不可能一次性整圖載入到記憶體中,所以肯定是區域性載入,那麼就需要用到一個類:BitmapRegionDecoder,顯示圖片的某一塊矩形區域
②既然螢幕顯示不完,那麼最起碼要新增一個上下左右拖動的手勢,讓使用者可以拖動檢視。
(2)自定義顯示大圖控制元件原理
①提供一個設定圖片的入口;
②重寫onTouchEvent,在裡面根據使用者移動的手勢,去更新顯示區域的引數;
③每次更新區域引數後,呼叫invalidate(在主執行緒當中重新整理UI),onDraw裡面去regionDecoder.decodeRegion拿到bitmap,去draw。
(3)參考連結
Android 高清載入巨圖方案 拒絕壓縮圖片

2.3.5 圖片三級快取

3 基礎版ImageLoader框架

3.1 介紹

  花了2周的晚上和週末,學習了MrSimp1e老師的教你寫Android ImageLoader框架,能夠在一個稍微簡單但功能齊全的框架基礎上,深入瞭解實現的同時學到知識,也能夠體會到在設計一個開源庫時應該要做哪些考慮、做哪些取捨、有什麼模式,瞭解一些開源庫的設計基本原則。當然在瞭解了ImageLoader的實現之後再去使用專業的開源庫,例如:Glide,也會更加的得心應手,出現問題的時候自己也能夠不太費力地去究其原因。因此,經過分析後,我決定把此框架加入到我的CodeLibs公共庫中。

3.2 使用方法

(1)在 gradle 中引入:

compile 'com.guan.codelibs:imageloader:1.0.0'

(2)example

public class MainActivity extends AppCompatActivity {

    private SimpleImageLoader imageLoader;
    /**
     * 是否對圖片壓縮處理
     */
    private boolean ivCompressEnable = true;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.list);

        //配置
        ImageLoaderConfig.Builder builder = new ImageLoaderConfig.Builder();
        ImageLoaderConfig config = builder.setThreadCount(3) // 執行緒數量
                .setLoadPolicy(new ReversePolicy())
                 // 根據是否對圖片壓縮處理,設定快取策略(保持基本條件一致)
                .setCachePolicy(ivCompressEnable? new DoubleCache(this) : null)
                 // 是否對圖片壓縮處理
                .setIvCompressEnable(ivCompressEnable)
                .setLoadingImage(R.drawable.loading)
                .setFaildImage(R.drawable.not_found)
                .build();

        //初始化
        imageLoader = SimpleImageLoader.getInstance(config);

        intGridView();
    }


    /**
     * 初始化GridView
     */
    private void intGridView() {
        GridView listview = (GridView) findViewById(R.id.listview);
        listview.setAdapter(new MyAdapter(this));
    }

    private class MyAdapter extends BaseAdapter {

        private LayoutInflater inflater;
        private Context context;

        MyAdapter(Context context) {
            inflater = LayoutInflater.from(context);
            this.context = context;
        }

        @Override
        public int getCount() {
            return imageThumbUrls.length;
        }

        @Override
        public Object getItem(int position) {
            return imageThumbUrls[position];
        }

        @Override
        public long getItemId(int position) {
            return position;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            View item = inflater.inflate(R.layout.item, null);
            ImageView imageView = (ImageView) item.findViewById(R.id.iv);

            /**
             * 載入大圖-對比未壓縮和壓縮的結果
             */
             /**
              * 直接載入本地大圖
              * 未壓縮-丟擲OOM異常:java.lang.OutOfMemoryError: Failed to allocate a 305209092 byte allocation with 12112306 free bytes and 174MB until OOM
              */
//            imageView.setImageResource(R.drawable.iv_big);

            /**
             * 自定義框架請求圖片
             *
             * ivCompressEnable:true
             * 壓縮-不會丟擲OOM異常:
             *
             * ivCompressEnable:false
             * 未壓縮-丟擲OOM異常:java.lang.OutOfMemoryError: Failed to allocate a 33912132 byte allocation with 13357108 free bytes and 12MB until OOM
             *
             */
            imageLoader.displayImage(imageView, imageThumbUrls[position]);

            /**
             * Glide框架請求
             */
//            Glide.with(context)
//                    .load(imageThumbUrls[position])
//                    .override(0,9)
//                    .centerCrop()
//                    .fitCenter()
//                    .into(imageView);
            return item;
        }
    }

    public final static String[] imageThumbUrls = new String[]{
            "http://img.my.csdn.net/uploads/201407/26/1406383290_1042.jpg",
            "http://img.my.csdn.net/uploads/201407/26/1406383275_3977.jpg",
            "http://img.my.csdn.net/uploads/201407/26/1406383265_8550.jpg",
            "http://img.my.csdn.net/uploads/201407/26/1406383264_3954.jpg",
            "http://img.my.csdn.net/uploads/201407/26/1406383264_4787.jpg",
            "http://img.my.csdn.net/uploads/201407/26/1406383264_8243.jpg",
            "http://img.my.csdn.net/uploads/201407/26/1406383248_3693.jpg",
            "http://img.my.csdn.net/uploads/201407/26/1406383243_5120.jpg",
            "http://img.my.csdn.net/uploads/201407/26/1406383242_3127.jpg",
            "http://img.my.csdn.net/uploads/201407/26/1406383242_9576.jpg",
            "http://img.my.csdn.net/uploads/201407/26/1406383242_1721.jpg",
            "http://img.my.csdn.net/uploads/201407/26/1406383219_5806.jpg",
            "http://img.my.csdn.net/uploads/201407/26/1406383214_7794.jpg",
            "http://img.my.csdn.net/uploads/201407/26/1406383213_4418.jpg",
            "http://img.my.csdn.net/uploads/201407/26/1406383213_3557.jpg",
            "http://img.my.csdn.net/uploads/201407/26/1406383210_8779.jpg",
            "http://img.my.csdn.net/uploads/201407/26/1406383172_4577.jpg",
            "http://img.my.csdn.net/uploads/201407/26/1406383166_3407.jpg",
            "http://img.my.csdn.net/uploads/201407/26/1406383166_2224.jpg",
            "http://img.my.csdn.net/uploads/201407/26/1406383166_7301.jpg",
            "http://img.my.csdn.net/uploads/201407/26/1406383165_7197.jpg",
            "http://img.my.csdn.net/uploads/201407/26/1406383150_8410.jpg",
            "http://img.my.csdn.net/uploads/201407/26/1406383131_3736.jpg",
            "http://img.my.csdn.net/uploads/201407/26/1406383130_5094.jpg",
            "http://img.my.csdn.net/uploads/201407/26/1406383130_7393.jpg",
            "http://img.my.csdn.net/uploads/201407/26/1406383129_8813.jpg",
            "http://img.my.csdn.net/uploads/201407/26/1406383100_3554.jpg",
            "http://img.my.csdn.net/uploads/201407/26/1406383093_7894.jpg",
            "http://img.my.csdn.net/uploads/201407/26/1406383092_2432.jpg",
            "http://img.my.csdn.net/uploads/201407/26/1406383092_3071.jpg",
            "http://img.my.csdn.net/uploads/201407/26/1406383091_3119.jpg",
            "http://img.my.csdn.net/uploads/201407/26/1406383059_6589.jpg",
            "http://img.my.csdn.net/uploads/201407/26/1406383059_8814.jpg",
            "http://img.my.csdn.net/uploads/201407/26/1406383059_2237.jpg",
            "http://img.my.csdn.net/uploads/201407/26/1406383058_4330.jpg",
            "http://img.my.csdn.net/uploads/201407/26/1406383038_3602.jpg",
            "http://img.my.csdn.net/uploads/201407/26/1406382942_3079.jpg",
            "http://img.my.csdn.net/uploads/201407/26/1406382942_8125.jpg",
            "http://img.my.csdn.net/uploads/201407/26/1406382942_4881.jpg",
            "http://img.my.csdn.net/uploads/201407/26/1406382941_4559.jpg",
            "http://img.my.csdn.net/uploads/201407/26/1406382941_3845.jpg",
            "http://img.my.csdn.net/uploads/201407/26/1406382924_8955.jpg",
            "http://img.my.csdn.net/uploads/201407/26/1406382923_2141.jpg",
            "http://img.my.csdn.net/uploads/201407/26/1406382923_8437.jpg",
            "http://img.my.csdn.net/uploads/201407/26/1406382922_6166.jpg",
            "http://img.my.csdn.net/uploads/201407/26/1406382922_4843.jpg",
            "http://img.my.csdn.net/uploads/201407/26/1406382905_5804.jpg",
            "http://img.my.csdn.net/uploads/201407/26/1406382904_3362.jpg",
            "http://img.my.csdn.net/uploads/201407/26/1406382904_2312.jpg",
            "http://img.my.csdn.net/uploads/201407/26/1406382904_4960.jpg",
            "http://img.my.csdn.net/uploads/201407/26/1406382900_2418.jpg",
            "http://img.my.csdn.net/uploads/201407/26/1406382881_4490.jpg",
            "http://img.my.csdn.net/uploads/201407/26/1406382881_5935.jpg",
            "http://img.my.csdn.net/uploads/201407/26/1406382880_3865.jpg",
            "http://img.my.csdn.net/uploads/201407/26/1406382880_4662.jpg",
            "http://img.my.csdn.net/uploads/201407/26/1406382879_2553.jpg",
            "http://img.my.csdn.net/uploads/201407/26/1406382862_5375.jpg",
            "http://img.my.csdn.net/uploads/201407/26/1406382862_1748.jpg",
            "http://img.my.csdn.net/uploads/201407/26/1406382861_7618.jpg",
            "http://img.my.csdn.net/uploads/201407/26/1406382861_8606.jpg",
            "http://img.my.csdn.net/uploads/201407/26/1406382861_8949.jpg",
            "http://img.my.csdn.net/uploads/201407/26/1406382841_9821.jpg",
            "http://img.my.csdn.net/uploads/201407/26/1406382840_6603.jpg",
            "http://img.my.csdn.net/uploads/201407/26/1406382840_2405.jpg",
            "http://img.my.csdn.net/uploads/201407/26/1406382840_6354.jpg",
            "http://img.my.csdn.net/uploads/201407/26/1406382839_5779.jpg",
            "http://img.my.csdn.net/uploads/201407/26/1406382810_7578.jpg",
            "http://img.my.csdn.net/uploads/201407/26/1406382810_2436.jpg",
            "http://img.my.csdn.net/uploads/201407/26/1406382809_3883.jpg",
            "http://img.my.csdn.net/uploads/201407/26/1406382809_6269.jpg",
            "http://img.my.csdn.net/uploads/201407/26/1406382808_4179.jpg",
            "http://img.my.csdn.net/uploads/201407/26/1406382790_8326.jpg",
            "http://img.my.csdn.net/uploads/201407/26/1406382789_7174.jpg",
            "http://img.my.csdn.net/uploads/201407/26/1406382789_5170.jpg",
            "http://img.my.csdn.net/uploads/201407/26/1406382789_4118.jpg",
            "http://img.my.csdn.net/uploads/201407/26/1406382788_9532.jpg",
            "http://img.my.csdn.net/uploads/201407/26/1406382767_3184.jpg",
            "http://img.my.csdn.net/uploads/201407/26/1406382767_4772.jpg",
            "http://img.my.csdn.net/uploads/201407/26/1406382766_4924.jpg",
            "http://img.my.csdn.net/uploads/201407/26/1406382766_5762.jpg",
            "http://img.my.csdn.net/uploads/201407/26/1406382765_7341.jpg"
    };
}

(3)效果圖
這裡寫圖片描述

3.3 原始碼地址

3.4 分析

3.5 使用到的知識點

  • 三級快取分別為記憶體, 硬碟, 網路
  • 記憶體快取 LruCache技術
  • 硬碟快取技術DiskLruCache技術
  • 圖片下載時請求轉發
  • 生產者–消費者模式
  • 建造者模式
  • 單例模式
  • 模板方法模式
  • 策略模式—LoadMannger–UrlLoader\LocalLoader\NullLoader