前言
上一講Android圖片加載(一)——框架的對比分析中我們提到了列表控件在快速滑動(dòng)或者是網(wǎng)絡(luò)不好的時(shí)候,可能會出現(xiàn)圖片錯(cuò)位、重復(fù)、閃爍等問題,為了使用戶擁有良好的App使用體驗(yàn),不僅要快速加載圖片,而且還不能有過多的主線程I/O或頻繁的垃圾回收。因此,我們引入了圖片加載框架Glide。
一、簡介
Glide是一個(gè)被google所推薦的圖片加載庫,作者是bumptech。這個(gè)庫被廣泛運(yùn)用在google的開源項(xiàng)目中,包括2014年的google I/O大會上發(fā)布的官方app。
其特點(diǎn)包括但不限于:
- 使用簡明的流式語法API,它允許你在大部分情況下一行代碼搞定需求。
例如:
Glide.with(fragment)
.load(url)
.into(imageView);
- 性能好,充分考慮了Android圖片加載性能的兩個(gè)關(guān)鍵方面:圖片解碼的速度、解碼圖片帶來的資源壓力。
- 可以自動(dòng)、智能地下采樣和緩存, 以最小化存儲開銷和解碼次數(shù)。能夠積極的資源重用,例如字節(jié)數(shù)組和Bitmap對象,以最小化垃圾回收和堆碎片的影響。包含深度的生命周期集成,以確保僅優(yōu)先處理活躍的Fragment和Activity的請求,有利于應(yīng)用在必要時(shí)釋放資源以避免在后臺時(shí)被殺掉。
2. 使用
1. 添加gradle依賴
dependencies {
implementation 'com.github.bumptech.glide:glide:4.11.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0'
}
2. 基本方法
例如加載一個(gè)地址為url的圖片到uImageView中:
Glide.with(uContext).load(url).into(uImageView);
其中,uContext是上下文,這里還可以使用Activity、FragmentActivity、androidx.Fragment.app.Fragment等對象。with方法中傳入的實(shí)例會決定Glide加載圖片的生命周期,如果傳入的是Activity或者Fragment的實(shí)例,那么當(dāng)這個(gè)Activity或Fragment被銷毀的時(shí)候,圖片加載也會停止。如果傳入ApplicationContext,那么只有當(dāng)應(yīng)用程序被殺掉的時(shí)候,圖片加載才會停止。
此外,也可以通過clear取消不必要的加載,但是這并不是必須的操作,實(shí)際上,當(dāng)Glide.with()中傳入的Activity或者是Fragment實(shí)例被銷毀時(shí),Glide會自動(dòng)取消加載并回收資源。
3. 占位圖
- 顧名思義,占位圖就是指在圖片的加載過程中,我們先顯示一張臨時(shí)的圖片,等圖片加載出來了之后再替換成要加載的圖片。
Glide.with(this)
.load(url)
.placeholder(R.drawable.loading) //
.into(imageView);
- 除了加載占位圖之外,還有一種異常占位圖。異常占位圖是指,如果因?yàn)槟承┊惓G闆r導(dǎo)致圖片加載失敗,比如說手機(jī)網(wǎng)絡(luò)信號不好,這個(gè)時(shí)候就顯示這張異常占位圖。
一般通過串接error()方法來指定異常占位圖。 - null占位圖
正常情況下,我們的 load() 方法里面的 url 應(yīng)該不是 null 的,但是如果有可能為 null 的情況,你可以通過設(shè)置 fallback() 方法來顯示 url 為 null 的情況。
4. 加載指定大小的圖片
我們平時(shí)在加載圖片的時(shí)候很容易會造成內(nèi)存浪費(fèi)。比如說一張圖片的尺寸是1000X1000像素,但是我們界面上的ImageView可能只有200X200像素,這個(gè)時(shí)候如果你不對圖片進(jìn)行任何壓縮就直接讀取到內(nèi)存中,這就屬于內(nèi)存浪費(fèi)了,因?yàn)槌绦蛑懈揪陀貌坏竭@么高像素的圖片。
而使用Glide,我們完全不用擔(dān)心圖片內(nèi)存浪費(fèi),甚至是內(nèi)存溢出的問題。因?yàn)?strong>Glide從來都不會直接將圖片的完整尺寸全部加載到內(nèi)存中,而是用多少加載多少。Glide會自動(dòng)判斷ImageView的大小,然后只將這么大的圖片像素加載到內(nèi)存當(dāng)中(默認(rèn)情況下),幫助我們節(jié)省內(nèi)存開支。所以使用Glide,在絕大多數(shù)情況下我們都是不需要指定圖片大小的,因?yàn)镚lide會自動(dòng)根據(jù)ImageView的大小來指定圖片的大小。
不過Glide仍然支持給圖片指定一個(gè)固定的大小的功能(使用override),指定后不管imagview的大小是多少,Glide只會講圖片加載成設(shè)定的尺寸。此外,如果不想讓Glide計(jì)算并壓縮要加載的圖片,要加載原始圖片的大小,可以使用override(Target.SIZE_ORIGINAL,Target.SIZE_ORIGINAL)作為options添加上去,但是這種加載方式是不被建議的,因?yàn)檎加玫膬?nèi)存會相差很大。
5. 加載不同格式:Bitmap、Gif、Drawable、File
在 Glide4.0 中有一個(gè) RequestBuilders 的泛型類,用于指定加載資源的格式,可以通過下面四種方法指定,得到不同的 RequestBuilders 對象:
- asDrawable() 得到 RequestBuilders
- asGif() 得到 RequestBuilders
- asBitmap() 得到 RequestBuilders
- asFile() 得到 RequestBuilders
默認(rèn)情況下,如果我們不指定,則是得到一個(gè) RequestBuilders 對象。
例如,代碼中使用:
Glide.with(this)
.asBitmap()
.load(url)
.into(userAvatarImageView);
表示加載bitmap
6. 換一種方式加載圖片
正常情況下,我們都是通過這行代碼來加載圖片的:
Glide.with(uContext).load(url).into(uImageView);
但是有時(shí)候,為了對加載出來的圖像進(jìn)行一些處理,我們還可以使用Glide為我們提供的一個(gè)封裝,封裝成一個(gè)Target或者Target的子類,例如SimpleTarget。
Glide.with(this)
.asBitmap()
.load(loginInfo.getAvatarUrl())
.into(new SimpleTarget<Bitmap>() {
@Override
public void onResourceReady(Bitmap resource, Transition<? super Bitmap> transition) {
userAvatarImageView.setImageBitmap(resource);
LoginUser.bitmap=resource;
}});
我們使用SimpleTarget可以將Glide加載出來的圖片對象獲取到并做一些處理,而不是像之前那樣只能顯示圖片。比如說,這里獲取到加載出來的圖片對象之后不僅將它顯示到一個(gè)ImageView當(dāng)中,還將它存儲到LoginUser.bitmap中作為一個(gè)靜態(tài)變量,方便后續(xù)頁面的使用。
7. 使用Generated API
Generated API模式的設(shè)計(jì)出于以下兩個(gè)目的:
- 集成庫可以為Generated API擴(kuò)展自定義選項(xiàng)。
- 在Appplication模塊中可以將常用的選項(xiàng)組打包成一個(gè)選項(xiàng)在Gnenrated API中使用。
我在代碼中引入了Generated API主要出于目的2,雖然可以通過手動(dòng)創(chuàng)建RequestOptions子類的方式來完成,但是這種用法降低了API使用的流暢性,如果使用 Generated API ,可以使我們不用去創(chuàng)建 RequestOptions 就可以直接使用 placeholder、error 等方法。簡單理解一下就是增加代碼復(fù)用行,便于后續(xù)的代碼維護(hù)。
下面通過實(shí)際的代碼來展示Generated API的使用:
- 使用Generated API必須首先引入下列依賴:
annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0'
- 在Application模塊下創(chuàng)建一個(gè)新的類繼承自GlideModule,并給類添加@GlideModule注解。
@GlideModule
public class MyAppGlideModule extends AppGlideModule {
@Override
public void applyOptions(Context context, GlideBuilder builder) {
builder.setDefaultRequestOptions(
new RequestOptions()
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
.placeholder(R.drawable.logo)
.fallback(R.drawable.fail)
.format(DecodeFormat.PREFER_ARGB_8888));
}
}
例如,上面的MyAppGlideModule中我們使用applyOptions的方法配置了一些options,Generated API默認(rèn)名字為GlideApp, 把原來的Glide.with替換成GlideApp.with,就可以使用該API去完成加載工作。
8. 集成網(wǎng)絡(luò)框架
Glide默認(rèn)使用的是HTTPUrlConnection,支持集成Volley,Okhttp等網(wǎng)絡(luò)棧。
為了使用比Android提供的HttpUrlConnection更nice的API,以及想要確保網(wǎng)絡(luò)層代碼不依賴于app所在的設(shè)備上Android OS版本,所以選擇Okhttp。其次,我們已經(jīng)在獲取遠(yuǎn)程github數(shù)據(jù)的時(shí)候使用了Okhttp,那么我們繼續(xù)選擇為Glide使用OKhttp。
具體使用
添加一個(gè)對OKhttp集成庫的依賴:compile "com.github.bumptech.glide:okhttp3-integration:4.11.0"添加Okhttp集成庫的Gradle依賴將使Glide自動(dòng)開始使用Okhttp來加載所有來自http和httpsURL的圖片。
9. 圖片的緩存處理
Glide的緩存包含內(nèi)存緩存和硬盤緩存。默認(rèn)情況下,Glide自動(dòng)就是開啟內(nèi)存緩存和磁盤緩存的。內(nèi)存緩存的主要作用是防止應(yīng)用重復(fù)將圖片數(shù)據(jù)讀取到內(nèi)存當(dāng)中,而硬盤緩存的主要作用是防止應(yīng)用從網(wǎng)絡(luò)或其他地方重復(fù)下載和讀取數(shù)據(jù)。(??這兩句話難道不是一個(gè)意思嗎)
9.1 內(nèi)存緩存
- 默認(rèn)情況下,Glide自動(dòng)就是開啟內(nèi)存緩存的。也就是說,當(dāng)我們使用Glide加載了一張圖片之后,這張圖片就會被緩存到內(nèi)存當(dāng)中,只要在它還沒從內(nèi)存中被清除之前,下次使用Glide再加載這張圖片都會直接從內(nèi)存當(dāng)中讀取,而不用重新從網(wǎng)絡(luò)或硬盤上讀取了,這樣無疑就可以大幅度提升圖片的加載效率。比方說你在一個(gè)RecyclerView當(dāng)中反復(fù)上下滑動(dòng),RecyclerView中只要是Glide加載過的圖片都可以直接從內(nèi)存當(dāng)中迅速讀取并展示出來,從而大大提升了用戶體驗(yàn)。還可以使用skipMemoryCache()方法并傳入true,就表示禁用掉Glide的內(nèi)存緩存功能。
- 從內(nèi)存緩存中讀取圖片的邏輯大致是:如果能從內(nèi)存緩存當(dāng)中讀取到要加載的圖片,那么就直接進(jìn)行回調(diào),如果讀取不到的話,才會開啟線程執(zhí)行后面的圖片加載邏輯。
- Glide的圖片加載過程會調(diào)用兩個(gè)方法來獲取內(nèi)存緩存,LoadFromCache()和LoadFromActiveResource()。這兩個(gè)方法中一個(gè)使用的就是LruCache算法,另一個(gè)使用的就是弱引用。activeResources就是一個(gè)弱引用的HashMap,用來緩存正在使用中的圖片,使用activeResources來緩存正在使用中的圖片,可以保護(hù)這些圖片不會被LruCache算法回收掉??偨Y(jié):正在使用中的圖片使用弱引用來進(jìn)行緩存,不在使用中的圖片使用LruCache來進(jìn)行緩存。
9.2 硬盤緩存
- 當(dāng)我們使用Glide去加載一張圖片的時(shí)候,Glide默認(rèn)并不會將原始圖片展示出來,而是會對圖片進(jìn)行壓縮和轉(zhuǎn)換。Glide默認(rèn)情況下在硬盤緩存的就是轉(zhuǎn)換過后的圖片。
- 可以通過.diskCacheStrategy來設(shè)置磁盤的緩存策略
包括下面這些:
//DiskCacheStrategy.ALL 既緩存原始圖片,也緩存轉(zhuǎn)換過后的圖片。
// DiskCacheStrategy.NONE 不緩存任何內(nèi)容
// DiskCacheStrategy.DATA 在資源解碼前就將原始數(shù)據(jù)寫入磁盤緩存(即只緩存原始圖片)
// DiskCacheStrategy.RESOURCE 在資源解碼后將數(shù)據(jù)寫入磁盤緩存,即經(jīng)過縮放等轉(zhuǎn)換后的圖片資源(即只緩存轉(zhuǎn)換過后的圖片)。
// DiskCacheStrategy.AUTOMATIC 讓Glide根據(jù)圖片資源智能地選擇使用哪一種緩存策略。
//默認(rèn)采用DiskCacheStrategy.AUTOMATIC策略
/*-------------------------------------------------------------------------------*/
//源碼 RequestOptions.java
private DiskCacheStrategy diskCacheStrategy = DiskCacheStrategy.AUTOMATIC;
三、從源碼的角度理解Glide的執(zhí)行流程
上面介紹的使用過程中實(shí)際上有小部分已經(jīng)討論到了Glide的源碼,但是由于Glide的源碼比較復(fù)雜,對于筆者這種菜雞來說是比較難以自己下手的,這里引用一下《第一行代碼》作者郭霖大神的原話:
閱讀源碼到底困難嗎?這個(gè)當(dāng)然主要還是要視具體的源碼而定。比如同樣是圖片加載框架,我讀Volley的源碼時(shí)就感覺酣暢淋漓,并且對Volley的架構(gòu)設(shè)計(jì)和代碼質(zhì)量深感佩服。讀Glide的源碼時(shí)卻讓我相當(dāng)痛苦,代碼極其難懂。當(dāng)然這里我并不是說Glide的代碼寫得不好,只是因?yàn)镚lide和復(fù)雜程度和Volley完全不是在一個(gè)量級上的。
但是郭霖大神仍然給出了一套更容易被理解的源碼解析,感興趣的同學(xué)可以去看:
Android圖片加載框架最全解析(二),從源碼的角度理解Glide的執(zhí)行流程
四、Glide4.0的注意事項(xiàng)
在4.0中不用像3.X需要在AndroidManifest.xml配置GlideModule,而是通過注解繼承AppGlideModule的子類來配置。
使用GlideApp代替Glide,asBitmap、asGif、asDrawable、asFile都要放到load之前(glide3.7.0都是要在load之后調(diào)用)。