一、概述
前面說的都是如何使用Glide
提供的介面來展示圖片資源,今天這篇,我們來講一下如何改變Glide
的配置。
二、定義GlideModule
2.1 步驟
首先,我們需要一個實現了GlideModule
介面的類,重寫其中的方法來改變Glide
的配置,然後讓Glide
在構造例項的過程中,讀取這個類中的配置資訊。
- 第一步:實現
GlideModule
介面
public class CustomGlideModule implements GlideModule {
@Override
public void applyOptions(Context context, GlideBuilder builder) {
//通過builder.setXXX進行配置.
}
@Override
public void registerComponents(Context context, Glide glide) {
//通過glide.register進行配置.
}
}
複製程式碼
- 第二步:在
AndroidManifest.xml
中的<application>
標籤下定義<meta-data>
,這樣Glide
才能知道我們定義了這麼一個類,其中android:name
是我們自定義的GlideModule
的完整路徑,而android:value
就固定寫死GlideModule
。
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<meta-data
android:name="com.example.lizejun.repoglidelearn.CustomGlideModule"
android:value="GlideModule"/>
</application>
複製程式碼
2.2 原始碼分析
上面2.1
所做的工作都是為了在Glide
建立時可以讀取我們在兩個回撥中配置的資訊,我們來看一下Glide
是如何使用這個自定義的類的,它的整個流程在Glide
的get
方法中:
public static Glide get(Context context) {
if (glide == null) {
synchronized (Glide.class) {
if (glide == null) {
Context applicationContext = context.getApplicationContext();
//第一步
List<GlideModule> modules = new ManifestParser(applicationContext).parse();
//第二步
GlideBuilder builder = new GlideBuilder(applicationContext);
for (GlideModule module : modules) {
//在builder構造出glide之前,讀取使用者自定義的配置.
module.applyOptions(applicationContext, builder);
}
glide = builder.createGlide();
//第三步
for (GlideModule module : modules) {
module.registerComponents(applicationContext, glide);
}
}
}
}
return glide;
}
複製程式碼
可以看到,整個例項化Glide
的過程分為三步:
- 第一步:去
AndroidManifest
中查詢meta-data
為GlideModule
的類,然後通過反射例項化它。 - 第二步:之後
Glide
會新建一個GlideBuilder
物件,它會先呼叫我們自定義的GlideModule
的applyOptions
方法,並把自己傳進去,這樣,自定義的GlideModule
就可以通過GlideBuilder
提供的介面來設定它內部的引數,在builder.createGlide()
的過程中就會根據它內部的引數來構建Glide
,假如我們沒有設定相應的引數,那麼在createGlide
時,就會採取預設的實現,下面就是memoryCache
的例子。
//我們在applyOptions中,可以通過GlideBuilder的這個方法來設定自己的memoryCache.
public GlideBuilder setMemoryCache(MemoryCache memoryCache) {
this.memoryCache = memoryCache;
return this;
}
Glide createGlide() {
//如果builder中沒有設定memoryCache,那麼採用預設的實現.
if (memoryCache == null) {
memoryCache = new LruResourceCache(calculator.getMemoryCacheSize());
}
return new Glide(engine, memoryCache, bitmapPool, context, decodeFormat);
}
複製程式碼
- 第三步:在
Glide
例項化完畢之後,呼叫自定義GlideModule
的registerComponents
,並傳入當前的Glide
例項來讓使用者註冊自己的元件,其實在Glide
例項化的過程中已經註冊了預設的元件,如果使用者定義了相同的元件,那麼就會替換之前的。
Glide(Engine engine, MemoryCache memoryCache, BitmapPool bitmapPool, Context context, DecodeFormat decodeFormat) {
//Glide預設註冊的元件.
register(File.class, ParcelFileDescriptor.class, new FileDescriptorFileLoader.Factory());
register(File.class, InputStream.class, new StreamFileLoader.Factory());
register(int.class, ParcelFileDescriptor.class, new FileDescriptorResourceLoader.Factory());
register(int.class, InputStream.class, new StreamResourceLoader.Factory());
register(Integer.class, ParcelFileDescriptor.class, new FileDescriptorResourceLoader.Factory());
register(Integer.class, InputStream.class, new StreamResourceLoader.Factory());
register(String.class, ParcelFileDescriptor.class, new FileDescriptorStringLoader.Factory());
register(String.class, InputStream.class, new StreamStringLoader.Factory());
register(Uri.class, ParcelFileDescriptor.class, new FileDescriptorUriLoader.Factory());
register(Uri.class, InputStream.class, new StreamUriLoader.Factory());
register(URL.class, InputStream.class, new StreamUrlLoader.Factory());
register(GlideUrl.class, InputStream.class, new HttpUrlGlideUrlLoader.Factory());
register(byte[].class, InputStream.class, new StreamByteArrayLoader.Factory());
}
複製程式碼
通俗的來說,註冊元件的目的就是告訴Glide
,當我們呼叫load(xxxx)
方法時,應該用什麼方式來獲取這個xxxx
所指向的資源。因此,我們可以看到register
的第一個引數就是我們load(xxxx)
的型別,第二個引數是對應的輸入流,而第三個引數就是定義獲取資源的方式。
我們也就分兩個部分,在第三、四節,我們分兩部分討論這兩個回撥函式的用法:applyOptions/registerComponents
。
2.3 注意事項
- 由於
Glide
是通過反射的方法來例項化GlideModule
物件的,因此自定義的GlideModule
只能有一個無參的構造方法。 - 可以看到,上面是支援配置多個
GlideModule
的,但是GlideModule
的讀取順序並不能保證,因此,不要在多個GlideModule
對同一個屬性進行不同的配置。
三、applyOptions(Context context, GlideBuilder builder)
方法詳解
在第二節中,我們已經解釋過,這個回撥方法的目的就是為了讓使用者能通過builder
定義自己的配置,而所支援的配置也就是GlideBuilder
的setXXX
方法,它們包括:
-
setBitmapPool(BitmapPool bitmapPool)
設定Bitmap
的快取池,用來重用Bitmap
,需要實現BitmapPool
介面,它的預設實現是LruBitmapPool
-
setMemoryCache(MemoryCache memoryCache)
設定記憶體快取,需要實現MemoryCache
介面,預設實現是LruResourceCache
。 -
setDiskCache(DiskCache.Factory diskCacheFactory)
設定磁碟快取,需要實現DiskCache.Factory
,預設實現是InternalCacheDiskCacheFactory
-
setResizeService(ExecutorService service)
當資源不在快取中時,需要通過這個Executor
發起請求,預設是實現是FifoPriorityThreadPoolExecutor
。 -
setDiskCacheService(ExecutorService service)
讀取磁碟快取的服務,預設實現是FifoPriorityThreadPoolExecutor
。 -
setDecodeFormat(DecodeFormat decodeFormat)
用於控制Bitmap
解碼的清晰度,DecodeFormat
可選的值有PREFER_ARGB_8888/PREFER_RGB_565
,預設為PREFER_RGB_565
。
四、registerComponents(Context context, Glide glide)
方法詳解
registerComponents
相對來說就複雜了很多,它主要和三個介面有關:
ModelLoaderFactory
ModelLoader
DataFetcher
為了便於理解,我們先通過它內部一個預設Module
的實現來看一下原始碼是如何實現的。
我們選取是通用的載入普通圖片的url
的例子,它對應的註冊方法是下面這句:
Glide(Engine engine, MemoryCache memoryCache, BitmapPool bitmapPool, Context context, DecodeFormat decodeFormat) {
//註冊載入網路url的元件.
register(GlideUrl.class, InputStream.class, new HttpUrlGlideUrlLoader.Factory());
}
複製程式碼
4.1 原始碼分析
4.1.1 HttpUrlGlideUrlLoader.Factory
首先看一下HttpUrlGlideUrlLoader
的內部工廠類,它實現了ModelLoaderFactory<T, Y>
介面
public interface ModelLoaderFactory<T, Y> {
ModelLoader<T, Y> build(Context context, GenericLoaderFactory factories);
void teardown();
}
複製程式碼
它要求我們返回一個ModelLoader
,我們看一下HttpUrlGlideUrlLoader.Factory
是怎麼做的,可以看到,它返回了HttpUrlGlideUrlLoader
,而它的兩個泛型引數就是我們register
中指定的前兩個引數型別。
public static class Factory implements ModelLoaderFactory<GlideUrl, InputStream> {
private final ModelCache<GlideUrl, GlideUrl> modelCache = new ModelCache<GlideUrl, GlideUrl>(500);
@Override
public ModelLoader<GlideUrl, InputStream> build(Context context, GenericLoaderFactory factories) {
return new HttpUrlGlideUrlLoader(modelCache);
}
@Override
public void teardown() {}
}
複製程式碼
4.1.2 HttpUrlGlideUrlLoader
HttpUrlGlideUrlLoader
實現了ModelLoader
介面:
public interface ModelLoader<T, Y> {
DataFetcher<Y> getResourceFetcher(T model, int width, int height);
}
複製程式碼
ModelLoader
提供了一個DataFetcher
,它會去請求這個抽象模型所表示的資料:
T
:模型的型別。Y
:一個可以被ResourceDecoder
解析出資料的表示。
GlideUrl
的實現如下:
public class HttpUrlGlideUrlLoader implements ModelLoader<GlideUrl, InputStream> {
private final ModelCache<GlideUrl, GlideUrl> modelCache;
public HttpUrlGlideUrlLoader() {
this(null);
}
public HttpUrlGlideUrlLoader(ModelCache<GlideUrl, GlideUrl> modelCache) {
this.modelCache = modelCache;
}
//最主要的方法,它決定了我們獲取資料的方式.
@Override
public DataFetcher<InputStream> getResourceFetcher(GlideUrl model, int width, int height) {
GlideUrl url = model;
if (modelCache != null) {
url = modelCache.get(model, 0, 0);
if (url == null) {
modelCache.put(model, 0, 0, model);
url = model;
}
}
return new HttpUrlFetcher(url);
}
}
複製程式碼
4.1.3 HttpUrlFetcher
DataFetcher
就是我們讀取資料的方式,它的關鍵方法是loadData
,該loadData
的返回值就是我們register
的第二個引數:
public interface DataFetcher<T> {
T loadData(Priority priority) throws Exception;
void cleanup();
String getId();
void cancel();
}
複製程式碼
HttpUrlFetcher
實現了DataFetcher
介面,在它的loadData
方法中,通過傳入的url
發起請求,最終返回一個InputStream
。
public class HttpUrlFetcher implements DataFetcher<InputStream> {
private static final String TAG = "HttpUrlFetcher";
private static final int MAXIMUM_REDIRECTS = 5;
private static final HttpUrlConnectionFactory DEFAULT_CONNECTION_FACTORY = new DefaultHttpUrlConnectionFactory();
private final GlideUrl glideUrl;
private final HttpUrlConnectionFactory connectionFactory;
private HttpURLConnection urlConnection;
private InputStream stream;
private volatile boolean isCancelled;
public HttpUrlFetcher(GlideUrl glideUrl) {
this(glideUrl, DEFAULT_CONNECTION_FACTORY);
}
HttpUrlFetcher(GlideUrl glideUrl, HttpUrlConnectionFactory connectionFactory) {
this.glideUrl = glideUrl;
this.connectionFactory = connectionFactory;
}
@Override
public InputStream loadData(Priority priority) throws Exception {
return loadDataWithRedirects(glideUrl.toURL(), 0 /*redirects*/, null /*lastUrl*/, glideUrl.getHeaders());
}
private InputStream loadDataWithRedirects(URL url, int redirects, URL lastUrl, Map<String, String> headers) throws IOException {
//就是呼叫HttpUrlConnection請求.
}
@Override
public void cleanup() {
if (stream != null) {
try {
stream.close();
} catch (IOException e) {
// Ignore
}
}
if (urlConnection != null) {
urlConnection.disconnect();
}
}
@Override
public String getId() {
return glideUrl.getCacheKey();
}
@Override
public void cancel() {
isCancelled = true;
}
}
複製程式碼
4.1.4 小結
對上面做個總結,整個流程如下:通過register
傳入一個ModelLoaderFactory<T, Y>
工廠類,該工廠生產的是ModelLoader<T, Y>
,而這個ModelLoader
會根據T
返回一個DataFetcher<Y>
,在DataFetcher<Y>
中,我們去獲取資料。(在上面的例子中T
就是GlideUrl
,Y
就是InputStream
)
4.2 自定義ModuleLoader
示例:用OkHttpClient
替換HttpURLConnection
下面的例子來自於這篇文章:
https://futurestud.io/tutorials/glide-module-example-accepting-self-signed-https-certificates
- 第一步:定義
ModelLoader
和ModelLoader.Factory
public class OkHttpGlideUrlLoader implements ModelLoader<GlideUrl, InputStream> {
@Override
public ModelLoader<GlideUrl, InputStream> build(Context context, GenericLoaderFactory factories) {
return new OkHttpGlideUrlLoader(getOkHttpClient());
}
@Override
public void teardown() {}
}
@Override
public DataFetcher<InputStream> getResourceFetcher(GlideUrl model, int width, int height) {
return new OkHttpGlideUrlFetcher(mOkHttpClient, model);
}
}
複製程式碼
- 第二步:
ModelLoader
的getResourceFetcher
返回一個DataFetcher
,我們給它傳入一個OkHttpClient
例項,讓它通過OkHttpClient
發起請求。
public class OkHttpGlideUrlFetcher implements DataFetcher<InputStream> {
public OkHttpGlideUrlFetcher(OkHttpClient client, GlideUrl url) {
this.client = client;
this.url = url;
}
@Override
public InputStream loadData(Priority priority) throws Exception {
Request.Builder requestBuilder = new Request.Builder().url(url.toStringUrl());
for (Map.Entry<String, String> headerEntry : url.getHeaders().entrySet()) {
String key = headerEntry.getKey();
requestBuilder.addHeader(key, headerEntry.getValue());
}
Request request = requestBuilder.build();
Response response = client.newCall(request).execute();
responseBody = response.body();
if (!response.isSuccessful()) {
throw new IOException("Request failed with code: " + response.code());
}
long contentLength = responseBody.contentLength();
stream = ContentLengthInputStream.obtain(responseBody.byteStream(), contentLength);
return stream;
}
}
複製程式碼
第三步:在CustomGlideModule
中註冊這個元件:
public class CustomGlideModule implements GlideModule {
@Override
public void applyOptions(Context context, GlideBuilder builder) {
//通過builder.setXXX進行配置.
}
@Override
public void registerComponents(Context context, Glide glide) {
//通過glide.register進行配置.
glide.register(GlideUrl.class, InputStream.class, new OkHttpGlideUrlLoader.Factory());
}
}
複製程式碼
接著我們發起一次請求,通過斷點可以發現,呼叫的是OkHttpClient
來進行資料的拉取:
4.3 自定義處理的Module
上面我們分析瞭如何定義ModuleLoader
來關聯已有的Module
和最終的資料型別,下面我們介紹一些如何定義自己的Model
,也就是前面在基礎介紹中,所說的load(Module)
方法。
- 第一步:定義
Module
的介面
public interface AutoSizeModel {
String requestSizeUrl(int width, int height);
}
複製程式碼
- 第二步:實現
Module
介面
public class AutoSizeModelImpl implements AutoSizeModel {
String mUrl;
public AutoSizeModelImpl(String url) {
mUrl = url;
}
@Override
public String requestSizeUrl(int width, int height) {
return mUrl;
}
}
複製程式碼
- 第三步:定義
ModuleLoader
和ModuleLoader.Factory
public class AutoSizeModelLoader extends BaseGlideUrlLoader<AutoSizeModel> {
public static class Factory implements ModelLoaderFactory<AutoSizeModel, InputStream> {
@Override
public ModelLoader<AutoSizeModel, InputStream> build(Context context, GenericLoaderFactory factories) {
return new AutoSizeModelLoader(context);
}
@Override
public void teardown() {}
}
public AutoSizeModelLoader(Context context) {
super(context);
}
@Override
protected String getUrl(AutoSizeModel model, int width, int height) {
return model.requestSizeUrl(width, height);
}
}
複製程式碼
- 第四步:在
CustomGlideModule
中進行關聯:
public class CustomGlideModule implements GlideModule {
@Override
public void applyOptions(Context context, GlideBuilder builder) {
//通過builder.setXXX進行配置.
}
@Override
public void registerComponents(Context context, Glide glide) {
//通過glide.register進行配置.
glide.register(AutoSizeModel.class, InputStream.class, new AutoSizeModelLoader.Factory());
}
}
複製程式碼
- 第五步:呼叫
public void loadCustomModule(View view) {
AutoSizeModelImpl autoSizeModel = new AutoSizeModelImpl("http://i.imgur.com/DvpvklR.png");
Glide.with(this)
.load(autoSizeModel)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.skipMemoryCache(true)
.into(mImageView);
}
複製程式碼
4.4 動態指定ModelLoader
在上面的例子中,我們是在自定義的CustomGlideModule
中指定了Model
和ModuleLoader
的關聯,當然,我們也可以採用動態指定ModelLoader
的方法,也就是說,我們去掉4.3
中的第四步,並把第五步改成下面這樣:
public void loadDynamicModule(View view) {
AutoSizeModelImpl autoSizeModel = new AutoSizeModelImpl("http://i.imgur.com/DvpvklR.png");
AutoSizeModelLoader autoSizeModelLoader = new AutoSizeModelLoader(this);
Glide.with(this)
.using(autoSizeModelLoader)
.load(autoSizeModel)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.skipMemoryCache(true)
.into(mImageView);
}
複製程式碼
使用using
方法,我們就可以在執行時根據情況為同一個Module
選擇不同型別的ModuleLoader
了。
五、小結
這也是我們Glide
學習的最後一章,所有的原始碼都可以從下面的連結中找到: