在接下來的幾篇文章中,咱們會對 Android 中經常使用的圖片加載框架 Glide 進行分析。在本篇文章中,咱們先經過介紹 Glide 的幾種經常使用的配置方式來了解 Glide 的部分源碼。後續的文中,咱們會對 Glide 的源碼進行更詳盡的分析。java
對於 Glide,相信多數 Android 開發者並不陌生,在本文中,咱們不打算對其具體使用作介紹,你能夠經過查看官方文檔進行學習。Glide 的 API 設計很是人性化,上手也很容易。git
在這篇文中中咱們主要介紹兩種經常使用的 Glide 的配置方式,並以此爲基礎來分析 Glide 的工做原理。在本文中咱們將會介紹的內容有:github
有時候,咱們須要對 Glide 進行配置來使其可以對特殊類型的圖片進行加載和緩存。考慮這麼一個場景:圖片路徑中帶有時間戳。這種情形比較場景,即有時候咱們經過爲圖片設置時間戳來讓圖片連接在指定的時間事後失效,從而達到數據保護的目的。api
在這種狀況下,咱們須要解決幾個問題:1).須要配置緩存的 key,否則緩存沒法命中,每次都須要從網絡中進行獲取;2).根據正確的連接,從網絡中獲取圖片並展現。緩存
咱們可使用自定義配置 Glide 的方式來解決這個問題。網絡
首先,按照下面的方式自定義 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));
}
/** * 註冊指定類型的源數據,並指定它的圖片加載所使用的 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()
方法,並調用 Glide 的 Registry
的 append()
方法來向 Glide 增長咱們的自定義圖片類型的加載方式。(若是替換某種資源加載方式則須要使用 replace()
方法,此外 Registry
還有其餘的方法,能夠經過查看源碼進行了解。)app
在上面的方法中,咱們新定義了兩個類,分別是 CachedImage
和 ImageLoader
。CachedImage
就是咱們的自定義資源類型,ImageLoader
是該資源類型的加載方式。當進行圖片加載的時候,會根據資源的類型找到該圖片加載方式,而後使用它來進行圖片加載。框架
咱們經過該類的構造方法將原始的圖片的連接傳入,並經過該類的 getImageId()
方法來返回圖片緩存的鍵,在該方法中咱們從圖片連接中過濾掉時間戳:ide
public class CachedImage {
private final String imageUrl;
public CachedImage(String imageUrl) {
this.imageUrl = imageUrl;
}
/** * 原始的圖片的 url,用來從網絡中加載圖片 */
public String getImageUrl() {
return imageUrl;
}
/** * 提取時間戳以前的部分做爲圖片的 key,這個 key 將會被用做緩存的 key,並用來從緩存中找緩存數據 */
public String getImageId() {
if (imageUrl.contains("?")) {
return imageUrl.substring(0, imageUrl.lastIndexOf("?"));
} else {
return imageUrl;
}
}
}
複製代碼
CachedImage
的加載經過 ImageLoader
實現。正如上面所說的,咱們將 CachedImage
的 getImageId()
方法獲得的字符串做爲緩存的鍵,而後使用默認的 HttpUrlFetcher
做爲圖片的加載方式。
public class ImageLoader implements ModelLoader<CachedImage, InputStream> {
/** * 在這個方法中,咱們使用 ObjectKey 來設置圖片的緩存的鍵 */
@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 */ }
}
}
複製代碼
當咱們按照上面的方式配置完畢以後就能夠在項目中使用 CachedImage
來加載圖片了:
GlideApp.with(getContext())
.load(new CachedImage(user.getAvatarUrl()))
.into(getBinding().ivAccount);
複製代碼
這裏,當有加載圖片需求的時候,都會把原始的圖片連接使用 CachedImage
包裝一層以後再進行加載,其餘的步驟與 Glide 的基本使用方式一致。
當咱們啓用了 @GlideModule
註解以後會在編譯期間生成 GeneratedAppGlideModuleImpl
。從下面的代碼中能夠看出,它實際上就是對咱們自定義的 MyAppGlideModule
作了一層包裝。這麼去作的目的就是它能夠經過反射來尋找 GeneratedAppGlideModuleImpl
,並經過調用 GeneratedAppGlideModuleImpl
的方法來間接調用咱們的 MyAppGlideModule
。本質上是一種代理模式的應用:
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
被用到的地方:
當咱們實例化單例的 Glide 的時候,會調用下面的方法來經過反射獲取該實現類(因此對生成類的混淆就是必不可少的):
Class<GeneratedAppGlideModule> clazz = (Class<GeneratedAppGlideModule>)
Class.forName("com.bumptech.glide.GeneratedAppGlideModuleImpl");
複製代碼
當獲得了以後會調用 GeneratedAppGlideModule
的各個方法。這樣咱們的自定義 GlideModule
的方法就被觸發了。(下面的方法比較重要,咱們自定義 Glide 的時候許多的配置都可以從下面的源碼中尋找到答案,後文中咱們仍然會提到這個方法)
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();
}
}
// 應用 GlideModule,咱們自定義 GlideModuel 的方法會在這裏被調用
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);
}
// 構建 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 進行自定義。從 initializeGlide()
方法中,咱們也看出,這裏的 GlideBuilder
也就是 initializeGlide()
方法中傳入的 GlideBuilder
。這裏使用了構建者模式,GlideBuilder
是構建者的實例。因此,咱們能夠經過調用 GlideBuilder
的方法來對 Glide 進行自定義。
在上面的自定義 GlideModule 中,咱們經過構建者來指定了 Glide 的緩存大小和緩存路徑。 GlideBuilder
還提供了一些其餘的方法,咱們能夠經過查看源碼瞭解,並調用這些方法來自定義 Glide.
Glide 默認使用 HttpURLConnection
實現網絡當中的圖片的加載。咱們能夠經過對 Glide 進行配置來使用 OkHttp 進行網絡圖片加載。
首先,咱們須要引用以下依賴:
api ('com.github.bumptech.glide:okhttp3-integration:4.8.0') {
transitive = false
}
複製代碼
該類庫中提供了基於 OkHttp 的 ModelLoader
和 DataFetcher
實現。它們是 Glide 圖片加載環節中的重要組成部分,咱們會在後面介紹源碼和 Glide 的架構的時候介紹它們被設計的意圖及其做用。
而後,咱們須要在自定義的 GlideModule
中註冊網絡圖片加載須要的組件,即在 registerComponents()
方法中替換 GlideUrl
的加載的默認實現:
@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) {
// 輸出日誌,用於確認使用了咱們配置的 OkHttp 進行網絡請求
LogUtils.d(call.request().url().toString());
}
})
.build();
registry.replace(GlideUrl.class, InputStream.class, new Factory(okHttpClient));
}
@Override
public boolean isManifestParsingEnabled() {
// 不使用 Manifest 中的 GlideModule
return false;
}
}
複製代碼
這樣咱們經過本身的配置指定網絡中圖片加載須要使用 OkHttp. 而且自定義了 OkHttp 的超時時間等參數。按照上面的方式咱們能夠在 Glide 中使用 OkHttp 來加載網絡中的圖片了。
不過,當咱們在項目中引用了 okhttp3-integration
的依賴以後,不進行上述配置同樣可使用 OkHttp 來進行網絡圖片加載的。這是由於上述依賴的包中已經提供了一個自定義的 GlideModule,即 OkHttpLibraryGlideModule
。該類使用了 @GlideModule
註解,而且已經指定了網絡圖片加載使用 OkHttp。因此,當咱們不自定義 GlideModule 的時候,只使用它同樣能夠在 Glide 中使用 OkHttp.
若是咱們使用了自定義的 GlideModule,當咱們編譯的時候會看到 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);
}
複製代碼
這裏先調用了 OkHttpLibraryGlideModule
的 registerComponents()
方法,而後調用了咱們自定義的 GlideModule 的 registerComponents()
方法,只是,咱們的 GlideModule 的 registerComponents()
方法會覆蓋掉 OkHttpLibraryGlideModule
中的實現。(由於咱們的 GlideModule 的 registerComponents()
方法中調用的是 Registry
的 replace()
方法,會替換以前的效果。)
若是不但願畫蛇添足,咱們能夠直接在自定義的 GlideModule 中使用 @Excludes
註解,並指定 OkHttpLibraryGlideModule
來直接排除該類。這樣 GeneratedAppGlideModuleImpl
中的 registerComponents()
方法將只使用咱們自定義的 GlideModule. 如下是排除以後生成的類中 registerComponents()
方法的實現:
@Override
public void registerComponents(@NonNull Context context, @NonNull Glide glide, @NonNull Registry registry) {
appGlideModule.registerComponents(context, glide, registry);
}
複製代碼
在本文中,咱們經過介紹 Glide 的兩種常見的配置方式來分析了 Glide 的部分源碼實現。在這部分中,咱們重點介紹了初始化 Glide 的並獲取 GlideModule
的過程,以及與圖片資源的時候相關的 ModelLoader
等的源碼。瞭解這部份內容是比較重要的,由於它們是暴露給用戶的 API 接口,比較經常使用;而且對這些類簡單瞭解以後可以不至於在隨後分析 Glide 整個加載流程的時候迷路。
這裏咱們對上面兩種配置方式中涉及到的類進行一個分析。以下圖所示
當咱們初始化 Glide 的時候會使用 Registry
的 append()
等一系列的方法構建資源類型-加載方式-輸出類型
的一個映射,而後當咱們使用 Glide 進行記載的時候,會先根據資源類型找到對應的加載方式,而後使用該加載方式從指定的數據源中加載數據,並將其轉換成指定的輸出類型。
以上面咱們自定義圖片加載方式的過程爲例,這裏咱們自定義了一個資源類型 CacheImage
,並經過自定義 GlideModule 指定了它的加載實現是咱們自定義的 ImageLoader
類。而後,在咱們自定義的 ImageLoader 中,咱們指定了獲取該資源的緩存的鍵的方式和從數據源中記載數據的具體實現 HttpUrlFetcher
。這樣,當 Glide 要加載某個 CacheImage 的時候,會先使用該緩存的鍵嘗試從緩存中獲取,拿不到結果以後使用 HttpUrlFetcher
從網絡當中獲取數據。從網絡中獲取數據的時候會獲得 InputStream,最後,再調用一個回調類,使用 BitmapFactory 從 InputStream 中獲取 Bitmap 並將其顯示到 ImageView 上面,這樣就完成了整個圖片加載的流程。
從上文的分析中,咱們能夠總結出 Glide 的幾個設計人性的地方:
資源類型-加載方式-輸出類型
映射的時候使用工廠方法而不是經過某個類創建一對一映射。上面咱們經過 Glide 的幾種配置方式簡單介紹了 Glide 的圖片加載流程。其實際的執行過程遠比咱們上述過程更加複雜。在下文中咱們會對 Glide 的圖片加載的主流程進行分析。歡迎繼續關注和閱讀!
若是您喜歡個人文章,能夠在如下平臺關注我:
更多文章:Gihub: Android-notes