1.什麼是Glide?android
Glide是一款由Bump Technologies開發的圖片加載框架,使得咱們能夠在Android平臺上以極度簡單的方式加載和展現圖片。 git
2.依賴,網絡權限:github
compile 'com.github.bumptech.glide:glide:3.7.0' //3.7的依賴canvas
<uses-permission android:name="android.permission.INTERNET" /> 網絡權限緩存
3.基本用法網絡
Glide.with(this).load(url).into(imageView);app
①.Glide在Activity,Fragment,Adapter中使用with參數的區別?框架
with()方法中傳入的實例會決定Glide加載圖片的生命週期,若是傳入的是Activity或者Fragment的實例,那麼當這個Activity或Fragment被銷燬的時候,圖片加載也會中止。若是傳入的是ApplicationContext,那麼只有當應用程序被殺掉的時候,圖片加載纔會中止。 ide
②. load()方法源碼分析
這個方法用於指定待加載的圖片資源。Glide支持加載各類各樣的圖片資源,包括網絡圖片、本地圖片、應用資源、二進制流、Uri對象等等。所以load()方法也有不少個方法重載
//加載本地圖片:
File file = new File(getExternalCacheDir()+"/image.jpg");
Glide.with(this).load(file).into(imageView);
// 加載應用資源
int resource = R.drawable.image;
Glide.with(this).load(resource).into(imageView);
// 加載二進制流
byte[] image = getImageBytes();
Glide.with(this).load(image).into(imageView);
// 加載Uri對象
Uri imageUri = getImageUri();
Glide.with(this).load(imageUri).into(imageView);
③.into()方法
into()方法不只僅是隻能接收ImageView類型的參數
4.使用
Glide.with(this) .load(url)//加載地址 // .asGif()//只容許加載動態圖片,若是加載靜態圖片會加載失敗 .asBitmap()//只容許加載靜態圖片 .placeholder(R.mipmap.ic_launcher)//佔位圖 .error(R.mipmap.ic_launcher)//異常佔位圖 .diskCacheStrategy(DiskCacheStrategy.NONE)//禁用掉Glide的緩存功能。 .override(100, 100)//指定圖片大小 .into(imageView);
5.源碼分析
在沒閱讀Glide源碼以前,咱們帶着下面幾個問題去閱讀源碼,但願在閱讀源碼的過程當中能夠解決:
with()方法
傳入Application參數的狀況:
若是在Glide.with()方法中傳入的是一個Application對象,那麼這裏就會調用帶有Context參數的get()方法重載,而後會在調用getApplicationManager()方法來獲取一個RequestManager對象。其實這是最簡單的一種狀況,由於Application對象的生命週期即應用程序的生命週期,所以Glide並不須要作什麼特殊的處理,它自動就是和應用程序的生命週期是同步的,若是應用程序關閉的話,Glide的加載也會同時終止
傳入非Application參數的狀況:
無論你在Glide.with()方法中傳入的是Activity、FragmentActivity、v4包下的Fragment、仍是app包下的Fragment,最終的流程都是同樣的,那就是會向當前的Activity當中添加一個隱藏的Fragment。具體添加的邏輯是在上述代碼的第117行和第141行,分別對應的app包和v4包下的兩種Fragment的狀況。那麼這裏爲何要添加一個隱藏的Fragment呢?由於Glide須要知道加載的生命週期。很簡單的一個道理,若是你在某個Activity上正在加載着一張圖片,結果圖片還沒加載出來,Activity就被用戶關掉了,那麼圖片還應該繼續加載嗎?固然不該該。但是Glide並無辦法知道Activity的生命週期,因而Glide就使用了添加隱藏Fragment的這種小技巧,由於Fragment的生命週期和Activity是同步的,若是Activity被銷燬了,Fragment是能夠監聽到的,這樣Glide就能夠捕獲這個事件並中止圖片加載了。
若是咱們是在非主線程當中使用的Glide,那麼無論你是傳入的Activity仍是Fragment,都會被強制當成Application來處理。
總結:with()方法其實就是爲了獲得一個RequestManager對象而已,而後Glide會根據咱們傳入with()方法的參數來肯定圖片加載的生命週期
load()方法:
調用fromString()方法,再調用load()方法,而後把傳入的圖片URL地址傳進去。而fromString()方法也極爲簡單,就是調用了loadGeneric()方法,而且指定參數爲String.class,由於load()方法傳入的是一個字符串參數,因能夠看出大多數的操做是在fromString()中的loadGeneric()方法中進行的
loadGeneric()調用了Glide.buildStreamModelLoader()和Glide.buildFileDescriptorModelLoader()方法來得到ModelLoader對象。ModelLoader對象是用於加載圖片的,而咱們給load()方法傳入不一樣類型的參數,這裏也會獲得不一樣的ModelLoader對象,因爲咱們剛纔傳入的參數是String.class,所以最終獲得的是StreamStringLoader對象,它是實現了ModelLoader接口的。
loadGeneric()方法是要返回一個DrawableTypeRequest對象的,所以在loadGeneric()方法的最後又去new了一個DrawableTypeRequest對象,而後把剛纔得到的ModelLoader對象,還有一大堆雜七雜八的東西都傳了進去
DrawableTypeRequest
主要的就是它提供了asBitmap()和asGif()這兩個方法 用於強制指定加載靜態圖片和動態圖片
而從源碼中能夠看出,它們分別又建立了一個BitmapTypeRequest和GifTypeRequest,若是沒有進行強制指定的話,那默認就是使用DrawableTypeRequest。
DrawableTypeRequest 裏面沒有load方法,於是它是父類裏面的方法
into()方法:
Glide緩存分爲倆種:
①.內存緩存
內存緩存的主要做用是防止應用重複將圖片數據讀取到內存當中
②.磁盤緩存
硬盤緩存的主要做用是防止應用重複從網絡或其餘地方重複下載和讀取數據
緩存key:
Engine類裏面的load()方法中的 fetcher.getId()方法得到了一個id字符串,這個字符串也就是咱們要加載的圖片的惟一標識,好比說若是是一張網絡上的圖片的話,那麼這個id就是這張圖片的url地址
下一行,將這個id連同着signature、width、height等等10個參數一塊兒傳入到EngineKeyFactory的buildKey()方法當中,從而構建出了一個EngineKey對象,這個EngineKey也就是Glide中的緩存Key了。
可見,決定緩存Key的條件很是多,即便你用override()方法改變了一下圖片的width或者height,也會生成一個徹底不一樣的緩存Key。
有了緩存Key,接下來就能夠開始進行緩存了,那麼咱們先從內存緩存看起。
首先你要知道,默認狀況下,Glide自動就是開啓內存緩存的。也就是說,當咱們使用Glide加載了一張圖片以後,這張圖片就會被緩存到內存當中,只要在它還沒從內存中被清除以前,下次使用Glide再加載這張圖片都會直接從內存當中讀取,而不用從新從網絡或硬盤上讀取了,這樣無疑就能夠大幅度提高圖片的加載效率。比方說你在一個RecyclerView當中反覆上下滑動,RecyclerView中只要是Glide加載過的圖片均可以直接從內存當中迅速讀取並展現出來,從而大大提高了用戶體驗。
而Glide最爲人性化的是,你甚至不須要編寫任何額外的代碼就能自動享受到這個極爲便利的內存緩存功能,由於Glide默認就已經將它開啓了。
禁用緩存功能:
Glide.with(this) .load(url) .skipMemoryCache(true) .into(imageView);
Glide.with(this) .load(url) .diskCacheStrategy(DiskCacheStrategy.NONE) .into(imageView);
調用diskCacheStrategy()方法並傳入DiskCacheStrategy.NONE,就能夠禁用掉Glide的硬盤緩存功能了。
這個diskCacheStrategy()方法基本上就是Glide硬盤緩存功能的一切,它能夠接收四種參數:
實現圖片預加載 preload():
Glide.with(this) .load(url) .diskCacheStrategy(DiskCacheStrategy.SOURCE) .preload();
若是使用了preload()方法,最好要將diskCacheStrategy的緩存策略指定成DiskCacheStrategy.SOURCE。由於preload()方法默認是預加載的原始圖片大小,而into()方法則默認會根據ImageView控件的大小來動態決定加載圖片的大小。所以,若是不將diskCacheStrategy的緩存策略指定成DiskCacheStrategy.SOURCE的話,很容易會形成咱們在預加載完成以後再使用into()方法加載圖片,卻仍然仍是要從網絡上去請求圖片這種現象。
PreloadTarget的源碼很是簡單,obtain()方法中就是new了一個PreloadTarget的實例而已,而onResourceReady()方法中也沒作什麼事情,只是調用了Glide.clear()方法。
這裏的Glide.clear()並非清空緩存的意思,而是表示加載已完成,釋放資源的意思,所以不用在這裏產生疑惑。
訪問圖片的緩存文件 downloadOnly()方法是定義在DrawableTypeRequest類:
有兩個方法重載,一個接收圖片的寬度和高度,另外一個接收一個泛型對象
這兩個方法各自有各自的應用場景,其中downloadOnly(int width, int height)是用於在子線程中下載圖片的,而downloadOnly(Y target)是用於在主線程中下載圖片的。
downloadOnly(int width, int height)的用法。當調用了downloadOnly(int width, int height)方法後會當即返回一個FutureTarget對象,而後Glide會在後臺開始下載圖片文件。接下來咱們調用FutureTarget的get()方法就能夠去獲取下載好的圖片文件了,若是此時圖片尚未下載完,那麼get()方法就會阻塞住,一直等到圖片下載完成纔會有值返回。
必須將硬盤緩存策略指定成DiskCacheStrategy.SOURCE或者DiskCacheStrategy.ALL,不然Glide將沒法使用咱們剛纔下載好的圖片緩存文件。
String url = "http://172.16.54.8:8080/test/zy.jpg"; Glide.with(this) .load(url) .listener(new RequestListener<String, GlideDrawable>() { @Override public boolean onException(Exception e, String model, Target<GlideDrawable> target, boolean isFirstResource) { return false; } @Override public boolean onResourceReady(GlideDrawable resource, String model, Target<GlideDrawable> target, boolean isFromMemoryCache, boolean isFirstResource) { return false; } }) .into(simpleTarget);
onResourceReady()方法和onException()方法都有一個布爾值的返回值,返回false就表示這個事件沒有被處理,還會繼續向下傳遞,返回true就表示這個事件已經被處理掉了,從而不會再繼續向下傳遞。舉個簡單點的例子,若是咱們在RequestListener的onResourceReady()方法中返回了true,那麼就不會再回調Target的onResourceReady()方法了。
requestListener的onResourceReady()方法,只有當這個onResourceReady()方法返回false的時候,纔會繼續調用Target的onResourceReady()方法,這也就是listener()方法的實現原理
requestListener的onException()方法,只有在onException()方法返回false的狀況下才會繼續調用setErrorPlaceholder()方法。也就是說,若是咱們在onException()方法中返回了true,那麼Glide請求中使用error(int resourceId)方法設置的異常佔位圖就失效了。
dontTransform()方法:表示讓Glide在加載圖片的過程當中不進行圖片變換
Glide.with(this) .load(url) .dontTransform() .into(imageView);
使用dontTransform()方法存在着一個問題,就是調用這個方法以後,全部的圖片變換操做就所有失效了,那若是我有一些圖片變換操做是必需要執行的該怎麼辦呢?不用擔憂,總歸是有辦法的,這種狀況下咱們只須要藉助override()方法強制將圖片尺寸指定成原始大小就能夠了,代碼以下所示:
Glide.with(this) .load(url) .override(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL) .into(imageView);
經過override()方法將圖片的寬和高都指定成Target.SIZE_ORIGINAL,問題一樣被解決了
添加圖片變換的用法很是簡單,咱們只須要調用transform()方法,並將想要執行的圖片變換操做做爲參數傳入transform()方法便可,以下所示:
Glide.with(this) .load(url) .transform(...) .into(imageView);
至於具體要進行什麼樣的圖片變換操做,這個一般都是須要咱們本身來寫的。不過Glide已經內置了兩種圖片變換操做,咱們能夠直接拿來使用,一個是CenterCrop,一個是FitCenter。
但這兩種內置的圖片變換操做其實都不須要使用transform()方法,Glide爲了方便咱們使用直接提供了現成的API:
Glide.with(this) .load(url) .centerCrop() .into(imageView); Glide.with(this) .load(url) .fitCenter() .into(imageView);
public class CenterCrop extends BitmapTransformation { public CenterCrop(Context context) { super(context); } public CenterCrop(BitmapPool bitmapPool) { super(bitmapPool); } // Bitmap doesn't implement equals, so == and .equals are equivalent here. @SuppressWarnings("PMD.CompareObjectsWithEquals") @Override protected Bitmap transform(BitmapPool pool, Bitmap toTransform, int outWidth, int outHeight) { final Bitmap toReuse = pool.get(outWidth, outHeight, toTransform.getConfig() != null ? toTransform.getConfig() : Bitmap.Config.ARGB_8888); Bitmap transformed = TransformationUtils.centerCrop(toReuse, toTransform, outWidth, outHeight); if (toReuse != null && toReuse != transformed && !pool.put(toReuse)) { toReuse.recycle(); } return transformed; } @Override public String getId() { return "CenterCrop.com.bumptech.glide.load.resource.bitmap"; } }
CenterCrop是繼承自BitmapTransformation的,這個是重中之重,由於整個圖片變換功能都是創建在這個繼承結構基礎上的。
接下來CenterCrop中最重要的就是transform()方法,其餘的方法咱們能夠暫時忽略。transform()方法中有四個參數,每個都很重要,咱們來一一解讀下。第一個參數pool,這個是Glide中的一個Bitmap緩存池,用於對Bitmap對象進行重用,不然每次圖片變換都從新建立Bitmap對象將會很是消耗內存。第二個參數toTransform,這個是原始圖片的Bitmap對象,咱們就是要對它來進行圖片變換。第三和第四個參數比較簡單,分別表明圖片變換後的寬度和高度,其實也就是override()方法中傳入的寬和高的值了
transform()方法的細節,首先第一行就從Bitmap緩存池中嘗試獲取一個可重用的Bitmap對象,而後把這個對象連同toTransform、outWidth、outHeight參數一塊兒傳入到了TransformationUtils.centerCrop()方法當中
public final class TransformationUtils { public static Bitmap centerCrop(Bitmap recycled, Bitmap toCrop, int width, int height) { if (toCrop == null) { return null; } else if (toCrop.getWidth() == width && toCrop.getHeight() == height) { return toCrop; } // From ImageView/Bitmap.createScaledBitmap. final float scale; float dx = 0, dy = 0; Matrix m = new Matrix(); if (toCrop.getWidth() * height > width * toCrop.getHeight()) { scale = (float) height / (float) toCrop.getHeight(); dx = (width - toCrop.getWidth() * scale) * 0.5f; } else { scale = (float) width / (float) toCrop.getWidth(); dy = (height - toCrop.getHeight() * scale) * 0.5f; } m.setScale(scale, scale); m.postTranslate((int) (dx + 0.5f), (int) (dy + 0.5f)); final Bitmap result; if (recycled != null) { result = recycled; } else { result = Bitmap.createBitmap(width, height, getSafeConfig(toCrop)); } // We don't add or remove alpha, so keep the alpha setting of the Bitmap we were given. TransformationUtils.setAlpha(toCrop, result); Canvas canvas = new Canvas(result); Paint paint = new Paint(PAINT_FLAGS); canvas.drawBitmap(toCrop, m, paint); return result; } }
這段代碼就是整個圖片變換功能的核心代碼了,先作了一些校驗,若是原圖爲空,或者原圖的尺寸和目標裁剪尺寸相同,那麼就放棄裁剪,經過數學計算來算出畫布的縮放的比例以及偏移值,判斷緩存池中取出的Bitmap對象是否爲空,若是不爲空就能夠直接使用,若是爲空則要建立一個新的Bitmap對象,將原圖Bitmap對象的alpha值複製到裁剪Bitmap對象上面,裁剪Bitmap對象進行繪製,並將最終的結果進行返回
獲得了裁剪後的Bitmap對象,咱們再回到CenterCrop當中,你會看到,在最終返回這個Bitmap對象以前,還會嘗試將複用的Bitmap對象從新放回到緩存池當中,以便下次繼續使用
使用技巧:
1.Glide.with(context).resumeRequests()和 Glide.with(context).pauseRequests()
當列表在滑動的時候,調用pauseRequests()取消請求,滑動中止時,調用resumeRequests()恢復請求。這樣是否是會好些呢?
2.Glide.clear()
當你想清除掉全部的圖片加載請求時,這個方法能夠幫助到你。
3.ListPreloader
若是你想讓列表預加載的話,不妨試一下ListPreloader這個類。