Glide Module 案例: 接受自簽名HTTPS證書
原文:Glide Module Example: Self-Signed HTTPS Network Stack
作者:Norman Peitek
翻譯:Dexter0218
在上一篇Glide的文章中,你已經學了GlideModule的基礎知識。他們提供了一個簡單的方法去訪問Glide核心部分的功能。你可以很快地通過實現和定義GlideModule改變Glide的行為。我們已經通過實現applyOptions()
方法改變解析格式,去提升圖片質量。文本,我們要使用其他的方法,registerComponents()
,去改變Glide的網絡棧,讓它能從自簽名HTTPS服務器接收連接和圖片。
Glide 系列概覽
- 入門簡介
- 高級加載
- 適配器(ListView, GridView)
- 占位圖& 淡入淡出動畫
- 圖片大小 & 縮放
- 播放GIF & 視頻
- 緩存基礎
- 請求優先級
- 縮略圖
- 回調:定制view中使用SimpleTarget和ViewTarget
- 通知欄和桌面小控件的圖片加載
- 異常: 調試和報錯處理
- 自定義變換
- 用animate()定制動畫
- 整合網絡協議棧
- 用Modules定制Glide
- Glide Module 案例: 接受自簽名HTTPS證書
- Glide Module 案例: 自定義緩存
- Glide Module 案例: 通過加載自定義大小圖片優化
- 動態使用 Model Loaders
- 如何旋轉圖片
- 系列綜述
用GlideModule定制Glide
在看下面的之前,請確保你看完并理解了前面文章關于GlideModules部分。我們不會在本文中再次回顧基礎,而是直接跳到問題。所以確保你打好GlideModule的基礎。
你已經知道GlideModule提供了兩個方法改變行為。上篇文章,我們學習了第一個方法applyOption()
。本文我們使用另一個方法registerComponents()
去設置一個不同的網絡棧。默認地,Glide內部使用標準的HTTPUrlConnection
去下載圖片。Glide也提供兩個集成庫。這三個方法優點是在安全設置上都是相當嚴格的。唯一的不足之處是當你從一個使用HTTPS,還是self-signed的服務器下載圖片時,Glide并不會下載或者顯示圖片,因為self-signed認證會被認為存在安全問題。
不安全的 OkHttpClient
這樣,你會需要去實現能夠接受self-signed認證的網絡棧。幸運地,我們已經實現并用過一個“不安全的”OkHttpClient
。由于它提供給了一個需要集成的常規OkHttpClient
,我們只需要拷貝并粘貼這個類:
public class UnsafeOkHttpClient {
public static OkHttpClient getUnsafeOkHttpClient() {
try {
// Create a trust manager that does not validate certificate chains
final TrustManager[] trustAllCerts = new TrustManager[]{
new X509TrustManager() {
@Override
public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException {
}
@Override
public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException {
}
@Override
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
//return null;//刪除這行,多謝下面評論的幾位小伙伴指出空指針問題,并提供解決方案。
}
}
};
// Install the all-trusting trust manager
final SSLContext sslContext = SSLContext.getInstance("SSL");
sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
// Create an ssl socket factory with our all-trusting manager
final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
OkHttpClient okHttpClient = new OkHttpClient();
okHttpClient.setSslSocketFactory(sslSocketFactory);
okHttpClient.setProtocols(Arrays.asList(Protocol.HTTP_1_1));
okHttpClient.setHostnameVerifier(new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
return true;
}
});
return okHttpClient;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
創建的OkHttpClient
關閉了所有的SSL認證檢查。
集成到 Glide
Glide的OkHTTP集成庫做的都是一樣的工作,所以我們可以跟隨他們的步驟。首先,我們需要在GlideModule
里聲明我們的定制。你應該想到,我們需要在registerComponents()
方法里做適配。我們可以調用.register()
方法去交換Glide基礎構成。Glide使用一個ModelLoader
去鏈接到數據模型創建一個具體的數據類型。我們的例子中,我們需要創建一個ModelLoader
,它連接到一個URL,通過GlideUrl
類響應并轉化為輸入流。Glide需要能夠創建我們的新ModelLoader
的實例,所以我們在.register()
方法中傳入一個工廠:
public class UnsafeOkHttpGlideModule implements GlideModule {
@Override
public void applyOptions(Context context, GlideBuilder builder) {
}
@Override
public void registerComponents(Context context, Glide glide) {
glide.register(GlideUrl.class, InputStream.class, new OkHttpUrlLoader.Factory());
}
}
方法里的前兩個參數是模型類和鏈接資源類。最后一個參數是ModelLoaderFactory
。最終,我們不能直接設置一個UnsafeOkHttpClient
實例,我們需要創建一個ModelLoaderFactory
,使用UnsafeOkHttpClient
去提供URL和輸入流之間的鏈接。
再次,OkHttp集成庫給了我們一個很棒的模版:
public class OkHttpUrlLoader implements ModelLoader<GlideUrl, InputStream> {
/**
* The default factory for {@link OkHttpUrlLoader}s.
*/
public static class Factory implements ModelLoaderFactory<GlideUrl, InputStream> {
private static volatile OkHttpClient internalClient;
private OkHttpClient client;
private static OkHttpClient getInternalClient() {
if (internalClient == null) {
synchronized (Factory.class) {
if (internalClient == null) {
internalClient = UnsafeOkHttpClient.getUnsafeOkHttpClient();
}
}
}
return internalClient;
}
/**
* Constructor for a new Factory that runs requests using a static singleton client.
*/
public Factory() {
this(getInternalClient());
}
/**
* Constructor for a new Factory that runs requests using given client.
*/
public Factory(OkHttpClient client) {
this.client = client;
}
@Override
public ModelLoader<GlideUrl, InputStream> build(Context context, GenericLoaderFactory factories) {
return new OkHttpUrlLoader(client);
}
@Override
public void teardown() {
// Do nothing, this instance doesn't own the client.
}
}
private final OkHttpClient client;
public OkHttpUrlLoader(OkHttpClient client) {
this.client = client;
}
@Override
public DataFetcher<InputStream> getResourceFetcher(GlideUrl model, int width, int height) {
return new OkHttpStreamFetcher(client, model);
}
}
在這個類里,你可以看到ModelLoaderFactory
是如何被構造的。對我們來說,最重要的一行是internalClient
對象的創建:internalClient = UnsafeOkHttpClient.getUnsafeOkHttpClient();
。
不幸地是,我們仍然要使用我們不安全的OkHttpClient
去鏈接Url到一個有效的輸入流。這樣,我們需要另外一個類去取到URL對應的輸入流的響應:
public class OkHttpStreamFetcher implements DataFetcher<InputStream> {
private final OkHttpClient client;
private final GlideUrl url;
private InputStream stream;
private ResponseBody responseBody;
public OkHttpStreamFetcher(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;
}
@Override
public void cleanup() {
if (stream != null) {
try {
stream.close();
} catch (IOException e) {
// Ignored
}
}
if (responseBody != null) {
try {
responseBody.close();
} catch (IOException e) {
// Ignored.
}
}
}
@Override
public String getId() {
return url.getCacheKey();
}
@Override
public void cancel() {
// TODO: call cancel on the client when this method is called on a background thread. See #257
}
}
你沒必要明白類里面的所有細節。相反,你應當對于Glide系統如何替換內部工廠部分有個概述。
展望
本文中,你已經看了另一個改變Glide工作方式使用案例。我們已經實現了一個“不安全”的網絡棧,并且用GlideModule的registerComponents()
集成它到Glide內。但那也僅是Glide可修改內容的冰山一角。
下篇文章,我們會學習另一個使用GlideModule去改變Glide緩存方式的案例。