Glide圖片原理解析
1.Glide是什麼?
Glide是Google在2014的IO大會發布一款圖片處理框架,是目前android領域比較成熟的一款,也是Google官方推薦的圖片處理框架,主要支援網路圖片、二進位制流、drawable資源、本地圖片顯示,還支援本地視訊顯示。
2.Glide基本功能使用
- 在app級別下面配置gradle
dependencies { compile 'com.github.bumptech.glide:glide:3.7.0' }
-
使用起來
通過Glide類進行一個鏈式呼叫,下面程式碼顯示了一張網路圖片。
-
with支援傳入一下物件,我們在使用過程中儘量要傳入activity、fragment因為glide會依賴它們的生命週期,如果onPaush時候,Glide就會暫停載入,重新onResume之後,又會繼續載入。
image
-
load() 支援網路圖片、二進位制流、drawable資源、本地圖片的傳入。
-
crossFade 這是開啟顯示淡入淡出的動畫
-
override 如果獲取的網路圖片過大,我們通過它進行一個大小的裁剪,傳入width和height引數進行寬高裁剪。
-
diskCacheStrategy 磁碟快取的設定,預設Glide會開啟的。
DiskCacheStrategy.NONE 什麼都不快取
DiskCacheStrategy.SOURCE 只快取全尺寸圖
DiskCacheStrategy.RESULT 只快取最終的載入圖
DiskCacheStrategy.ALL 快取所有版本圖(預設行為)
Glide 不僅快取了全尺寸的圖,還會根據 ImageView 大小所生成的圖也會快取起來。比如,請求一個 800x600 的圖載入到一個 400x300 的 ImageView 中,Glide預設會將這原圖還有載入到 ImageView 中的 400x300 的圖也會快取起來。
- error 這裡的設定是當載入圖片出現錯誤時,顯示的圖片。
- placeholder 圖片載入完成之前顯示的佔位圖。
String url = "https://ss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=2912495429,3557331556&fm=27&gp=0.jpg"; Glide .with(this) .load(url) .crossFade() .override(200, 200) .diskCacheStrategy(DiskCacheStrategy.ALL) .error(R.mipmap.active_user_default_icon) .placeholder(R.mipmap.ic_album_image_source_pick) .into(img);
獲取Glide請求完成的Bitmap
通過前面這種方式我們能很輕鬆的顯示一張,圖片到ImgeView上。但是我們有時候想獲取載入完成後的Bitmap怎麼辦呢,可以通過SimpleTarget來獲取。
//SimpleTarget 獲取bitmap,定義siez SimpleTarget<Bitmap> mSimpleTarget = new SimpleTarget<Bitmap>(200, 200) { @Override public void onResourceReady(Bitmap resource, GlideAnimation<? super Bitmap> glideAnimation) { img.setImageBitmap(resource); } }; //獲取Bitmap的載入方法。 Glide.with(getApplicationContext()) .load(url) .asBitmap() .into(mSimpleTarget);
我們定義了一個SimpleTarget類,裡面有一個onResourceReady方法的回撥,會返回載入完成的Bitmap物件,into傳入這個SimpleTarget物件。
自定義控制元件使用Glide顯示Image
有時候我們自定義的控制元件也需要顯示Imaeg,但是這個控制元件不是Image類或者子類,那麼前面這種方法就會不使用了,我們可以通過ViewTarget來實現這個邏輯。
public class MyImageView extends FrameLayout { private ImageView mImageView; public MyImageView(@NonNull Context context) { super(context); } public MyImageView(@NonNull Context context, @Nullable AttributeSet attrs) { super(context, attrs); } public MyImageView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override protected void onFinishInflate() { super.onFinishInflate(); mImageView = new ImageView(getContext()); addView(mImageView, LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); } public void setImage(Drawable drawable) { mImageView.setImageDrawable(drawable); } }
MyImageView img3; //ViewTarget自定義View也支援獲取Bitmap ViewTarget viewTarget = new ViewTarget<MyImageView, GlideDrawable>(img3) { @Override public void onResourceReady(GlideDrawable resource, GlideAnimation<? super GlideDrawable> glideAnimation) { img3.setImage(resource); } }; Glide.with(getApplicationContext()) .load(url) .into(viewTarget);
通過定義一個ViewTarget傳入了二個泛型類,一個是MyImageView,一個是GlideDrawable,傳入了img3。通過onResourceReady的回撥我們可以對自定義控制元件進行圖片顯示了。
3.Glide原理了解?
Glide的with、load、into雖然只有三個方法呼叫但是內部的邏輯是很複雜的,成噸的程式碼這裡我只做一個大概瞭解。首先with方法會構造一個單例的RequestManager物件。
with方法
public static RequestManager with(FragmentActivity activity) { RequestManagerRetriever retriever = RequestManagerRetriever.get(); return retriever.get(activity); }
public static RequestManagerRetriever get() { return INSTANCE; }
可以看出來我們一個程式生命週期內只有一個RequestManager物件,工程師在設計的時候很考慮到效能的問題。
load方法
public DrawableTypeRequest<String> load(String string) { return (DrawableTypeRequest<String>) fromString().load(string); }
接著通過RequestManage物件的load方法返回一個DrawableTypeRequest類,它是一個DrawableRequestBuilder的一個子類,通過類名可以看出來主要作用是主要是構建請求中的一些引數用的,比如我們之前寫的 override、error、placeholder、diskCacheStrategy這些輔助方法的設定。
into方法
最後的一個方法也是最複雜的一個,是真正請求網路請求的地方。
public Target<GlideDrawable> into(ImageView view) { return super.into(view); }
public Target<TranscodeType> into(ImageView view) { Util.assertMainThread(); ... return into(glide.buildImageViewTarget(view, transcodeClass)); }
@SuppressWarnings("unchecked") public <Z> Target<Z> buildTarget(ImageView view, Class<Z> clazz) { if (GlideDrawable.class.isAssignableFrom(clazz)) { return (Target<Z>) new GlideDrawableImageViewTarget(view); } else if (Bitmap.class.equals(clazz)) { return (Target<Z>) new BitmapImageViewTarget(view); } else if (Drawable.class.isAssignableFrom(clazz)) { return (Target<Z>) new DrawableImageViewTarget(view); } else { throw new IllegalArgumentException("Unhandled class: " + clazz + ", try .as*(Class).transcode(ResourceTranscoder)"); } }
into接收一個ViewTeger,
into的呼叫
public <Y extends Target<TranscodeType>> Y into(Y target) { ... Request request = buildRequest(target); target.setRequest(request); lifecycle.addListener(target); requestTracker.runRequest(request); return target; }
public void runRequest(Request request) { requests.add(request); if (!isPaused) { request.begin(); } else { pendingRequests.add(request); } }
runRequest方法來控制請求的佇列,如果當前檢視狀態是Paused狀態就會把request新增到等待請求的佇列,如果不是則直接執行。裡面會有一個HttpUrlFetcher來執行前面封裝好的網路請求
4.Glide是如何快取的?
glide快取主要分為
- 記憶體快取
記憶體快取主要是防止同樣的圖片重複讀取到記憶體中來,解約JVM的記憶體空間
glide的記憶體快取機制是使用LruCache演算法實現,首先需要會需要一個key,這個key是通過url+構建請求時候的引數決定的。keyFactory.buildKey的程式碼就是生成Key的邏輯。glide記憶體快取會呼叫二個方法來獲取快取。
-
loadFromCache
使用LruCache
-
loadFromActiveResources
使用弱引用
記憶體快取的邏輯就是,先會在loadFromCache中去取快取,這個是一個LruCaChe算啊實現的,如果取到了值就會把它存入一個弱引用中。這樣的防止被LruCache演算法回收掉,如果LruCache沒有取到就會去loadFromActiveResources方法裡面取。
如果都沒有那就會開啟子執行緒去網路請求圖片。
public <T, Z, R> LoadStatus load(Key signature, int width, int height, DataFetcher<T> fetcher, DataLoadProvider<T, Z> loadProvider, Transformation<Z> transformation, ResourceTranscoder<Z, R> transcoder, Priority priority, boolean isMemoryCacheable, DiskCacheStrategy diskCacheStrategy, ResourceCallback cb) { Util.assertMainThread(); long startTime = LogTime.getLogTime(); final String id = fetcher.getId(); EngineKey key = keyFactory.buildKey(id, signature, width, height, loadProvider.getCacheDecoder(), loadProvider.getSourceDecoder(), transformation, loadProvider.getEncoder(), transcoder, loadProvider.getSourceEncoder()); EngineResource<?> cached = loadFromCache(key, isMemoryCacheable); if (cached != null) { cb.onResourceReady(cached); if (Log.isLoggable(TAG, Log.VERBOSE)) { logWithTimeAndKey("Loaded resource from cache", startTime, key); } return null; } EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable); if (active != null) { cb.onResourceReady(active); if (Log.isLoggable(TAG, Log.VERBOSE)) { logWithTimeAndKey("Loaded resource from active resources", startTime, key); } return null; } EngineJob current = jobs.get(key); if (current != null) { current.addCallback(cb); if (Log.isLoggable(TAG, Log.VERBOSE)) { logWithTimeAndKey("Added to existing load", startTime, key); } return new LoadStatus(cb, current); } EngineJob engineJob = engineJobFactory.build(key, isMemoryCacheable); DecodeJob<T, Z, R> decodeJob = new DecodeJob<T, Z, R>(key, width, height, fetcher, loadProvider, transformation, transcoder, diskCacheProvider, diskCacheStrategy, priority); EngineRunnable runnable = new EngineRunnable(engineJob, decodeJob, priority); jobs.put(key, engineJob); engineJob.addCallback(cb); engineJob.start(runnable); if (Log.isLoggable(TG, Log.VERBOSE)) { logWithTimeAndKey("Started new load", startTime, key); } return new LoadStatus(cb, engineJob); }
- 磁碟快取
磁碟快取主要是防止同一張網路圖片,重複從網路中讀取和下載,磁碟快取也是使用LruCache演算法實現的。
磁碟快取有一個邏輯,當需要去載入一張圖片的時候,Glide預設不會顯示原始圖片,而是會對圖片進行壓縮轉換。經過這些轉換操作之後才會把,圖片顯示出來,磁碟快取預設就是快取轉換後的圖片。
Glide
Glide的快取策略是如果載入是一張1000 * 1000大小的圖片,但是最終顯示到ImageView上的只有500 * 500,Glide只會快取最終顯示的那張500*500的圖片。
Glide的四種快取策略
- DiskCacheStrategy.NONE 不快取檔案
- DiskCacheStrategy.SOURCE 只快取原圖
- DiskCacheStrategy.RESULT 只快取最終載入的圖(預設的快取策略)
- DiskCacheStrategy.ALL 同時快取原圖和結果圖
配置快取策略
通過實現GlideModel介面來定義一個自定義的快取策略類
public class DiskCachMoudle implements GlideModule { @Override public void applyOptions(Context context, GlideBuilder builder) { } @Override public void registerComponents(Context context, Glide glide) { } }
<application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".test.Activity2"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <meta-data android:name="com.jin.mak.DiskCachMoudle" android:value="DiskCachMoudle" /> </application>
Glide原始碼裡面的get方法會自動讀取我們在ManifestParert中配置的那個DiskCachMoudle配置。
public static Glide get(Context context) { if (glide == null) { synchronized (Glide.class) { if (glide == null) { Context applicationContext = context.getApplicationContext(); //第一步 List<GlideModule> modules = new ManifestParser(applicationContext).parse(); //第二步 GlideBuilder builder = new GlideBuilder(applicationContext); for (GlideModule module : modules) { //在builder構造出glide之前,讀取使用者自定義的配置. module.applyOptions(applicationContext, builder); } glide = builder.createGlide(); //第三步 for (GlideModule module : modules) { module.registerComponents(applicationContext, glide); } } } } return glide; }
applyOptions 方法
-
builder.setBitmapPool ()
這個是用來設定BitMap快取池的,需要實現一個BitmapPool介面,它的預設實現是LruBitmapPool類。
-
builder. setMemoryCache()
設定記憶體快取,需要實現MemoryCache介面,它的預設實現是LruResourceCache類。
-
setResizeService(ExecutorService service)
當資源不在快取中時,需要通過這個Executor發起請求,預設是實現是FifoPriorityThreadPoolExecutor。
-
setDiskCacheService(ExecutorService service)
讀取磁碟快取的服務,預設實現是FifoPriorityThreadPoolExecutor。
-
setDecodeFormat(DecodeFormat decodeFormat)
用於控制Bitmap解碼的清晰度,DecodeFormat可選的值有PREFER_ARGB_8888/PREFER_RGB_565,預設為PREFER_RGB_565。