国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 學院 > 開發設計 > 正文

探索Glide對Gif圖片資源的獲取、解析過程

2019-11-09 14:56:11
字體:
來源:轉載
供稿:網友

先預祝大家湯圓節快樂!很久沒寫博客了。今天我們來探索一下Glide是如何支持Gif圖片加載的。

本篇博客的目的

了解代碼分析的基本思路與方法了解Glide是如何對Gif圖片進行支持的

探索背景

為什么會有這么一個想法呢,一來一直對Glide是知其名而不知其所以然,二來還主要是工作中需要對它研究研究,以便更好的支持工作內容。

我想很多同學都希望自己可以對某種著名的開源框架了解貫通,但是很多時候研究一款框架實在是費神費力,很容易就會放棄。

造成這樣的困局主要有三點:

一來因為我們在探究源碼時沒有明確的目標。二來是因為我們沒有合適順手的工具。三來是因為找不到重點,容易被其它不相干代碼迷惑。

接下來我們就對上面這些問題一一帶入。

探索準備工作

1,首先我的目標很明確,我需要了解Glide是否支持Gif圖片,以及它是如何支持Gif圖片的。這樣我才可以在應用層對其做良好的支持。

因為我們的工作要求是:所有的ImageView都必須支持Gif圖片

我的解決辦法有三種:

1.如果Glide支持Gif圖片,那么我只需要在圖片調用層全部加上Gif支持開關。(事實上Glide默認就支持Gif,不需要我單獨添加控制。)2.如果Glide支持Gif圖片,但是它的檢測開銷成本很大,那我就必須手動對資源進行解析,判斷是否是Gif,如果是,則調用Gif圖片的加載邏輯。如果不是,則走一般的圖片加載邏輯。3.如果Glide不支持Gif圖片,那么我必須對ImageView進行擴展,然后更改應用內所有的ImageView的繼承關系。這個工作量是巨大的。

因為有以上判斷條件,所以我決定先從Glide的Gif支持入手。

2,因為我們需要對Glide研究、分析,那么手上必須有Glide的最新代碼。我們在Glide的主頁上找到源代碼的下載地址,下載即可。

Glide首頁: https://github.com/bumptech/glide/releases Glide源碼地址: https://github.com/bumptech/glide/releases/download/v3.7.0/glide-3.7.0-sources.jar

3.準備工作已經做的差不多了,最后還剩代碼分析利器Android Studio以及Source Insight,當然放在手邊為我們做輔助記錄的筆和紙是少不了的。

Source Insight的主頁為:https://www.sourceinsight.com/ Source Insight的功能很強大,我也只是懂一點點基本用法而已,不過足夠用了。下載好的代碼需要使用Source Insight打開,我們需要實時檢索文件使用。這里不再說明Source Insight的用法,請自行學習了解。它在這里的作用是幫我們做一些引用關系檢查。

除了Source Insight之外,我們主要使用Android Studio進行代碼分析調試。需要將剛剛下載好的源代碼解壓,然后作為我們工程的一部分: 這里寫圖片描述

然后按照Glide的使用說明開始我們的分析入口編寫:

// For a simple view:@Override public void onCreate(Bundle savedInstanceState) { ... ImageView imageView = (ImageView) findViewById(R.id.my_image_view); Glide.with(this).load("http://QQ.yh31.com/tp/zjbq/201612231514480890.gif").into(imageView);}

為了輔助我們一次次分析Glide的網絡訪問,我們在onDestroy方法中加入以下代碼:

PRotected void onDestroy() { super.onDestroy(); Glide.get(this).clearMemory(); Glide.get(this).clearDiskCache();}

開始探索之旅

我們如果需要了解Glide是否默認支持Gif圖片,那么只需要在load方法內替換成gif圖片的地址即可。

我們發現,它支持。

那么它是如何完成網絡資源獲取、Gif類型識別、Gif資源解析這些工作的呢?下面讓我們一起來一探究竟。

Glide的網絡資源獲取

Glide對Gif資源的獲取也是Glide網絡請求的核心,我想大家對這些框架一般都看中的是這部分。讓我們從這里究其所以然。

在這里聲明一下,我們剛開始拿到代碼時,就算會使用,也不知道真正的分析入口在哪里。但是不要灰心,就算是對代碼再熟悉的人,也會迷失在這結構復雜的代碼海洋里。請記住,分析的過程是總是需要來回反復查看、嘗試的。所以手邊的紙和筆對我們的幫助就體現出來了,我們需要通過紙和筆來記錄我們走過的重要流程。

PS: 以后的分析過程會將沒有歧義的過程自動略過,并且會將無關代碼自動省略。

PS: 我們的分析手段主要有兩種,一是通過斷點調試來分析,二是通過上下文來分析。其中第一種比較方便,文章中主要采用第一種方法。

我們先來分析這段代碼:

Glide.with(this)

由于我們是在Activity中使用的,所以這里的this應當是Activity,我們進入這個方法查看:

public static RequestManager with(FragmentActivity activity) { RequestManagerRetriever retriever = RequestManagerRetriever.get(); return retriever.get(activity); }

好,從上面得知,這個方法返回了一個RequestManager對象,接下來分析

.load("http://qq.yh31.com/tp/zjbq/201612231514480890.gif")

這里的load方法則調用的是RequestManager的load方法,我們看一下:

public DrawableTypeRequest<String> load(String string) { return (DrawableTypeRequest<String>) fromString().load(string); }

我們看到,load方法返回了一個DrawableTypeRequest對象,我們先記住它。接下來我們需要分析

.into(new ImageView(this));

我們跟著這個into方法一路追蹤,最后來到了GenericRequestBuilder的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; }

這里我們看到構建了一個Request對象,我們進去看一下是如何構建這個對象的,最后我們在GenericRequestBuilder類中定位到了這個方法:

private Request obtainRequest(Target<TranscodeType> target, float sizeMultiplier, Priority priority, RequestCoordinator requestCoordinator) { return GenericRequest.obtain( loadProvider, model, signature, context, priority, target, sizeMultiplier, placeholderDrawable, placeholderId, errorPlaceholder, errorId, fallbackDrawable, fallbackResource, requestListener, requestCoordinator, glide.getEngine(), transformation, transcodeClass, isCacheable, animationFactory, overrideWidth, overrideHeight, diskCacheStrategy); }

看來上面提到的Request對象實則為GenericRequest的實例,我們先記下。

然后返回進入requestTracker.runRequest(request)中查看,看起來像是運行這個請求的意思。

runRequest的內部實現是這樣的:

public void runRequest(Request request) { requests.add(request); if (!isPaused) { request.begin(); } else { pendingRequests.add(request); } }

它內部調用了request對象的begin方法,也就是說這里調用了GenericRequest的begin()方法。我們找到這個方法:

public void begin() { ... if (Util.isValidDimensions(overrideWidth, overrideHeight)) { onSizeReady(overrideWidth, overrideHeight); } else { target.getSize(this); } ... }

在這里走的else條件,我們可能已經不太記得target到底是誰實現的,它只是個接口,幸好有AS,我們通過調試知道這個target其實為:GlideDrawableImageViewTarget,具體它是什么時候被設置到這里的,我們先不去深究它,肯定能找到地方,但找它不是我們的目的。

我們找到它對應的getSize()方法:

public void getSize(SizeReadyCallback cb) { sizeDeterminer.getSize(cb); }

我們不要在這里停留,繼續往下走,最后我們會走到com.bumptech.glide.request.GenericRequest的onSizeReady方法中,我們在這里注意重點部分:

public void onSizeReady(int width, int height) { ... loadStatus = engine.load(signature, width, height, dataFetcher, loadProvider, transformation, transcoder, priority, isMemoryCacheable, diskCacheStrategy, this); ... }

從Engine的load方法我們進去看,這里是我們繼續執行的重點,我們進入到com.bumptech.glide.load.engine.Engine的load方法:

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) { ... 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); ... return new LoadStatus(cb, engineJob); }

在這路上一定不能被其它代碼迷惑,要感知哪部分是重點,嘗試自己分析一下這部分。有沒有很像任務及線程池?沒錯,你如果看各個類之間的繼承關系的話,它們確實是,我們就不再看它們之間的關系,我們只用看EngineRunnable的run()方法。

public void run() { ... Exception exception = null; Resource<?> resource = null; try { resource = decode(); } catch (Exception e) { if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "Exception decoding", e); } exception = e; } ... if (resource == null) { onLoadFailed(exception); } else { onLoadComplete(resource); } }

這段代碼主要由兩部分組成,這先簡單描述一下它們的工作流程,首先進入decode方法嘗試從緩存中獲取資源,第一次當然是null,然后進入onLoadFailed方法。onLoadFailed會將這個任務再次提交,再次重新執行,這次會進入decodeFromSource方法:

private Resource<?> decodeFromSource() throws Exception { return decodeJob.decodeFromSource(); }

我們一路向下,最后來到com.bumptech.glide.load.engine.DecodeJob的decodeSource方法,這個過程千萬別掉隊了,這里馬上就要見到如何訪問網絡了:

private Resource<T> decodeSource() throws Exception { ... final A data = fetcher.loadData(priority); ... decoded = decodeFromSourceData(data); ... return decoded; }

這里有兩部分重點,一個是獲取資源,一個是對資源進行解析。這里的fetcher也是一個接口,它的實現類中有HttpUrlFetcher,很明顯的網絡資源獲取類,我們通過調試也發現這里的對象是ImageVideoFetcher,而它的內部正是調用了HttpUrlFetcher的loadData方法,我們再繼續往下,我們很快就發現了Glide的網絡訪問核心方法:

private InputStream loadDataWithRedirects(URL url, int redirects, URL lastUrl, Map<String, String> headers) throws IOException { ... urlConnection = connectionFactory.build(url); for (Map.Entry<String, String> headerEntry : headers.entrySet()) { urlConnection.addRequestProperty(headerEntry.getKey(), headerEntry.getValue()); } urlConnection.setConnectTimeout(2500); urlConnection.setReadTimeout(2500); urlConnection.setUseCaches(false); urlConnection.setDoInput(true); ... final int statusCode = urlConnection.getResponseCode(); if (statusCode / 100 == 2) { return getStreamForSuccessfulRequest(urlConnection); } ... }

好,是不是很熟悉呢?原來Glide內部使用了Android的HttpURLConnection來進行網絡訪問,而且這里的訪問訪問超時時間是固定的:2500毫秒。

到目前為止,我們所處的位置為HttpUrlFetcher的loadDataWithRedirects方法,當然,我們并不在主線程:

at com.bumptech.glide.load.data.HttpUrlFetcher.loadDataWithRedirects(HttpUrlFetcher.java:49) at com.bumptech.glide.load.data.HttpUrlFetcher.loadData(HttpUrlFetcher.java:44) at com.bumptech.glide.load.data.HttpUrlFetcher.loadData(HttpUrlFetcher.java:20) at com.bumptech.glide.load.model.ImageVideoModelLoader$ImageVideoFetcher.loadData(ImageVideoModelLoader.java:70) at com.bumptech.glide.load.model.ImageVideoModelLoader$ImageVideoFetcher.loadData(ImageVideoModelLoader.java:53) at com.bumptech.glide.load.engine.DecodeJob.decodeSource(DecodeJob.java:170) at com.bumptech.glide.load.engine.DecodeJob.decodeFromSource(DecodeJob.java:128) at com.bumptech.glide.load.engine.EngineRunnable.decodeFromSource(EngineRunnable.java:122) at com.bumptech.glide.load.engine.EngineRunnable.decode(EngineRunnable.java:101) at com.bumptech.glide.load.engine.EngineRunnable.run(EngineRunnable.java:58)

所以,到目前為止,我們已經知道了Glide是如何訪問網絡的。

Glide對Gif資源的識別方式

接著上面的部分繼續,因為我們已經得到了從網絡傳回的數據流,那么接下來就需要對這些數據進行解析,我們回到com.bumptech.glide.load.engine.DecodeJo的decodeSource方法,也就是回到這里:

private Resource<T> decodeSource() throws Exception { Resource<T> decoded = null; try { long startTime = LogTime.getLogTime(); final A data = fetcher.loadData(priority); ... decoded = decodeFromSourceData(data); } finally { fetcher.cleanup(); } return decoded; }

因為我們是從fetcher.loadData中返回的,所以接下來我們需要進入decodeFromSourceData方法內,然后再一路向下追蹤,最后來到com.bumptech.glide.load.resource.gifbitmap.GifBitmapWrapperResourceDecode的decodeStream方法內:

private GifBitmapWrapper decodeStream(ImageVideoWrapper source, int width, int height, byte[] bytes) throws IOException { InputStream bis = streamFactory.build(source.getStream(), bytes); bis.mark(MARK_LIMIT_BYTES); ImageHeaderParser.ImageType type = parser.parse(bis); bis.reset(); ... return result; }

我們會注意到有段代碼,將InputStream解析為了ImageHeaderParser.ImageType類型的對象,我們可以猜測,這極有可能是對各種網絡流進行分類的地方,我們進去繼續向下追蹤一探究竟,最后來到com.bumptech.glide.load.resource.bitmap.ImageHeaderParser的getType方法:

public ImageType getType() throws IOException { int firstTwoBytes = streamReader.getUInt16(); // JPEG. if (firstTwoBytes == EXIF_MAGIC_NUMBER) { return JPEG; } final int firstFourBytes = firstTwoBytes << 16 & 0xFFFF0000 | streamReader.getUInt16() & 0xFFFF; // PNG. if (firstFourBytes == PNG_HEADER) { // See: http://stackoverflow.com/questions/2057923/how-to-check-a-png-for-grayscale-alpha-color-type streamReader.skip(25 - 4); int alpha = streamReader.getByte(); // A RGB indexed PNG can also have transparency. Better safe than sorry! return alpha >= 3 ? PNG_A : PNG; } // GIF from first 3 bytes. if (firstFourBytes >> 8 == GIF_HEADER) { return GIF; } return UNKNOWN; }

果不其然,在這個方法內部對所有的數據進行識別,我們在最后面看到了gif數據的識別原理:firstFourBytes >> 8 == GIF_HEADER。

Glide對Gif資源的解析方式

好,既然知道了現在的數據流是gif了,那么接下來就是解析過程了,我們回到com.bumptech.glide.load.resource.gifbitmap.GifBitmapWrapperResourceDecoder的decodeStream方法處,繼續往下走,我們很快就在該方法內看到有這么一行代碼:

if (type == ImageHeaderParser.ImageType.GIF) { result = decodeGifWrapper(bis, width, height); }

原來這個方法對GIF類型的圖片做了專門的處理,我們進入這個方法并一路向下,最后我們會來到com.bumptech.glide.load.resource.gif.GifResourceDecoder的decode(byte[] data, int width, int height, GifHeaderParser parser, GifDecoder decoder)方法:

private GifDrawableResource decode(byte[] data, int width, int height, GifHeaderParser parser, GifDecoder decoder) { ... Bitmap firstFrame = decodeFirstFrame(decoder, header, data); ... GifDrawable gifDrawable = new GifDrawable(context, provider, bitmapPool, unitTransformation, width, height, header, data, firstFrame); return new GifDrawableResource(gifDrawable); }

我們注意到在這個方法內解析了Gif資源的第一幀。我們進到decodeFirstFrame方法看一下它是如何解析的:

private Bitmap decodeFirstFrame(GifDecoder decoder, GifHeader header, byte[] data) { decoder.setData(header, data); decoder.advance(); return decoder.getNextFrame(); }

這里最后調用了decoder.getNextFrame()方法,這里的decoder為GifDecoder,也就是專門用于解析Gif資源的解碼器,我們進入getNextFrame()方法一探究竟:

public synchronized Bitmap getNextFrame() { ... status = STATUS_OK; GifFrame currentFrame = header.frames.get(framePointer); GifFrame previousFrame = null; int previousIndex = framePointer - 1; if (previousIndex >= 0) { previousFrame = header.frames.get(previousIndex); } ... // Transfer pixel data to image. Bitmap result = setPixels(currentFrame, previousFrame); ... return result; }

這里的代碼還挺長的,我們只挑最主要的看,它最后調用了setPixels()方法:

private Bitmap setPixels(GifFrame currentFrame, GifFrame previousFrame) { ... // Decode pixels for this frame into the global pixels[] scratch. decodeBitmapData(currentFrame); // Copy each source line to the appropriate place in the destination. int pass = 1; int inc = 8; int iline = 0; for (int i = 0; i < currentFrame.ih; i++) { int line = i; if (currentFrame.interlace) { if (iline >= currentFrame.ih) { pass++; switch (pass) { case 2: iline = 4; break; case 3: iline = 2; inc = 4; break; case 4: iline = 1; inc = 2; break; default: break; } } line = iline; iline += inc; } line += currentFrame.iy; if (line < header.height) { int k = line * header.width; // Start of line in dest. int dx = k + currentFrame.ix; // End of dest line. int dlim = dx + currentFrame.iw; if ((k + header.width) < dlim) { // Past dest edge. dlim = k + header.width; } // Start of line in source. int sx = i * currentFrame.iw; while (dx < dlim) { // Map color and insert in destination. int index = ((int) mainPixels[sx++]) & 0xff; int c = act[index]; if (c != 0) { dest[dx] = c; } dx++; } } } ... // Set pixels for current image. Bitmap result = getNextBitmap(); result.setPixels(dest, 0, width, 0, 0, width, height); return result; }

這段代碼還是很長,我們將不主要的代碼隱去,中間很長一部分推測應該是進行數據轉換。最終是調用了Bitmap的setPixels方法完成位圖的創建。

好,到此為止,我們知道了Gif圖是如何解析成位圖的了,然后我們返回,回到com.bumptech.glide.load.resource.gif.GifResourceDecoder的decode方法繼續向下走:

private GifDrawableResource decode(byte[] data, int width, int height, GifHeaderParser parser, GifDecoder decoder) { ... Bitmap firstFrame = decodeFirstFrame(decoder, header, data);//這里是剛剛出來的地方,從這里繼續向下 if (firstFrame == null) { return null; } Transformation<Bitmap> unitTransformation = UnitTransformation.get(); GifDrawable gifDrawable = new GifDrawable(context, provider, bitmapPool, unitTransformation, width, height, header, data, firstFrame); return new GifDrawableResource(gifDrawable); }

我們很快就發現,剛才解析好的位圖被用作創建了GifDrawable對象,然后GifDrawable對象又用來創建了GifDrawableResource對象,然后返回,回到最開始的com.bumptech.glide.load.engine.EngineRunnable的run方法:

public void run() { if (isCancelled) { return; } Exception exception = null; Resource<?> resource = null; try { resource = decode();//我們剛剛從這里返回 } catch (Exception e) { if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "Exception decoding", e); } exception = e; } ... if (resource == null) { onLoadFailed(exception); } else { onLoadComplete(resource);//然后代碼繼續向下執行會從這里走 } }

我們回到最開始的EngineRunnable的run方法。然后我們知道這里的resource不是null,所以進入onLoadComplete方法。到這里為止,我們就完成了Gif資源的解析過程分析。

從onLoadComplete方法開始就是Gif資源的輪播流程了,由于篇幅有限,在這里就不再涉及,有興趣的同學可以自行分析鍛煉一下。

最后希望同學們可以嘗試使用本方法舉一反三,分析一下其它框架,反復學習,加深印象。


我建了一個QQ群,歡迎對學習有興趣的同學加入。我們可以一起探討、深究、掌握那些我們會用到的技術,讓自己不至于太落伍。 這里寫圖片描述


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 荣成市| 江永县| 荔浦县| 望江县| 陆丰市| 囊谦县| 洛宁县| 延安市| 黄骅市| 连城县| 聊城市| 珲春市| 金平| 景泰县| 稻城县| 云林县| 永嘉县| 无锡市| 高碑店市| 太原市| 瑞金市| 尼勒克县| 宝兴县| 佛学| 汕头市| 崇信县| 山阴县| 夹江县| 措美县| 铜川市| 乳源| 河池市| 门源| 安溪县| 教育| 德昌县| 普洱| 吉木萨尔县| 介休市| 杭锦后旗| 衡阳市|