在接下來的幾篇文章中,我們會(huì)對 Android 中常用的圖片加載框架 Glide 進(jìn)行分析雁竞。在本篇文章中钦椭,我們先通過介紹 Glide 的幾種常用的配置方式來了解 Glide 的部分源碼拧额。后續(xù)的文中,我們會(huì)對 Glide 的源碼進(jìn)行更詳盡的分析彪腔。
對于 Glide侥锦,相信多數(shù) Android 開發(fā)者并不陌生,在本文中德挣,我們不打算對其具體使用做介紹恭垦,你可以通過查看官方文檔進(jìn)行學(xué)習(xí)。Glide 的 API 設(shè)計(jì)非常人性化格嗅,上手也很容易番挺。
在這篇文中中我們主要介紹兩種常用的 Glide 的配置方式,并以此為基礎(chǔ)來分析 Glide 的工作原理吗浩。在本文中我們將會(huì)介紹的內(nèi)容有:
- 通過自定義 GlideModule 指定 Glide 的緩存路徑和緩存空間的大薪ㄜ健;
- 帶有時(shí)間戳的圖片的緩存命中問題的解決懂扼;
- 在 Glide 中使用 OkHttp 作為網(wǎng)絡(luò)中的圖片資源加載方式的實(shí)現(xiàn)禁荸。
1、自定義圖片加載方式
有時(shí)候阀湿,我們需要對 Glide 進(jìn)行配置來使其能夠?qū)μ厥忸愋偷膱D片進(jìn)行加載和緩存赶熟。考慮這么一個(gè)場景:圖片路徑中帶有時(shí)間戳陷嘴。這種情形比較場景映砖,即有時(shí)候我們通過為圖片設(shè)置時(shí)間戳來讓圖片鏈接在指定的時(shí)間過后失效,從而達(dá)到數(shù)據(jù)保護(hù)的目的灾挨。
在這種情況下邑退,我們需要解決幾個(gè)問題:1).需要配置緩存的 key,不然緩存無法命中劳澄,每次都需要從網(wǎng)絡(luò)中進(jìn)行獲鹊丶肌;2).根據(jù)正確的鏈接秒拔,從網(wǎng)絡(luò)中獲取圖片并展示莫矗。
我們可以使用自定義配置 Glide 的方式來解決這個(gè)問題。
1.1 帶時(shí)間戳圖片加載的實(shí)現(xiàn)
1.1.1 MyAppGlideModule
首先砂缩,按照下面的方式自定義 GlideModule
作谚,
@GlideModule
public class MyAppGlideModule extends AppGlideModule {
/**
* 配置圖片緩存的路徑和緩存空間的大小
*/
@Override
public void applyOptions(Context context, GlideBuilder builder) {
builder.setDiskCache(new InternalCacheDiskCacheFactory(context, Constants.DISK_CACHE_DIR, 100 << 20));
}
/**
* 注冊指定類型的源數(shù)據(jù),并指定它的圖片加載所使用的 ModelLoader
*/
@Override
public void registerComponents(Context context, Glide glide, Registry registry) {
glide.getRegistry().append(CachedImage.class, InputStream.class, new ImageLoader.Factory());
}
/**
* 是否啟用基于 Manifest 的 GlideModule庵芭,如果沒有在 Manifest 中聲明 GlideModule妹懒,可以通過返回 false 禁用
*/
@Override
public boolean isManifestParsingEnabled() {
return false;
}
}
在上面的代碼中,我們通過覆寫 registerComponents()
方法双吆,并調(diào)用 Glide 的 Registry
的 append()
方法來向 Glide 增加我們的自定義圖片類型的加載方式眨唬。(如果替換某種資源加載方式則需要使用 replace()
方法滔悉,此外 Registry
還有其他的方法,可以通過查看源碼進(jìn)行了解单绑。)
在上面的方法中,我們新定義了兩個(gè)類曹宴,分別是 CachedImage
和 ImageLoader
搂橙。CachedImage
就是我們的自定義資源類型,ImageLoader
是該資源類型的加載方式笛坦。當(dāng)進(jìn)行圖片加載的時(shí)候区转,會(huì)根據(jù)資源的類型找到該圖片加載方式,然后使用它來進(jìn)行圖片加載版扩。
1.1.2 CachedImage
我們通過該類的構(gòu)造方法將原始的圖片的鏈接傳入废离,并通過該類的 getImageId()
方法來返回圖片緩存的鍵,在該方法中我們從圖片鏈接中過濾掉時(shí)間戳:
public class CachedImage {
private final String imageUrl;
public CachedImage(String imageUrl) {
this.imageUrl = imageUrl;
}
/**
* 原始的圖片的 url礁芦,用來從網(wǎng)絡(luò)中加載圖片
*/
public String getImageUrl() {
return imageUrl;
}
/**
* 提取時(shí)間戳之前的部分作為圖片的 key蜻韭,這個(gè) key 將會(huì)被用作緩存的 key,并用來從緩存中找緩存數(shù)據(jù)
*/
public String getImageId() {
if (imageUrl.contains("?")) {
return imageUrl.substring(0, imageUrl.lastIndexOf("?"));
} else {
return imageUrl;
}
}
}
1.1.3 ImageLoader
CachedImage
的加載通過 ImageLoader
實(shí)現(xiàn)柿扣。正如上面所說的肖方,我們將 CachedImage
的 getImageId()
方法得到的字符串作為緩存的鍵,然后使用默認(rèn)的 HttpUrlFetcher
作為圖片的加載方式未状。
public class ImageLoader implements ModelLoader<CachedImage, InputStream> {
/**
* 在這個(gè)方法中俯画,我們使用 ObjectKey 來設(shè)置圖片的緩存的鍵
*/
@Override
public LoadData<InputStream> buildLoadData(CachedImage cachedImage, int width, int height, Options options) {
return new LoadData<>(new ObjectKey(cachedImage.getImageId()),
new HttpUrlFetcher(new GlideUrl(cachedImage.getImageUrl()), 15000));
}
@Override
public boolean handles(CachedImage cachedImage) {
return true;
}
public static class Factory implements ModelLoaderFactory<CachedImage, InputStream> {
@Override
public ModelLoader<CachedImage, InputStream> build(MultiModelLoaderFactory multiFactory) {
return new ImageLoader();
}
@Override
public void teardown() { /* no op */ }
}
}
1.1.4 使用
當(dāng)我們按照上面的方式配置完畢之后就可以在項(xiàng)目中使用 CachedImage
來加載圖片了:
GlideApp.with(getContext())
.load(new CachedImage(user.getAvatarUrl()))
.into(getBinding().ivAccount);
這里,當(dāng)有加載圖片需求的時(shí)候司草,都會(huì)把原始的圖片鏈接使用 CachedImage
包裝一層之后再進(jìn)行加載艰垂,其他的步驟與 Glide 的基本使用方式一致。
1.2 原理分析
當(dāng)我們啟用了 @GlideModule
注解之后會(huì)在編譯期間生成 GeneratedAppGlideModuleImpl
埋虹。從下面的代碼中可以看出猜憎,它實(shí)際上就是對我們自定義的 MyAppGlideModule
做了一層包裝。這么去做的目的就是它可以通過反射來尋找 GeneratedAppGlideModuleImpl
吨岭,并通過調(diào)用 GeneratedAppGlideModuleImpl
的方法來間接調(diào)用我們的 MyAppGlideModule
拉宗。本質(zhì)上是一種代理模式的應(yīng)用:
final class GeneratedAppGlideModuleImpl extends GeneratedAppGlideModule {
private final MyAppGlideModule appGlideModule;
GeneratedAppGlideModuleImpl() {
appGlideModule = new MyAppGlideModule();
}
@Override
public void applyOptions(Context context, GlideBuilder builder) {
appGlideModule.applyOptions(context, builder);
}
@Override
public void registerComponents(Context context, Glide glide, Registry registry) {
appGlideModule.registerComponents(context, glide, registry);
}
@Override
public boolean isManifestParsingEnabled() {
return appGlideModule.isManifestParsingEnabled();
}
@Override
public Set<Class<?>> getExcludedModuleClasses() {
return Collections.emptySet();
}
@Override
GeneratedRequestManagerFactory getRequestManagerFactory() {
return new GeneratedRequestManagerFactory();
}
}
下面就是 GeneratedAppGlideModuleImpl
被用到的地方:
當(dāng)我們實(shí)例化單例的 Glide 的時(shí)候,會(huì)調(diào)用下面的方法來通過反射獲取該實(shí)現(xiàn)類(所以對生成類的混淆就是必不可少的):
Class<GeneratedAppGlideModule> clazz = (Class<GeneratedAppGlideModule>)
Class.forName("com.bumptech.glide.GeneratedAppGlideModuleImpl");
當(dāng)?shù)玫搅酥髸?huì)調(diào)用 GeneratedAppGlideModule
的各個(gè)方法辣辫。這樣我們的自定義 GlideModule
的方法就被觸發(fā)了旦事。(下面的方法比較重要,我們自定義 Glide 的時(shí)候許多的配置都能夠從下面的源碼中尋找到答案急灭,后文中我們?nèi)匀粫?huì)提到這個(gè)方法)
private static void initializeGlide(Context context, GlideBuilder builder) {
Context applicationContext = context.getApplicationContext();
// 利用反射獲取 GeneratedAppGlideModuleImpl
GeneratedAppGlideModule annotationGeneratedModule = getAnnotationGeneratedGlideModules();
// 從 Manifest 中獲取 GlideModule
List<com.bumptech.glide.module.GlideModule> manifestModules = Collections.emptyList();
if (annotationGeneratedModule == null || annotationGeneratedModule.isManifestParsingEnabled()) {
manifestModules = new ManifestParser(applicationContext).parse();
}
// 獲取被排除掉的 GlideModule
if (annotationGeneratedModule != null
&& !annotationGeneratedModule.getExcludedModuleClasses().isEmpty()) {
Set<Class<?>> excludedModuleClasses = annotationGeneratedModule.getExcludedModuleClasses();
Iterator<com.bumptech.glide.module.GlideModule> iterator = manifestModules.iterator();
while (iterator.hasNext()) {
com.bumptech.glide.module.GlideModule current = iterator.next();
if (!excludedModuleClasses.contains(current.getClass())) {
continue;
}
iterator.remove();
}
}
// 應(yīng)用 GlideModule姐浮,我們自定義 GlideModuel 的方法會(huì)在這里被調(diào)用
RequestManagerRetriever.RequestManagerFactory factory = annotationGeneratedModule != null
? annotationGeneratedModule.getRequestManagerFactory() : null;
builder.setRequestManagerFactory(factory);
for (com.bumptech.glide.module.GlideModule module : manifestModules) {
module.applyOptions(applicationContext, builder);
}
if (annotationGeneratedModule != null) {
annotationGeneratedModule.applyOptions(applicationContext, builder);
}
// 構(gòu)建 Glide 對象
Glide glide = builder.build(applicationContext);
for (com.bumptech.glide.module.GlideModule module : manifestModules) {
module.registerComponents(applicationContext, glide, glide.registry);
}
if (annotationGeneratedModule != null) {
annotationGeneratedModule.registerComponents(applicationContext, glide, glide.registry);
}
applicationContext.registerComponentCallbacks(glide);
Glide.glide = glide;
}
再回到之前的自定義 GlideModule 部分代碼中:
public void applyOptions(Context context, GlideBuilder builder) {
builder.setDiskCache(new InternalCacheDiskCacheFactory(context, Constants.DISK_CACHE_DIR, 100 << 20));
}
這里的 applyOptions()
方法允許我們對 Glide 進(jìn)行自定義。從 initializeGlide()
方法中葬馋,我們也看出卖鲤,這里的 GlideBuilder
也就是 initializeGlide()
方法中傳入的 GlideBuilder
肾扰。這里使用了構(gòu)建者模式,GlideBuilder
是構(gòu)建者的實(shí)例蛋逾。所以集晚,我們可以通過調(diào)用 GlideBuilder
的方法來對 Glide 進(jìn)行自定義。
在上面的自定義 GlideModule 中区匣,我們通過構(gòu)建者來指定了 Glide 的緩存大小和緩存路徑偷拔。 GlideBuilder
還提供了一些其他的方法,我們可以通過查看源碼了解亏钩,并調(diào)用這些方法來自定義 Glide.
2莲绰、在 Glide 中使用 OkHttp
Glide 默認(rèn)使用 HttpURLConnection
實(shí)現(xiàn)網(wǎng)絡(luò)當(dāng)中的圖片的加載。我們可以通過對 Glide 進(jìn)行配置來使用 OkHttp 進(jìn)行網(wǎng)絡(luò)圖片加載姑丑。
首先蛤签,我們需要引用如下依賴:
api ('com.github.bumptech.glide:okhttp3-integration:4.8.0') {
transitive = false
}
該類庫中提供了基于 OkHttp 的 ModelLoader
和 DataFetcher
實(shí)現(xiàn)。它們是 Glide 圖片加載環(huán)節(jié)中的重要組成部分栅哀,我們會(huì)在后面介紹源碼和 Glide 的架構(gòu)的時(shí)候介紹它們被設(shè)計(jì)的意圖及其作用震肮。
然后,我們需要在自定義的 GlideModule
中注冊網(wǎng)絡(luò)圖片加載需要的組件留拾,即在 registerComponents()
方法中替換 GlideUrl
的加載的默認(rèn)實(shí)現(xiàn):
@GlideModule
@Excludes(value = {com.bumptech.glide.integration.okhttp3.OkHttpLibraryGlideModule.class})
public class MyAppGlideModule extends AppGlideModule {
private static final String DISK_CACHE_DIR = "Glide_cache";
private static final long DISK_CACHE_SIZE = 100 << 20; // 100M
@Override
public void applyOptions(@NonNull Context context, @NonNull GlideBuilder builder) {
builder.setDiskCache(new InternalCacheDiskCacheFactory(context, DISK_CACHE_DIR, DISK_CACHE_SIZE));
}
@Override
public void registerComponents(@NonNull Context context, @NonNull Glide glide, @NonNull Registry registry) {
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS)
.writeTimeout(10, TimeUnit.SECONDS)
.readTimeout(15, TimeUnit.SECONDS)
.eventListener(new EventListener() {
@Override
public void callStart(Call call) {
// 輸出日志钙蒙,用于確認(rèn)使用了我們配置的 OkHttp 進(jìn)行網(wǎng)絡(luò)請求
LogUtils.d(call.request().url().toString());
}
})
.build();
registry.replace(GlideUrl.class, InputStream.class, new Factory(okHttpClient));
}
@Override
public boolean isManifestParsingEnabled() {
// 不使用 Manifest 中的 GlideModule
return false;
}
}
這樣我們通過自己的配置指定網(wǎng)絡(luò)中圖片加載需要使用 OkHttp. 并且自定義了 OkHttp 的超時(shí)時(shí)間等參數(shù)。按照上面的方式我們可以在 Glide 中使用 OkHttp 來加載網(wǎng)絡(luò)中的圖片了间驮。
不過躬厌,當(dāng)我們在項(xiàng)目中引用了 okhttp3-integration
的依賴之后惑淳,不進(jìn)行上述配置一樣可以使用 OkHttp 來進(jìn)行網(wǎng)絡(luò)圖片加載的犁跪。這是因?yàn)樯鲜鲆蕾嚨陌幸呀?jīng)提供了一個(gè)自定義的 GlideModule,即 OkHttpLibraryGlideModule
涛菠。該類使用了 @GlideModule
注解屹篓,并且已經(jīng)指定了網(wǎng)絡(luò)圖片加載使用 OkHttp疙渣。所以,當(dāng)我們不自定義 GlideModule 的時(shí)候堆巧,只使用它一樣可以在 Glide 中使用 OkHttp.
如果我們使用了自定義的 GlideModule妄荔,當(dāng)我們編譯的時(shí)候會(huì)看到 GeneratedAppGlideModuleImpl
中的 registerComponents()
方法定義如下:
@Override
public void registerComponents(@NonNull Context context, @NonNull Glide glide, @NonNull Registry registry) {
new OkHttpLibraryGlideModule().registerComponents(context, glide, registry);
appGlideModule.registerComponents(context, glide, registry);
}
這里先調(diào)用了 OkHttpLibraryGlideModule
的 registerComponents()
方法,然后調(diào)用了我們自定義的 GlideModule 的 registerComponents()
方法谍肤,只是啦租,我們的 GlideModule 的 registerComponents()
方法會(huì)覆蓋掉 OkHttpLibraryGlideModule
中的實(shí)現(xiàn)。(因?yàn)槲覀兊?GlideModule 的 registerComponents()
方法中調(diào)用的是 Registry
的 replace()
方法荒揣,會(huì)替換之前的效果篷角。)
如果不希望多此一舉,我們可以直接在自定義的 GlideModule 中使用 @Excludes
注解系任,并指定 OkHttpLibraryGlideModule
來直接排除該類恳蹲。這樣 GeneratedAppGlideModuleImpl
中的 registerComponents()
方法將只使用我們自定義的 GlideModule. 以下是排除之后生成的類中 registerComponents()
方法的實(shí)現(xiàn):
@Override
public void registerComponents(@NonNull Context context, @NonNull Glide glide, @NonNull Registry registry) {
appGlideModule.registerComponents(context, glide, registry);
}
3虐块、總結(jié)
在本文中,我們通過介紹 Glide 的兩種常見的配置方式來分析了 Glide 的部分源碼實(shí)現(xiàn)嘉蕾。在這部分中贺奠,我們重點(diǎn)介紹了初始化 Glide 的并獲取 GlideModule
的過程,以及與圖片資源的時(shí)候相關(guān)的 ModelLoader
等的源碼错忱。了解這部分內(nèi)容是比較重要的敞嗡,因?yàn)樗鼈兪潜┞督o用戶的 API 接口,比較常用航背;并且對這些類簡單了解之后能夠不至于在隨后分析 Glide 整個(gè)加載流程的時(shí)候迷路。
這里我們對上面兩種配置方式中涉及到的類進(jìn)行一個(gè)分析棱貌。如下圖所示
當(dāng)我們初始化 Glide 的時(shí)候會(huì)使用 Registry
的 append()
等一系列的方法構(gòu)建資源類型-加載方式-輸出類型
的一個(gè)映射玖媚,然后當(dāng)我們使用 Glide 進(jìn)行記載的時(shí)候,會(huì)先根據(jù)資源類型找到對應(yīng)的加載方式婚脱,然后使用該加載方式從指定的數(shù)據(jù)源中加載數(shù)據(jù)今魔,并將其轉(zhuǎn)換成指定的輸出類型。
以上面我們自定義圖片加載方式的過程為例障贸,這里我們自定義了一個(gè)資源類型 CacheImage
错森,并通過自定義 GlideModule 指定了它的加載實(shí)現(xiàn)是我們自定義的 ImageLoader
類。然后篮洁,在我們自定義的 ImageLoader 中涩维,我們指定了獲取該資源的緩存的鍵的方式和從數(shù)據(jù)源中記載數(shù)據(jù)的具體實(shí)現(xiàn) HttpUrlFetcher
。這樣袁波,當(dāng) Glide 要加載某個(gè) CacheImage 的時(shí)候瓦阐,會(huì)先使用該緩存的鍵嘗試從緩存中獲取,拿不到結(jié)果之后使用 HttpUrlFetcher
從網(wǎng)絡(luò)當(dāng)中獲取數(shù)據(jù)篷牌。從網(wǎng)絡(luò)中獲取數(shù)據(jù)的時(shí)候會(huì)得到 InputStream睡蟋,最后,再調(diào)用一個(gè)回調(diào)類枷颊,使用 BitmapFactory 從 InputStream 中獲取 Bitmap 并將其顯示到 ImageView 上面戳杀,這樣就完成了整個(gè)圖片加載的流程。
從上文的分析中夭苗,我們可以總結(jié)出 Glide 的幾個(gè)設(shè)計(jì)人性的地方:
- 使用代理類包裝自定義 GlideModule信卡,然后可以使用發(fā)射獲取該代理類,并通過調(diào)用代理類的方法來間接調(diào)用我們的 GlideModuel题造;
- 構(gòu)建
資源類型-加載方式-輸出類型
映射的時(shí)候使用工廠方法而不是通過某個(gè)類建立一對一映射坐求。
上面我們通過 Glide 的幾種配置方式簡單介紹了 Glide 的圖片加載流程。其實(shí)際的執(zhí)行過程遠(yuǎn)比我們上述過程更加復(fù)雜晌梨。在下文中我們會(huì)對 Glide 的圖片加載的主流程進(jìn)行分析桥嗤。歡迎繼續(xù)關(guān)注和閱讀须妻!
如果您喜歡我的文章,可以在以下平臺(tái)關(guān)注我:
- 博客:https://shouheng88.github.io/
- 掘金:https://juejin.im/user/585555e11b69e6006c907a2a
- Github:https://github.com/Shouheng88
- CSDN:https://blog.csdn.net/github_35186068
- 微博:https://weibo.com/u/5401152113
更多文章:Gihub: Android-notes