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生命週期一致
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