隨著業(yè)務(wù)的增長(zhǎng),加載圖片是不可避免的需求。從一開始的自己寫一個(gè) ImageLoader 到井噴似的第三方圖片加載庫(kù),當(dāng)然中間還時(shí)不時(shí)穿插著 asynctask ,三級(jí)緩存,LRU Cache等。那個(gè)時(shí)候想必大家都用過 nostra13/Android-Universal-Image-Loader , 大家都紛紛擁抱了它,確實(shí)時(shí)勢(shì)造英雄吧,所以我很欽佩作者。但三年前作者大概可能覺得實(shí)在是太累了的,宣布不再維護(hù)了的。在此期間也有一些優(yōu)秀的開源庫(kù)比如 square 出來(lái)的 Picasso ,優(yōu)雅的鏈?zhǔn)秸{(diào)用想必很多人選擇了擁抱他。后來(lái)Google在2014年的google I/O大會(huì)上發(fā)布的官方app中使用的 bumptech/glide 闖入大家的視野, Google 推薦大家圖片加載使用 Glide. 當(dāng)然 Glide 的使用方式也是仿照 Picasso 。所以幾乎沒有任何遷移成本,很多人也開始擁抱了 Glide. 當(dāng)然在此期間 Facebook 也不甘寂寞橫空出來(lái)開源了 fresco 。
為什么會(huì)選擇 Glide
為什么選擇 Glide ,前言中也提到了 畢竟是 Google 推薦的最佳選擇。除此之外也可以做一下簡(jiǎn)單的對(duì)比
Glide VS Picasso
雙胞胎兄弟之間的對(duì)比,使用方式相同,但 Glide 之所以勝出,不僅僅是 Google的推薦,更多應(yīng)該歸功于 GIF 的支持。 在沒有 Glide 之前,常用的做法就是寫了個(gè)自定義 view 然后 用一個(gè) media 去播放。有了 Glide 之后幾乎對(duì)于 GIF 無(wú)感知了的, 內(nèi)部已經(jīng)支持了的。可以像普通圖片那樣去加載并且顯示出來(lái)動(dòng)圖。
Glide VS Android-Universal-Image-Loader
雖然有再多的不舍,一個(gè)已經(jīng)不再維護(hù)的開源庫(kù),Android碎片化那么嚴(yán)重,我們自己維護(hù)起來(lái)還是要考慮成本的。所以 Glide 勝出。
Glide VS fresco
兩個(gè)都支持 GIF。所以 GIF 這一關(guān)pass掉。說到這里不得不提到一個(gè)頭疼的OOM問題,fresco 之所以很快闖入大家的視線,大概就是因?yàn)?Facebook 說他們使用了 native 內(nèi)存規(guī)避掉了 OutOfMemoryError 問題。而且官方還專門寫了個(gè)demo,把幾大流行的開源庫(kù)都集成進(jìn)去,為了說明自己的圖片加載庫(kù)加載同樣的圖片速度更快,內(nèi)存占用更低。所以 fresco 相比較于 Glide 的(官方)優(yōu)勢(shì)就是這兩點(diǎn): 內(nèi)存以及加載速度。但是我為什么依舊堅(jiān)持拋棄了 fresco ?
“ In Android 4.x and lower, Fresco puts images in a special region of Android memory. This lets your application run faster - and suffer the dreaded OutOfMemoryError much less often.” 官方的原話是這么說的,所以在高版本上面依舊使用的Java 內(nèi)存,所以不可避免依舊會(huì)占用內(nèi)存。
提到內(nèi)存,不得不說到另外一個(gè)笑話,fresco 最大只支持圖片文件大小為 2M 。記得有一次幫其他團(tuán)隊(duì)跟蹤問題,看到了 fresco 源碼中有一個(gè) 最大 size 2M 常量 。于是當(dāng)場(chǎng)找了一個(gè)10M的圖片作為測(cè)試。 Glide 正常顯示, fresco顯示黑屏。。。
使用方式上,fresco 推薦的是用他提供的 SimpleDraweeView . 這個(gè)方式意味著我們的遷移成本會(huì)非常的高,要改布局文件,其次還必須給定大?。ɑ蛘弑壤?當(dāng)然他也支持代碼來(lái)加載圖片,比如 DraweeHierarchy,但是寫起來(lái)還是真心很費(fèi)勁的,很不友好,改動(dòng)成本居高。
fresco 更多是native實(shí)現(xiàn)。所以需要對(duì)NDK有所了解,但個(gè)人對(duì)NDK不太了解,相比較于 Glide, 同樣遇到問題之后,修改源碼的成本,Glide 成本更可控。前者可能就不太好下手了的。
Glide 各種 BitmapTransformation,比如圓形,圓角等,更讓人喜歡。
這一點(diǎn)就當(dāng)隨意吐槽一下,當(dāng)然也可以說心疼一下 Facebook。因?yàn)樵跊]有 Android studio (gradle構(gòu)建)的情況下,想必大家都用的是 eclipse 吧。那么就意味著 fresco 得提供 Jar 包. 這一點(diǎn)當(dāng)時(shí)也是把很多人拒之門外了的,可笑的是當(dāng) Facebook 費(fèi)了老大勁的搞出來(lái) jar 包之后,大家早就紛紛轉(zhuǎn)戰(zhàn) gradle 構(gòu)建工程, 直接 maven 依賴?yán)?。大寫的尷尬?/p>
綜上所述,Glide 依舊勝出。
Glide 是如何解決圖片加載生命周期的?(精髓之一)(也是bug高發(fā)地帶)
當(dāng)一個(gè)界面離開之后,我們更希望當(dāng)前的圖片取消加載,那么 Glide 是怎么做到的呢?
Glide 的使用方式上,一定需要傳入一個(gè) context 給它。它為什么需要拿上下文呢?原因就是可以根據(jù)不同的上下文進(jìn)行處理,拿到 context (除了application context)之后,Glide做了一件很巧妙的事情,就是在這個(gè)界面上追加一個(gè) fragment,由于 fragment 添加到了 activity 上,是可以捕獲到生命周期的,因此可以在 destroy 的時(shí)候取消掉當(dāng)前context下的 glide對(duì)象中的加載任務(wù)。
為什么標(biāo)題后面說是 ‘也是bug高發(fā)地帶’ 呢? 因?yàn)閺膶?shí)現(xiàn)方式上,它是巧妙的利用了fragment的生命周期來(lái)實(shí)現(xiàn)的‘銷毀’動(dòng)作,那么就類似于另外一個(gè)高發(fā)bug,延時(shí)的匿名內(nèi)部類(網(wǎng)絡(luò)請(qǐng)求callback回來(lái)),界面已經(jīng)銷毀,所以當(dāng)前activity依附的glide也就銷毀了的,此時(shí)再嘗試加載圖片的話,就會(huì)crash。具體源碼中可以看到這里:
https://github.com/bumptech/glide/blob/master/library/src/main/java/com/bumptech/glide/Glide.java
@NonNull
private static RequestManagerRetriever getRetriever(@Nullable Context context) {
// Context could be null for other reasons (ie the user passes in null), but in practice it will
// only occur due to errors with the Fragment lifecycle.
Preconditions.checkNotNull(
context,
"You cannot start a load on a not yet attached View or a Fragment where getActivity() "
+ "returns null (which usually occurs when getActivity() is called before the Fragment "
+ "is attached or after the Fragment is destroyed).");
return Glide.get(context).getRequestManagerRetriever();
}
Glide 是如何做到鏈?zhǔn)降膬?yōu)雅調(diào)用?(精髓之二)
鏈?zhǔn)秸{(diào)用其實(shí)只不過是一種語(yǔ)法招數(shù)。它能讓你通過重用一個(gè)初始操作來(lái)達(dá)到用少量代碼表達(dá)復(fù)雜操作的目的。其實(shí)就是類似builder一樣,把this return 出去,一直可以調(diào)用自己的方法。所以也稱之為方法鏈。
// Some time in the future:
Glide.with(fragment)
.load(newUrl)
.into(target);
鏈?zhǔn)秸{(diào)用的好與不好:
- 編程性強(qiáng)
- 可讀性強(qiáng)
- 代碼簡(jiǎn)潔
- 對(duì)程序員的業(yè)務(wù)能力要求高
- 不太利于代碼調(diào)試
Glide 坑爹的 wrap_content 不支持的問題
官方說了的,不支持并且不建議imageview設(shè)置wrap_content。因?yàn)檫@樣 glide 不知道要加載多大的圖片給我們才好,在他的接口(Sizes and dimensions)中也有體現(xiàn)。普通的imageview其實(shí)也還好,如果放在列表(RecyclerView)中, 由于我們并不知道目標(biāo)圖片大小是多大的,所以我們選擇了wrap_content,那么在上下來(lái)回滾動(dòng)過程中,就會(huì)導(dǎo)致圖片一會(huì)大一會(huì)小的bug.
官方 issue 作者回答如下:
Don't use wrap_content.
Even if you don't use Glide, wrap_content necessarily means that the size of your views in RecyclerView are going to change from item to item. That's going to cause all sorts of UI weirdness.
One option is to try to obtain the image dimensions in whatever metadata you're using to populate the RecyclerView. Then you can set a fixed View size in onBindViewHolder so the view size at least doesn't change when the image is loaded. You're still likely to see weird scroll bar behavior though.
If nothing else, you can always pick a uniform size that's large enough for all items and use the same consistent size for every item.
For the image file size, you can downscale or upscale by setting the ImageView size manually to 150dp x 150dp. Ultimately either you need uniform view sizes or predetermined view sizes. There's nothing else that will prevent content from expanding or shrinking in your RecyclerView.
For the placeholder bit, I think that will be fixed by 648c58e, you can check by trying the 4.2.0-SNAPSHOT version: http://bumptech.github.io/glide/dev/snapshots.html.
so...還是建議我們指定圖片的大小。
Glide 坑爹的 support包 版本問題
為什么會(huì)有這個(gè)問題呢?其實(shí)剛才已經(jīng)提到了的 ,由于它用到了 fragmen t,那么自然就有版本沖突問題。support 包大家都懂的,不同的版本,差異可能巨大,有個(gè)段子就是說 Google 的 support 包 大概是招了個(gè)實(shí)習(xí)生寫的。不同的版本沖突可能會(huì)編譯不過,可能會(huì)有 ‘nosuchmethod’ 等等問題。
比如我們產(chǎn)線現(xiàn)在的用的是 Glide 版本是 4.3.1,之所以遲遲沒有升級(jí)到最新版本,就是因?yàn)楹竺娴陌姹?Glide采用了 27編譯。。而我們項(xiàng)目才25 。。。 中間這個(gè)編譯升級(jí)的風(fēng)險(xiǎn)。有點(diǎn)不可控。所以一直沒有升級(jí)上去。
所以建議,在升級(jí) Glide 版本的時(shí)候 看一下對(duì)應(yīng)版本源碼中依賴的 support 版本是多少。
寫在最后
之所以今天簡(jiǎn)單的跟大家聊一聊 Glide。其實(shí)也只是找了一個(gè)項(xiàng)目中用到的開源庫(kù)作為例子,想跟大家聊聊,當(dāng)項(xiàng)目中需要技術(shù)選型的時(shí)候,不能給的答案是:因?yàn)榇蠹叶荚谟冒。?/p>
而我更想知道的是,大家為什么會(huì)選擇它,不僅僅是人群中多看了你一眼, 而是從外表 API的“美”,再到內(nèi)在框架設(shè)計(jì)的 “美”。只有知其所以然,那么當(dāng)遇到坑的時(shí)候,才知道如何去解決它。而不是簡(jiǎn)單的“跟風(fēng)似的” “一見鐘情”。。。