Android图片加载框架:玩转Glide的回调与监听
回調的源碼實現
作為一名Glide老手,相信大家對于Glide的基本用法已經非常熟練了。我們都知道,使用Glide在界面上加載并展示一張圖片只需要一行代碼:
Glide.with(this).load(url).into(imageView);而在這一行代碼的背后,Glide幫我們執行了成千上萬行的邏輯。其實在第二篇文章當中,我們已經分析了這一行代碼背后的完整執行流程,但是這里我準備再帶著大家單獨回顧一下回調這部分的源碼,這將有助于我們今天這篇文章的學習。
首先來看一下into()方法,這里我們將ImageView的實例傳入到into()方法當中,Glide將圖片加載完成之后,圖片就能顯示到ImageView上了。這是怎么實現的呢?我們來看一下into()方法的源碼:
public Target<TranscodeType> into(ImageView view) {Util.assertMainThread();if (view == null) {throw new IllegalArgumentException("You must pass in a non null View");}if (!isTransformationSet && view.getScaleType() != null) {switch (view.getScaleType()) {case CENTER_CROP:applyCenterCrop();break;case FIT_CENTER:case FIT_START:case FIT_END:applyFitCenter();break;default:// Do nothing.}}return into(glide.buildImageViewTarget(view, transcodeClass)); }可以看到,最后一行代碼會調用glide.buildImageViewTarget()方法構建出一個Target對象,然后再把它傳入到另一個接收Target參數的into()方法中。Target對象則是用來最終展示圖片用的,如果我們跟進到glide.buildImageViewTarget()方法中,你會看到如下的源碼:
public class ImageViewTargetFactory {@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)");}} }buildTarget()方法會根據傳入的class參數來構建不同的Target對象,如果你在使用Glide加載圖片的時候調用了asBitmap()方法,那么這里就會構建出BitmapImageViewTarget對象,否則的話構建的都是GlideDrawableImageViewTarget對象。至于上述代碼中的DrawableImageViewTarget對象,這個通常都是用不到的,我們可以暫時不用管它。
之后就會把這里構建出來的Target對象傳入到GenericRequest當中,而Glide在圖片加載完成之后又會回調GenericRequest的onResourceReady()方法,我們來看一下這部分源碼:
public final class GenericRequest<A, T, Z, R> implements Request, SizeReadyCallback,ResourceCallback {private Target<R> target;...private void onResourceReady(Resource<?> resource, R result) {boolean isFirstResource = isFirstReadyResource();status = Status.COMPLETE;this.resource = resource;if (requestListener == null || !requestListener.onResourceReady(result, model, target,loadedFromMemoryCache, isFirstResource)) {GlideAnimation<R> animation = animationFactory.build(loadedFromMemoryCache, isFirstResource);target.onResourceReady(result, animation);}notifyLoadSuccess();}... }這里在第14行調用了target.onResourceReady()方法,而剛才我們已經知道,這里的target就是GlideDrawableImageViewTarget對象,那么我們再來看一下它的源碼:
public class GlideDrawableImageViewTarget extends ImageViewTarget<GlideDrawable> {...@Overridepublic void onResourceReady(GlideDrawable resource, GlideAnimation<? super GlideDrawable> animation) {if (!resource.isAnimated()) {float viewRatio = view.getWidth() / (float) view.getHeight();float drawableRatio = resource.getIntrinsicWidth() / (float) resource.getIntrinsicHeight();if (Math.abs(viewRatio - 1f) <= SQUARE_RATIO_MARGIN&& Math.abs(drawableRatio - 1f) <= SQUARE_RATIO_MARGIN) {resource = new SquaringDrawable(resource, view.getWidth());}}super.onResourceReady(resource, animation);this.resource = resource;resource.setLoopCount(maxLoopCount);resource.start();}@Overrideprotected void setResource(GlideDrawable resource) {view.setImageDrawable(resource);}... }可以看到,這里在onResourceReady()方法中處理了圖片展示,還有GIF播放的邏輯,那么一張圖片也就顯示出來了,這也就是Glide回調的基本實現原理。
好的,那么原理就先分析到這兒,接下來我們就來看一下在回調和監聽方面還有哪些知識是可以擴展的。
into()方法
使用了這么久的Glide,我們都知道into()方法中是可以傳入ImageView的。那么into()方法還可以傳入別的參數嗎?我可以讓Glide加載出來的圖片不顯示到ImageView上嗎?答案是肯定的,這就需要用到自定義Target功能。
其實通過上面的分析,我們已經知道了,into()方法還有一個接收Target參數的重載。即使我們傳入的參數是ImageView,Glide也會在內部自動構建一個Target對象。而如果我們能夠掌握自定義Target技術的話,就可以更加隨心所欲地控制Glide的回調了。
我們先來看一下Glide中Target的繼承結構圖吧,如下所示:
可以看到,Target的繼承結構還是相當復雜的,實現Target接口的子類非常多。不過你不用被這么多的子類所嚇到,這些大多數都是Glide已經實現好的具備完整功能的Target子類,如果我們要進行自定義的話,通常只需要在兩種Target的基礎上去自定義就可以了,一種是SimpleTarget,一種是ViewTarget。
接下來我就分別以這兩種Target來舉例,學習一下自定義Target的功能。
首先來看SimpleTarget,顧名思義,它是一種極為簡單的Target,我們使用它可以將Glide加載出來的圖片對象獲取到,而不是像之前那樣只能將圖片在ImageView上顯示出來。
那么下面我們來看一下SimpleTarget的用法示例吧,其實非常簡單:
SimpleTarget<GlideDrawable> simpleTarget = new SimpleTarget<GlideDrawable>() {@Overridepublic void onResourceReady(GlideDrawable resource, GlideAnimation glideAnimation) {imageView.setImageDrawable(resource);} };public void loadImage(View view) {String url = "http://cn.bing.com/az/hprichbg/rb/TOAD_ZH-CN7336795473_1920x1080.jpg";Glide.with(this).load(url).into(simpleTarget); }怎么樣?不愧是SimpleTarget吧,短短幾行代碼就搞了。這里我們創建了一個SimpleTarget的實例,并且指定它的泛型是GlideDrawable,然后重寫了onResourceReady()方法。在onResourceReady()方法中,我們就可以獲取到Glide加載出來的圖片對象了,也就是方法參數中傳過來的GlideDrawable對象。有了這個對象之后你可以使用它進行任意的邏輯操作,這里我只是簡單地把它顯示到了ImageView上。
SimpleTarget的實現創建好了,那么只需要在加載圖片的時候將它傳入到into()方法中就可以了,現在運行一下程序,效果如下圖所示。
雖然目前這個效果和直接在into()方法中傳入ImageView并沒有什么區別,但是我們已經拿到了圖片對象的實例,然后就可以隨意做更多的事情了。
當然,SimpleTarget中的泛型并不一定只能是GlideDrawable,如果你能確定你正在加載的是一張靜態圖而不是GIF圖的話,我們還能直接拿到這張圖的Bitmap對象,如下所示:
SimpleTarget<Bitmap> simpleTarget = new SimpleTarget<Bitmap>() {@Overridepublic void onResourceReady(Bitmap resource, GlideAnimation glideAnimation) {imageView.setImageBitmap(resource);} };public void loadImage(View view) {String url = "http://cn.bing.com/az/hprichbg/rb/TOAD_ZH-CN7336795473_1920x1080.jpg";Glide.with(this).load(url).asBitmap().into(simpleTarget); }可以看到,這里我們將SimpleTarget的泛型指定成Bitmap,然后在加載圖片的時候調用了asBitmap()方法強制指定這是一張靜態圖,這樣就能在onResourceReady()方法中獲取到這張圖的Bitmap對象了。
好了,SimpleTarget的用法就是這么簡單,接下來我們學習一下ViewTarget的用法。
事實上,從剛才的繼承結構圖上就能看出,Glide在內部自動幫我們創建的GlideDrawableImageViewTarget就是ViewTarget的子類。只不過GlideDrawableImageViewTarget被限定只能作用在ImageView上,而ViewTarget的功能更加廣泛,它可以作用在任意的View上。
這里我們還是通過一個例子來演示一下吧,比如我創建了一個自定義布局MyLayout,如下所示:
public class MyLayout extends LinearLayout {private ViewTarget<MyLayout, GlideDrawable> viewTarget;public MyLayout(Context context, AttributeSet attrs) {super(context, attrs);viewTarget = new ViewTarget<MyLayout, GlideDrawable>(this) {@Overridepublic void onResourceReady(GlideDrawable resource, GlideAnimation glideAnimation) {MyLayout myLayout = getView();myLayout.setImageAsBackground(resource);}};}public ViewTarget<MyLayout, GlideDrawable> getTarget() {return viewTarget;}public void setImageAsBackground(GlideDrawable resource) {setBackground(resource);}}在MyLayout的構造函數中,我們創建了一個ViewTarget的實例,并將Mylayout當前的實例this傳了進去。ViewTarget中需要指定兩個泛型,一個是View的類型,一個圖片的類型(GlideDrawable或Bitmap)。然后在onResourceReady()方法中,我們就可以通過getView()方法獲取到MyLayout的實例,并調用它的任意接口了。比如說這里我們調用了setImageAsBackground()方法來將加載出來的圖片作為MyLayout布局的背景圖。
接下來看一下怎么使用這個Target吧,由于MyLayout中已經提供了getTarget()接口,我們只需要在加載圖片的地方這樣寫就可以了:
public class MainActivity extends AppCompatActivity {MyLayout myLayout;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);myLayout = (MyLayout) findViewById(R.id.background);}public void loadImage(View view) {String url = "http://cn.bing.com/az/hprichbg/rb/TOAD_ZH-CN7336795473_1920x1080.jpg";Glide.with(this).load(url).into(myLayout.getTarget());}}就是這么簡單,在into()方法中傳入myLayout.getTarget()即可。現在重新運行一下程序,效果如下圖所示。
好的,關于自定義Target的功能我們就介紹這么多,這些雖說都是自定義Target最基本的用法,但掌握了這些用法之后,你就能應對各種各樣復雜的邏輯了。
preload()方法
Glide加載圖片雖說非常智能,它會自動判斷該圖片是否已經有緩存了,如果有的話就直接從緩存中讀取,沒有的話再從網絡去下載。但是如果我希望提前對圖片進行一個預加載,等真正需要加載圖片的時候就直接從緩存中讀取,不想再等待慢長的網絡加載時間了,這該怎么辦呢?
對于很多Glide新手來說這確實是一個煩惱的問題,因為在沒有學習本篇文章之前,into()方法中必須傳入一個ImageView呀,而傳了ImageView之后圖片就顯示出來了,這還怎么預加載呢?
不過在學習了本篇文章之后,相信你已經能夠想到解決方案了。因為into()方法中除了傳入ImageView之后還可以傳入Target對象,如果我們在Target對象的onResourceReady()方法中做一個空實現,也就是不做任何邏輯處理,那么圖片自然也就顯示不出來了,而Glide的緩存機制卻仍然還會正常工作,這樣不就實現預加載功能了嗎?
沒錯,上述的做法完全可以實現預加載功能,不過有沒有感覺這種實現方式有點笨笨的。事實上,Glide專門給我們提供了預加載的接口,也就是preload()方法,我們只需要直接使用就可以了。
preload()方法有兩個方法重載,一個不帶參數,表示將會加載圖片的原始尺寸,另一個可以通過參數指定加載圖片的寬和高。
preload()方法的用法也非常簡單,直接使用它來替換into()方法即可,如下所示:
Glide.with(this).load(url).diskCacheStrategy(DiskCacheStrategy.SOURCE).preload();需要注意的是,我們如果使用了preload()方法,最好要將diskCacheStrategy的緩存策略指定成DiskCacheStrategy.SOURCE。因為preload()方法默認是預加載的原始圖片大小,而into()方法則默認會根據ImageView控件的大小來動態決定加載圖片的大小。因此,如果不將diskCacheStrategy的緩存策略指定成DiskCacheStrategy.SOURCE的話,很容易會造成我們在預加載完成之后再使用into()方法加載圖片,卻仍然還是要從網絡上去請求圖片這種現象。
調用了預加載之后,我們以后想再去加載這張圖片就會非常快了,因為Glide會直接從緩存當中去讀取圖片并顯示出來,代碼如下所示:
Glide.with(this).load(url).diskCacheStrategy(DiskCacheStrategy.SOURCE).into(imageView);注意,這里我們仍然需要使用diskCacheStrategy()方法將硬盤緩存策略指定成DiskCacheStrategy.SOURCE,以保證Glide一定會去讀取剛才預加載的圖片緩存。
preload()方法的用法大概就是這么簡單,但是僅僅會使用顯然層次有些太低了,下面我們就滿足一下好奇心,看看它的源碼是如何實現的。
和into()方法一樣,preload()方法也是在GenericRequestBuilder類當中的,代碼如下所示:
public class GenericRequestBuilder<ModelType, DataType, ResourceType, TranscodeType> implements Cloneable {...public Target<TranscodeType> preload(int width, int height) {final PreloadTarget<TranscodeType> target = PreloadTarget.obtain(width, height);return into(target);}public Target<TranscodeType> preload() {return preload(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL);}... }正如剛才所說,preload()方法有兩個方法重載,你可以調用帶參數的preload()方法來明確指定圖片的寬和高,也可以調用不帶參數的preload()方法,它會在內部自動將圖片的寬和高都指定成Target.SIZE_ORIGINAL,也就是圖片的原始尺寸。
然后我們可以看到,這里在第5行調用了PreloadTarget.obtain()方法獲取一個PreloadTarget的實例,并把它傳入到了into()方法當中。從剛才的繼承結構圖中可以看出,PreloadTarget是SimpleTarget的子類,因此它是可以直接傳入到into()方法中的。
那么現在的問題就是,PreloadTarget具體的實現到底是什么樣子的了,我們看一下它的源碼,如下所示:
public final class PreloadTarget<Z> extends SimpleTarget<Z> {public static <Z> PreloadTarget<Z> obtain(int width, int height) {return new PreloadTarget<Z>(width, height);}private PreloadTarget(int width, int height) {super(width, height);}@Overridepublic void onResourceReady(Z resource, GlideAnimation<? super Z> glideAnimation) {Glide.clear(this);} }PreloadTarget的源碼非常簡單,obtain()方法中就是new了一個PreloadTarget的實例而已,而onResourceReady()方法中也沒做什么事情,只是調用了Glide.clear()方法。
這里的Glide.clear()并不是清空緩存的意思,而是表示加載已完成,釋放資源的意思,因此不用在這里產生疑惑。
其實PreloadTarget的思想和我們剛才提到設計思路是一樣的,就是什么都不做就可以了。因為圖片加載完成之后只將它緩存而不去顯示它,那不就相當于預加載了嘛。
preload()方法不管是在用法方面還是源碼實現方面都還是非常簡單的,那么關于這個方法我們就學到這里。
downloadOnly()方法
一直以來,我們使用Glide都是為了將圖片顯示到界面上。雖然我們知道Glide會在圖片的加載過程中對圖片進行緩存,但是緩存文件到底是存在哪里的,以及如何去直接訪問這些緩存文件?我們都還不知道。
其實Glide將圖片加載接口設計成這樣也是希望我們使用起來更加的方便,不用過多去考慮底層的實現細節。但如果我現在就是想要去訪問圖片的緩存文件該怎么辦呢?這就需要用到downloadOnly()方法了。
和preload()方法類似,downloadOnly()方法也是可以替換into()方法的,不過downloadOnly()方法的用法明顯要比preload()方法復雜不少。顧名思義,downloadOnly()方法表示只會下載圖片,而不會對圖片進行加載。當圖片下載完成之后,我們可以得到圖片的存儲路徑,以便后續進行操作。
那么首先我們還是先來看下基本用法。downloadOnly()方法是定義在DrawableTypeRequest類當中的,它有兩個方法重載,一個接收圖片的寬度和高度,另一個接收一個泛型對象,如下所示:
downloadOnly(int width, int height)downloadOnly(Y target)這兩個方法各自有各自的應用場景,其中downloadOnly(int width, int height)是用于在子線程中下載圖片的,而downloadOnly(Y target)是用于在主線程中下載圖片的。
那么我們先來看downloadOnly(int width, int height)的用法。當調用了downloadOnly(int width, int height)方法后會立即返回一個FutureTarget對象,然后Glide會在后臺開始下載圖片文件。接下來我們調用FutureTarget的get()方法就可以去獲取下載好的圖片文件了,如果此時圖片還沒有下載完,那么get()方法就會阻塞住,一直等到圖片下載完成才會有值返回。
下面我們通過一個例子來演示一下吧,代碼如下所示:
public void downloadImage(View view) {new Thread(new Runnable() {@Overridepublic void run() {try {String url = "http://cn.bing.com/az/hprichbg/rb/TOAD_ZH-CN7336795473_1920x1080.jpg";final Context context = getApplicationContext();FutureTarget<File> target = Glide.with(context).load(url).downloadOnly(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL);final File imageFile = target.get();runOnUiThread(new Runnable() {@Overridepublic void run() {Toast.makeText(context, imageFile.getPath(), Toast.LENGTH_LONG).show();}});} catch (Exception e) {e.printStackTrace();}}}).start(); }這段代碼稍微有一點點長,我帶著大家解讀一下。首先剛才說了,downloadOnly(int width, int height)方法必須要用在子線程當中,因此這里的第一步就是new了一個Thread。在子線程當中,我們先獲取了一個Application Context,這個時候不能再用Activity作為Context了,因為會有Activity銷毀了但子線程還沒執行完這種可能出現。
接下來就是Glide的基本用法,只不過將into()方法替換成了downloadOnly()方法。downloadOnly()方法會返回一個FutureTarget對象,這個時候其實Glide已經開始在后臺下載圖片了,我們隨時都可以調用FutureTarget的get()方法來獲取下載的圖片文件,只不過如果圖片還沒下載好線程會暫時阻塞住,等下載完成了才會把圖片的File對象返回。
最后,我們使用runOnUiThread()切回到主線程,然后使用Toast將下載好的圖片文件路徑顯示出來。
現在重新運行一下代碼,效果如下圖所示。
這樣我們就能清晰地看出來圖片完整的緩存路徑是什么了。
之后我們可以使用如下代碼去加載這張圖片,圖片就會立即顯示出來,而不用再去網絡上請求了:
public void loadImage(View view) {String url = "http://cn.bing.com/az/hprichbg/rb/TOAD_ZH-CN7336795473_1920x1080.jpg";Glide.with(this).load(url).diskCacheStrategy(DiskCacheStrategy.SOURCE).into(imageView); }需要注意的是,這里必須將硬盤緩存策略指定成DiskCacheStrategy.SOURCE或者DiskCacheStrategy.ALL,否則Glide將無法使用我們剛才下載好的圖片緩存文件。
現在重新運行一下代碼,效果如下圖所示。
可以看到,圖片的加載和顯示是非常快的,因為Glide直接使用的是剛才下載好的緩存文件。
那么這個downloadOnly(int width, int height)方法的工作原理到底是什么樣的呢?我們來簡單快速地看一下它的源碼吧。
首先在DrawableTypeRequest類當中可以找到定義這個方法的地方,如下所示:
public class DrawableTypeRequest<ModelType> extends DrawableRequestBuilder<ModelType>implements DownloadOptions {...public FutureTarget<File> downloadOnly(int width, int height) {return getDownloadOnlyRequest().downloadOnly(width, height);}private GenericTranscodeRequest<ModelType, InputStream, File> getDownloadOnlyRequest() {return optionsApplier.apply(new GenericTranscodeRequest<ModelType, InputStream, File>(File.class, this, streamModelLoader, InputStream.class, File.class, optionsApplier));} }這里會先調用getDownloadOnlyRequest()方法得到一個GenericTranscodeRequest對象,然后再調用它的downloadOnly()方法,代碼如下所示:
public class GenericTranscodeRequest<ModelType, DataType, ResourceType>implements DownloadOptions {...public FutureTarget<File> downloadOnly(int width, int height) {return getDownloadOnlyRequest().into(width, height);}private GenericRequestBuilder<ModelType, DataType, File, File> getDownloadOnlyRequest() {ResourceTranscoder<File, File> transcoder = UnitTranscoder.get();DataLoadProvider<DataType, File> dataLoadProvider = glide.buildDataProvider(dataClass, File.class);FixedLoadProvider<ModelType, DataType, File, File> fixedLoadProvider =new FixedLoadProvider<ModelType, DataType, File, File>(modelLoader, transcoder, dataLoadProvider);return optionsApplier.apply(new GenericRequestBuilder<ModelType, DataType, File, File>(fixedLoadProvider,File.class, this)).priority(Priority.LOW).diskCacheStrategy(DiskCacheStrategy.SOURCE).skipMemoryCache(true);} }這里又是調用了一個getDownloadOnlyRequest()方法來構建了一個圖片下載的請求,getDownloadOnlyRequest()方法會返回一個GenericRequestBuilder對象,接著調用它的into(width, height)方法,我們繼續跟進去瞧一瞧:
public FutureTarget<TranscodeType> into(int width, int height) {final RequestFutureTarget<ModelType, TranscodeType> target =new RequestFutureTarget<ModelType, TranscodeType>(glide.getMainHandler(), width, height);glide.getMainHandler().post(new Runnable() {@Overridepublic void run() {if (!target.isCancelled()) {into(target);}}});return target; }可以看到,這里首先是new出了一個RequestFutureTarget對象,RequestFutureTarget也是Target的子類之一。然后通過Handler將線程切回到主線程當中,再將這個RequestFutureTarget傳入到into()方法當中。
那么也就是說,其實這里就是調用了接收Target參數的into()方法,然后Glide就開始執行正常的圖片加載邏輯了。那么現在剩下的問題就是,這個RequestFutureTarget中到底處理了些什么邏輯?我們打開它的源碼看一看:
public class RequestFutureTarget<T, R> implements FutureTarget<R>, Runnable {...@Overridepublic R get() throws InterruptedException, ExecutionException {try {return doGet(null);} catch (TimeoutException e) {throw new AssertionError(e);}}@Overridepublic R get(long time, TimeUnit timeUnit) throws InterruptedException, ExecutionException, TimeoutException {return doGet(timeUnit.toMillis(time));}@Overridepublic void getSize(SizeReadyCallback cb) {cb.onSizeReady(width, height);}@Overridepublic synchronized void onLoadFailed(Exception e, Drawable errorDrawable) {exceptionReceived = true;this.exception = e;waiter.notifyAll(this);}@Overridepublic synchronized void onResourceReady(R resource, GlideAnimation<? super R> glideAnimation) {resultReceived = true;this.resource = resource;waiter.notifyAll(this);}private synchronized R doGet(Long timeoutMillis) throws ExecutionException, InterruptedException, TimeoutException {if (assertBackgroundThread) {Util.assertBackgroundThread();}if (isCancelled) {throw new CancellationException();} else if (exceptionReceived) {throw new ExecutionException(exception);} else if (resultReceived) {return resource;}if (timeoutMillis == null) {waiter.waitForTimeout(this, 0);} else if (timeoutMillis > 0) {waiter.waitForTimeout(this, timeoutMillis);}if (Thread.interrupted()) {throw new InterruptedException();} else if (exceptionReceived) {throw new ExecutionException(exception);} else if (isCancelled) {throw new CancellationException();} else if (!resultReceived) {throw new TimeoutException();}return resource;}static class Waiter {public void waitForTimeout(Object toWaitOn, long timeoutMillis) throws InterruptedException {toWaitOn.wait(timeoutMillis);}public void notifyAll(Object toNotify) {toNotify.notifyAll();}}... }這里我對RequestFutureTarget的源碼做了一些精簡,我們只看最主要的邏輯就可以了。
剛才我們已經學習過了downloadOnly()方法的基本用法,在調用了downloadOnly()方法之后,再調用FutureTarget的get()方法,就能獲取到下載的圖片文件了。而downloadOnly()方法返回的FutureTarget對象其實就是這個RequestFutureTarget,因此我們直接來看它的get()方法就行了。
RequestFutureTarget的get()方法中又調用了一個doGet()方法,而doGet()方法才是真正處理具體邏輯的地方。首先在doGet()方法中會判斷當前是否是在子線程當中,如果不是的話會直接拋出一個異常。然后下面會判斷下載是否已取消、或者已失敗,如果是已取消或者已失敗的話都會直接拋出一個異常。接下來會根據resultReceived這個變量來判斷下載是否已完成,如果這個變量為true的話,就直接把結果進行返回。
那么如果下載還沒有完成呢?我們繼續往下看,接下來就進入到一個wait()當中,把當前線程給阻塞住,從而阻止代碼繼續往下執行。這也是為什么downloadOnly(int width, int height)方法要求必須在子線程當中使用,因為它會對當前線程進行阻塞,如果在主線程當中使用的話,那么就會讓主線程卡死,從而用戶無法進行任何其他操作。
那么現在線程被阻塞住了,什么時候才能恢復呢?答案在onResourceReady()方法中。可以看到,onResourceReady()方法中只有三行代碼,第一行把resultReceived賦值成true,說明圖片文件已經下載好了,這樣下次再調用get()方法時就不會再阻塞線程,而是可以直接將結果返回。第二行把下載好的圖片文件賦值到一個全局的resource變量上面,這樣doGet()方法就也可以訪問到它。第三行notifyAll一下,通知所有wait的線程取消阻塞,這個時候圖片文件已經下載好了,因此doGet()方法也就可以返回結果了。
好的,這就是downloadOnly(int width, int height)方法的基本用法和實現原理,那么下面我們來看一下downloadOnly(Y target)方法。
回想一下,其實downloadOnly(int width, int height)方法必須使用在子線程當中,最主要還是因為它在內部幫我們自動創建了一個RequestFutureTarget,是這個RequestFutureTarget要求必須在子線程當中執行。而downloadOnly(Y target)方法則要求我們傳入一個自己創建的Target,因此就不受RequestFutureTarget的限制了。
但是downloadOnly(Y target)方法的用法也會相對更復雜一些,因為我們又要自己創建一個Target了,而且這次必須直接去實現最頂層的Target接口,比之前的SimpleTarget和ViewTarget都要復雜不少。
那么下面我們就來實現一個最簡單的DownloadImageTarget吧,注意Target接口的泛型必須指定成File對象,這是downloadOnly(Y target)方法要求的,代碼如下所示:
public class DownloadImageTarget implements Target<File> {private static final String TAG = "DownloadImageTarget";@Overridepublic void onStart() {}@Overridepublic void onStop() {}@Overridepublic void onDestroy() {}@Overridepublic void onLoadStarted(Drawable placeholder) {}@Overridepublic void onLoadFailed(Exception e, Drawable errorDrawable) {}@Overridepublic void onResourceReady(File resource, GlideAnimation<? super File> glideAnimation) {Log.d(TAG, resource.getPath());}@Overridepublic void onLoadCleared(Drawable placeholder) {}@Overridepublic void getSize(SizeReadyCallback cb) {cb.onSizeReady(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL);}@Overridepublic void setRequest(Request request) {}@Overridepublic Request getRequest() {return null;} }由于是要直接實現Target接口,因此需要重寫的方法非常多。這些方法大多是數Glide加載圖片生命周期的一些回調,我們可以不用管它們,其中只有兩個方法是必須實現的,一個是getSize()方法,一個是onResourceReady()方法。
在第二篇Glide源碼解析的時候,我帶著大家一起分析過,Glide在開始加載圖片之前會先計算圖片的大小,然后回調到onSizeReady()方法當中,之后才會開始執行圖片加載。而這里,計算圖片大小的任務就交給我們了。只不過這是一個最簡單的Target實現,我在getSize()方法中就直接回調了Target.SIZE_ORIGINAL,表示圖片的原始尺寸。
然后onResourceReady()方法我們就非常熟悉了,圖片下載完成之后就會回調到這里,我在這個方法中只是打印了一下下載的圖片文件的路徑。
這樣一個最簡單的DownloadImageTarget就定義好了,使用它也非常的簡單,我們不用再考慮什么線程的問題了,而是直接把它的實例傳入downloadOnly(Y target)方法中即可,如下所示:
public void downloadImage(View view) {String url = "http://cn.bing.com/az/hprichbg/rb/TOAD_ZH-CN7336795473_1920x1080.jpg";Glide.with(this).load(url).downloadOnly(new DownloadImageTarget()); }現在重新運行一下代碼并點擊Download Image按鈕,然后觀察控制臺日志的輸出,結果如下圖所示。
這樣我們就使用了downloadOnly(Y target)方法同樣獲取到下載的圖片文件的緩存路徑了。
好的,那么關于downloadOnly()方法我們就學到這里。
listener()方法
今天學習的內容已經夠多了,下面我們就以一個簡單的知識點結尾吧,Glide回調與監聽的最后一部分——listener()方法。
其實listener()方法的作用非常普遍,它可以用來監聽Glide加載圖片的狀態。舉個例子,比如說我們剛才使用了preload()方法來對圖片進行預加載,但是我怎樣確定預加載有沒有完成呢?還有如果Glide加載圖片失敗了,我該怎樣調試錯誤的原因呢?答案都在listener()方法當中。
首先來看下listener()方法的基本用法吧,不同于剛才幾個方法都是要替換into()方法的,listener()是結合into()方法一起使用的,當然也可以結合preload()方法一起使用。最基本的用法如下所示:
public void loadImage(View view) {String url = "http://cn.bing.com/az/hprichbg/rb/TOAD_ZH-CN7336795473_1920x1080.jpg";Glide.with(this).load(url).listener(new RequestListener<String, GlideDrawable>() {@Overridepublic boolean onException(Exception e, String model, Target<GlideDrawable> target,boolean isFirstResource) {return false;}@Overridepublic boolean onResourceReady(GlideDrawable resource, String model,Target<GlideDrawable> target, boolean isFromMemoryCache, boolean isFirstResource) {return false;}}).into(imageView); }這里我們在into()方法之前串接了一個listener()方法,然后實現了一個RequestListener的實例。其中RequestListener需要實現兩個方法,一個onResourceReady()方法,一個onException()方法。從方法名上就可以看出來了,當圖片加載完成的時候就會回調onResourceReady()方法,而當圖片加載失敗的時候就會回調onException()方法,onException()方法中會將失敗的Exception參數傳進來,這樣我們就可以定位具體失敗的原因了。
沒錯,listener()方法就是這么簡單。不過還有一點需要處理,onResourceReady()方法和onException()方法都有一個布爾值的返回值,返回false就表示這個事件沒有被處理,還會繼續向下傳遞,返回true就表示這個事件已經被處理掉了,從而不會再繼續向下傳遞。舉個簡單點的例子,如果我們在RequestListener的onResourceReady()方法中返回了true,那么就不會再回調Target的onResourceReady()方法了。
關于listener()方法的用法就講這么多,不過還是老規矩,我們再來看一下它的源碼是怎么實現的吧。
首先,listener()方法是定義在GenericRequestBuilder類當中的,而我們傳入到listener()方法中的實例則會賦值到一個requestListener變量當中,如下所示:
public class GenericRequestBuilder<ModelType, DataType, ResourceType, TranscodeType> implements Cloneable {private RequestListener<? super ModelType, TranscodeType> requestListener;...public GenericRequestBuilder<ModelType, DataType, ResourceType, TranscodeType> listener(RequestListener<? super ModelType, TranscodeType> requestListener) {this.requestListener = requestListener;return this;}... }接下來在構建GenericRequest的時候這個變量也會被一起傳進去,最后在圖片加載完成的時候,我們會看到如下邏輯:
public final class GenericRequest<A, T, Z, R> implements Request, SizeReadyCallback,ResourceCallback {private RequestListener<? super A, R> requestListener;...private void onResourceReady(Resource<?> resource, R result) {boolean isFirstResource = isFirstReadyResource();status = Status.COMPLETE;this.resource = resource;if (requestListener == null || !requestListener.onResourceReady(result, model, target,loadedFromMemoryCache, isFirstResource)) {GlideAnimation<R> animation = animationFactory.build(loadedFromMemoryCache, isFirstResource);target.onResourceReady(result, animation);}notifyLoadSuccess();}... }可以看到,這里在第11行會先回調requestListener的onResourceReady()方法,只有當這個onResourceReady()方法返回false的時候,才會繼續調用Target的onResourceReady()方法,這也就是listener()方法的實現原理。
另外一個onException()方法的實現機制也是一模一樣的,代碼同樣是在GenericRequest類中,如下所示:
public final class GenericRequest<A, T, Z, R> implements Request, SizeReadyCallback,ResourceCallback {...@Overridepublic void onException(Exception e) {status = Status.FAILED;if (requestListener == null || !requestListener.onException(e, model, target, isFirstReadyResource())) {setErrorPlaceholder(e);}}... }可以看到,這里會在第9行回調requestListener的onException()方法,只有在onException()方法返回false的情況下才會繼續調用setErrorPlaceholder()方法。也就是說,如果我們在onException()方法中返回了true,那么Glide請求中使用error(int resourceId)方法設置的異常占位圖就失效了。
這樣我們也就將listener()方法的全部實現原理都分析完了。
原文:http://blog.csdn.net/guolin_blog/article/details/70215985
總結
以上是生活随笔為你收集整理的Android图片加载框架:玩转Glide的回调与监听的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 如何优雅获取B站壁纸?
- 下一篇: 数据流分析基础和局域网基础