Glide圖片加載框架的強(qiáng)大录煤,使用簡(jiǎn)單相信是眾所周知的,之前在項(xiàng)目中一直都用的荞胡,但是最近在項(xiàng)目中需要使用代理妈踊,在加載圖片的時(shí)候也要用,本來(lái)以為很簡(jiǎn)單泪漂,網(wǎng)上查了下發(fā)現(xiàn)這一塊其實(shí)沒(méi)那么容易啊廊营,只能邊看源碼邊學(xué)習(xí)解決。
- Glide項(xiàng)目地址: Glide Github
- Glide WIKI
- Glide 4.0源碼解析
查看源碼萝勤,可以看到進(jìn)行網(wǎng)絡(luò)請(qǐng)求的時(shí)候露筒,使用的是HttpUrlConnection(這里我們沒(méi)有使用OKHttp或者Volley進(jìn)行優(yōu)化,有興趣的可以查看WIKI文檔進(jìn)行集成)
源碼分析基于Glide 4.1.1
Glide常用方式與源碼分析:
Glide.with(activity).load(url).into(imageview)
1. 初始化敌卓,配置 Loader
- 查看Glide類,Registry類:
Glide.with(activity)慎式,對(duì)Glide進(jìn)行初始化,主要方法如下:
Glide.getRetriever()
Glide.get(context)
Glide.checkAndInitializeGlide(context);
Glide.initializeGlide
Glide glide = builder.build(applicationContext);
Glide 用GlideBuilder對(duì)象生成Glide對(duì)象,Glide類的構(gòu)造函數(shù)中趟径,對(duì)Glide對(duì)象進(jìn)行初始化瘪吏,這里涉及到的初始化的重點(diǎn)是對(duì)Registry對(duì)象的初始化:
registry.register(ByteBuffer.class, new ByteBufferEncoder())
.register(InputStream.class, new StreamEncoder(arrayPool))
/* Bitmaps */
.append(ByteBuffer.class, Bitmap.class,
new ByteBufferBitmapDecoder(downsampler))
.append(InputStream.class, Bitmap.class,
new StreamBitmapDecoder(downsampler, arrayPool))
.append(ParcelFileDescriptor.class, Bitmap.class, new VideoBitmapDecoder(bitmapPool))
.register(Bitmap.class, new BitmapEncoder())
/* GlideBitmapDrawables */
.append(ByteBuffer.class, BitmapDrawable.class,
new BitmapDrawableDecoder<>(resources, bitmapPool,
new ByteBufferBitmapDecoder(downsampler)))
.append(InputStream.class, BitmapDrawable.class,
new BitmapDrawableDecoder<>(resources, bitmapPool,
new StreamBitmapDecoder(downsampler, arrayPool)))
.append(ParcelFileDescriptor.class, BitmapDrawable.class,
new BitmapDrawableDecoder<>(resources, bitmapPool, new VideoBitmapDecoder(bitmapPool)))
.register(BitmapDrawable.class, new BitmapDrawableEncoder(bitmapPool, new BitmapEncoder()))
/* GIFs */
.prepend(InputStream.class, GifDrawable.class,
new StreamGifDecoder(registry.getImageHeaderParsers(), byteBufferGifDecoder, arrayPool))
.prepend(ByteBuffer.class, GifDrawable.class, byteBufferGifDecoder)
.register(GifDrawable.class, new GifDrawableEncoder())
/* GIF Frames */
.append(GifDecoder.class, GifDecoder.class, new UnitModelLoader.Factory<GifDecoder>())
.append(GifDecoder.class, Bitmap.class, new GifFrameResourceDecoder(bitmapPool))
/* Files */
.register(new ByteBufferRewinder.Factory())
.append(File.class, ByteBuffer.class, new ByteBufferFileLoader.Factory())
.append(File.class, InputStream.class, new FileLoader.StreamFactory())
.append(File.class, File.class, new FileDecoder())
.append(File.class, ParcelFileDescriptor.class, new FileLoader.FileDescriptorFactory())
.append(File.class, File.class, new UnitModelLoader.Factory<File>())
/* Models */
.register(new InputStreamRewinder.Factory(arrayPool))
.append(int.class, InputStream.class, new ResourceLoader.StreamFactory(resources))
.append(
int.class,
ParcelFileDescriptor.class,
new ResourceLoader.FileDescriptorFactory(resources))
.append(Integer.class, InputStream.class, new ResourceLoader.StreamFactory(resources))
.append(
Integer.class,
ParcelFileDescriptor.class,
new ResourceLoader.FileDescriptorFactory(resources))
.append(String.class, InputStream.class, new DataUrlLoader.StreamFactory())
.append(String.class, InputStream.class, new StringLoader.StreamFactory())
.append(String.class, ParcelFileDescriptor.class, new StringLoader.FileDescriptorFactory())
.append(Uri.class, InputStream.class, new HttpUriLoader.Factory())
.append(Uri.class, InputStream.class, new AssetUriLoader.StreamFactory(context.getAssets()))
.append(
Uri.class,
ParcelFileDescriptor.class,
new AssetUriLoader.FileDescriptorFactory(context.getAssets()))
.append(Uri.class, InputStream.class, new MediaStoreImageThumbLoader.Factory(context))
.append(Uri.class, InputStream.class, new MediaStoreVideoThumbLoader.Factory(context))
.append(
Uri.class,
InputStream.class,
new UriLoader.StreamFactory(context.getContentResolver()))
.append(Uri.class, ParcelFileDescriptor.class,
new UriLoader.FileDescriptorFactory(context.getContentResolver()))
.append(Uri.class, InputStream.class, new UrlUriLoader.StreamFactory())
.append(URL.class, InputStream.class, new UrlLoader.StreamFactory())
.append(Uri.class, File.class, new MediaStoreFileLoader.Factory(context))
.append(GlideUrl.class, InputStream.class, new HttpGlideUrlLoader.Factory())
.append(byte[].class, ByteBuffer.class, new ByteArrayLoader.ByteBufferFactory())
.append(byte[].class, InputStream.class, new ByteArrayLoader.StreamFactory())
/* Transcoders */
.register(Bitmap.class, BitmapDrawable.class,
new BitmapDrawableTranscoder(resources, bitmapPool))
.register(Bitmap.class, byte[].class, new BitmapBytesTranscoder())
.register(GifDrawable.class, byte[].class, new GifDrawableBytesTranscoder());
- Registry類是用來(lái):
/**
* Manages component registration to extend or replace Glide's default loading, decoding, and
* encoding logic.
*/
簡(jiǎn)單來(lái)說(shuō):就是根據(jù)傳入什么樣的modelClass來(lái)取使用哪個(gè)ModelLoaderFactory。類似于EventBus 這種蜗巧,根據(jù)傳入的類型調(diào)用具體的處理邏輯掌眠。這里我們的httpUrl字符串在Glide中會(huì)被封裝成GlideUrl對(duì)象,對(duì)應(yīng)的loader類是HttpGlideUrlLoader
GlideUrl.class, InputStream.class, new HttpGlideUrlLoader.Factory()
2. GlideApp
在Glide4中會(huì)在make之后自動(dòng)生成GlideApp:
- 在build.gradle中添加:
dependencies {
compile 'com.github.bumptech.glide:glide:4.1.1'
annotationProcessor 'com.github.bumptech.glide:compiler:4.1.1'
}
其中第二句annotationProcessor 是重點(diǎn)
- 使用:@GlideModule
@GlideModule
public class MyGlideAppModule extends AppGlideModule {
@Override
public void applyOptions(Context context, GlideBuilder builder) {
super.applyOptions(context, builder);
int memoryCacheSizeBytes = 1024 * 1024 * 20; // 20mb
builder.setMemoryCache(new LruResourceCache(memoryCacheSizeBytes));
}
@Override
public boolean isManifestParsingEnabled() {
return false;
}
}
- Clean and make
3. HttpGlideUrlLoader幕屹,HttpUrlFetcher
這兩個(gè)類主要負(fù)責(zé)進(jìn)行網(wǎng)絡(luò)請(qǐng)求蓝丙。
- HttpUrlFetcher 這個(gè)類中使用HttpURLConnection進(jìn)行網(wǎng)絡(luò)請(qǐng)求
@Override
public void loadData(Priority priority, DataCallback<? super InputStream> callback) {
long startTime = LogTime.getLogTime();
final InputStream result;
try {
result = loadDataWithRedirects(glideUrl.toURL(), 0 /*redirects*/, null /*lastUrl*/,
glideUrl.getHeaders());
} catch (IOException e) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Failed to load data for url", e);
}
callback.onLoadFailed(e);
return;
}
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "Finished http url fetcher fetch in " + LogTime.getElapsedMillis(startTime)
+ " ms and loaded " + result);
}
callback.onDataReady(result);
}
private InputStream loadDataWithRedirects(URL url, int redirects, URL lastUrl,
Map<String, String> headers) throws IOException {
if (redirects >= MAXIMUM_REDIRECTS) {
throw new HttpException("Too many (> " + MAXIMUM_REDIRECTS + ") redirects!");
} else {
// Comparing the URLs using .equals performs additional network I/O and is generally broken.
// See http://michaelscharf.blogspot.com/2006/11/javaneturlequals-and-hashcode-make.html.
try {
if (lastUrl != null && url.toURI().equals(lastUrl.toURI())) {
throw new HttpException("In re-direct loop");
}
} catch (URISyntaxException e) {
// Do nothing, this is best effort.
}
}
urlConnection = connectionFactory.build(url);
for (Map.Entry<String, String> headerEntry : headers.entrySet()) {
urlConnection.addRequestProperty(headerEntry.getKey(), headerEntry.getValue());
}
urlConnection.setConnectTimeout(timeout);
urlConnection.setReadTimeout(timeout);
urlConnection.setUseCaches(false);
urlConnection.setDoInput(true);
// Stop the urlConnection instance of HttpUrlConnection from following redirects so that
// redirects will be handled by recursive calls to this method, loadDataWithRedirects.
urlConnection.setInstanceFollowRedirects(false);
// Connect explicitly to avoid errors in decoders if connection fails.
urlConnection.connect();
if (isCancelled) {
return null;
}
final int statusCode = urlConnection.getResponseCode();
if (statusCode / 100 == 2) {
return getStreamForSuccessfulRequest(urlConnection);
} else if (statusCode / 100 == 3) {
String redirectUrlString = urlConnection.getHeaderField("Location");
if (TextUtils.isEmpty(redirectUrlString)) {
throw new HttpException("Received empty or null redirect url");
}
URL redirectUrl = new URL(url, redirectUrlString);
return loadDataWithRedirects(redirectUrl, redirects + 1, url, headers);
} else if (statusCode == -1) {
throw new HttpException(statusCode);
} else {
throw new HttpException(urlConnection.getResponseMessage(), statusCode);
}
}
而HttpUrlConnection 是使用HttpUrlFetcher中的connectionFactory字段進(jìn)行生成的HttpUrlConnection:
@Override
public HttpURLConnection build(URL url) throws IOException {
return (HttpURLConnection) url.openConnection();
}
- HttpGlideUrlLoader:核心代碼如下,創(chuàng)建HttpUrlFetcher進(jìn)行網(wǎng)絡(luò)請(qǐng)求
@Override
public LoadData<InputStream> buildLoadData(GlideUrl model, int width, int height,
Options options) {
// GlideUrls memoize parsed URLs so caching them saves a few object instantiations and time
// spent parsing urls.
GlideUrl url = model;
if (modelCache != null) {
url = modelCache.get(model, 0, 0);
if (url == null) {
modelCache.put(model, 0, 0, model);
url = model;
}
}
int timeout = options.get(TIMEOUT);
return new LoadData<>(url, new HttpUrlFetcher(url, timeout));
}
主要的代碼分析就到這里望拖。
不難看出要想支持Proxy只需要改動(dòng)一行代碼即可:
@Override
public HttpURLConnection build(URL url) throws IOException {
return (HttpURLConnection) url.openConnection(proxy);
}
當(dāng)然還要加一些是否存在proxy的判斷:
@Override
public HttpURLConnection build(URL url) throws IOException {
HttpURLConnection conn = null;
if (existProxy()) {
conn = (HttpURLConnection) url.openConnection(getProxy());
} else {
conn = (HttpURLConnection) url.openConnection();
}
}
- 如果需要添加用戶名和密碼認(rèn)證:
if (!TextUtils.isEmpty(username) && !TextUtils.isEmpty(pwd)) {
Authenticator authenticator = new Authenticator() {
public PasswordAuthentication getPasswordAuthentication() {
return (new PasswordAuthentication(username,
pwd.toCharArray()));
}
};
Authenticator.setDefault(authenticator);
} else {
Authenticator.setDefault(null);
}
注意 關(guān)鍵是如何添加渺尘??靠娱?
這里能夠想到的是使用自定義的HttpGlideUrlLoader。
問(wèn)題1. HttpUrlFetcher中的connectionFactory是final的,HttpUrlConnectionFactory 是default的不能擴(kuò)展,HttpUrlFetchery構(gòu)造函數(shù)也是default的
// Visible for testing.
HttpUrlFetcher(GlideUrl glideUrl, int timeout, HttpUrlConnectionFactory connectionFactory) {
this.glideUrl = glideUrl;
this.timeout = timeout;
this.connectionFactory = connectionFactory;
}
解決方案
1. 方案一
修改Glide源碼掠兄,重新打包像云⌒咳福或者直接將源碼放到項(xiàng)目中修改
- HttpUrlFetcher第二個(gè)構(gòu)造方法置為public,將HttpUrlFetcher$HttpUrlConnectionFactory 接口置為 public
- HttpGlideUrlLoader中添加Proxy字段與設(shè)置方法迅诬,建議是 static腋逆,如果proxy為空就調(diào)用HttpUrlFetcher第一個(gè)構(gòu)造函數(shù),如果不為空就調(diào)用第二個(gè)侈贷,實(shí)現(xiàn)HttpUrlFetcher$HttpUrlConnectionFactory接口并將proxy傳入即可
private static class ProxyHttpUrlConnectionFactory implements HttpUrlConnectionFactory {
private Proxy mProxy
private static boolean setProxy(proxy p,String username,String pwd){
mProxy = p;
if (!TextUtils.isEmpty(username) && !TextUtils.isEmpty(pwd)) {
Authenticator authenticator = new Authenticator() {
public PasswordAuthentication getPasswordAuthentication() {
return (new PasswordAuthentication(username,
pwd.toCharArray()));
}
};
Authenticator.setDefault(authenticator);
} else {
Authenticator.setDefault(null);
}
}
private static boolean exist(){
return mProxy != null;
}
@Synthetic
public ProxyHttpUrlConnectionFactory () { }
@Override
public HttpURLConnection build(URL url) throws IOException {
//return (HttpURLConnection) url.openConnection(mProxy);
}
}
這個(gè)方案有點(diǎn):
- 改動(dòng)少
- 容易理解和實(shí)現(xiàn)
缺點(diǎn):
- 改動(dòng)了源碼撑蚌,對(duì)于后續(xù)Glide升級(jí)存在問(wèn)題
2. 方案二
自定義HttpGlideUrlLoader,前面說(shuō)到的Registry類的初始化中,當(dāng)modelClass是GlideUrl時(shí),使用的就是HttpGlideUrlLoader,那這里取代這個(gè)默認(rèn)的Loader继蜡,使用自定義即可:
假設(shè)自定義的是MyHttpGlideUriLoader, 擴(kuò)展 extends ModelLoader<GlideUrl, InputStream>, 則:
registry.replace(GlideUrl.class, InputStream.class, new MyHttpGlideUrlLoader.Factory());
并把HttpGlideUrlLoader中的代碼復(fù)制到MyHttpGlideUriLoader鳄逾,區(qū)別只有buildLoadData方法中最后return時(shí)殴俱。
然后自定義MyHttpUrlFetcher,將HttpUrlFetcher復(fù)制一份,區(qū)別只有DefaultHttpUrlConnectionFactory :
private static class DefaultHttpUrlConnectionFactory implements HttpUrlConnectionFactory {
private static Proxy mProxy
private static boolean setProxy(proxy p,String username,String pwd){
mProxy = p;
if (!TextUtils.isEmpty(username) && !TextUtils.isEmpty(pwd)) {
Authenticator authenticator = new Authenticator() {
public PasswordAuthentication getPasswordAuthentication() {
return (new PasswordAuthentication(username,
pwd.toCharArray()));
}
};
Authenticator.setDefault(authenticator);
} else {
Authenticator.setDefault(null);
}
}
private static boolean exist(){
return mProxy != null;
}
@Synthetic
DefaultHttpUrlConnectionFactory() { }
@Override
public HttpURLConnection build(URL url) throws IOException {
//return (HttpURLConnection) url.openConnection();
// 改成
HttpURLConnection conn = null;
if (existProxy()) {
conn = (HttpURLConnection) url.openConnection(mProxy);
} else {
conn = (HttpURLConnection) url.openConnection();
}
}
}
這個(gè)方案其實(shí)很好理解的。但是這個(gè)方案需要改的東西太多,有點(diǎn)挫,其實(shí)真正的改動(dòng)只是HttpUrlConnectionFactory.build 方法而已,
優(yōu)點(diǎn):
- 容易理解
缺點(diǎn):
- 改動(dòng)太多,添加太多冗余代碼
3. 方案三
使用反射+動(dòng)態(tài)代理
思路說(shuō)明:因?yàn)檎嬲枰薷牡闹皇荋ttpUrlFetcher$HttpUrlConnectionFactory 接口實(shí)現(xiàn)的build方法,如果能夠?qū)ttpUrlFetcher.connectionFactory替換掉就行了饼问,所以這里需要使用到反射讹开。先反射出HttpUrlConnectionFactory接口闹击,并實(shí)現(xiàn),然后反射HttpUrlFetcher.connectionFactory字段并賦值。這就是整體的思路
反射這里就不講了,主要是如何實(shí)現(xiàn)HttpUrlConnectionFactory接口呢埃撵?
動(dòng)態(tài)代理,如果看過(guò)retrofit源碼的話應(yīng)該不難理解洲赵。
首先跟方案二一樣,自定義MyGlideUrlLoader,使用register.replace替換默認(rèn)的loader。
在MyGlideUrlLoader中buildLoadData方法進(jìn)行復(fù)寫:
public class MyHttpGlideUrlLoader extends HttpGlideUrlLoader {
private static final String TAG = MyHttpGlideUrlLoader.class.getSimpleName();
public MyHttpGlideUrlLoader(ModelCache<GlideUrl, GlideUrl> modelCache) {
super(modelCache);
}
@Override
public LoadData<InputStream> buildLoadData(GlideUrl model, int width, int height, Options options) {
LoadData old = super.buildLoadData(model, width, height, options);//if proy is not null,drop the result,just set the modelCache
if (!MyHttpUrlConnectionFactory.existProxy()) {
return old;
}
//反射出HttpUrlFetcher$HttpUrlConnectionFactory接口
Class factoryInterface = null;
try {
factoryInterface = Class.forName("com.bumptech.glide.load.data.HttpUrlFetcher$HttpUrlConnectionFactory");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
//動(dòng)態(tài)代理這個(gè)接口
Object factoryProxy = null;
if (factoryInterface != null) {
factoryProxy = java.lang.reflect.Proxy.newProxyInstance(factoryInterface.getClassLoader(),
new Class[]{factoryInterface},
new MyInvocationHandler(new MyHttpUrlConnectionFactory()));
}
if (old != null && old.fetcher != null && factoryProxy != null) {
try {
//給connectionFactory字段賦新的值
Field factoryField = HttpUrlFetcher.class.getDeclaredField("connectionFactory");
factoryField.setAccessible(true);
factoryField.set(old.fetcher, factoryProxy);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
return old;
}
private static class MyInvocationHandler implements InvocationHandler {
private MyHttpUrlConnectionFactory factory;
public MyInvocationHandler(MyHttpUrlConnectionFactory f) {
factory = f;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
URL url = null;
if(args != null && args.length > 0 && args[0] instanceof URL){
url = (URL) args[0];
}
if(url != null){
return factory.build(url);
}
return method.invoke(proxy,args);
}
}
//代理類中真正的執(zhí)行是這個(gè)類
public static class MyHttpUrlConnectionFactory {
private static Proxy sProxyConfig;
public static void setProxy(Proxy proxy,final String username,final String pwd) {
if (!TextUtils.isEmpty(username) && !TextUtils.isEmpty(pwd)) {
Authenticator authenticator = new Authenticator() {
public PasswordAuthentication getPasswordAuthentication() {
return (new PasswordAuthentication(username,
pwd.toCharArray()));
}
};
Authenticator.setDefault(authenticator);
} else {
Authenticator.setDefault(null);
}
MyHttpUrlConnectionFactory.sProxyConfig = proxy;
}
public static void clearProxy() {
setProxy(null,null,null);
}
public static boolean existProxy() {
return sProxyConfig != null;
}
public MyHttpUrlConnectionFactory() {
}
public HttpURLConnection build(URL url) throws IOException {
HttpURLConnection conn = null;
if (existProxy()) {
conn = (HttpURLConnection) url.openConnection(sProxyConfig);
} else {
conn = (HttpURLConnection) url.openConnection();
}
return conn;
}
}
/**
* The default factory for {@link MyHttpGlideUrlLoader}s.
*/
public static class Factory implements ModelLoaderFactory<GlideUrl, InputStream> {
private final ModelCache<GlideUrl, GlideUrl> modelCache = new ModelCache<>(500);
@Override
public ModelLoader<GlideUrl, InputStream> build(MultiModelLoaderFactory multiFactory) {
return new MyHttpGlideUrlLoader(modelCache);
}
@Override
public void teardown() {
// Do nothing.
}
}
}
方案三優(yōu)點(diǎn):
- 代碼改動(dòng)量不大
- 使用反射和動(dòng)態(tài)代理拇厢,比方案二看上去高大上了點(diǎn)邪媳,動(dòng)態(tài)代理之前只是看沒(méi)有實(shí)踐過(guò)迅涮,呵呵。徽龟。叮姑。。
缺點(diǎn):
- 需要理解反射和動(dòng)態(tài)代理
- 在Glide后續(xù)版本升級(jí)中,如果com.bumptech.glide.load.data.HttpUrlFetcher$HttpUrlConnectionFactory名稱改了传透,或者com.bumptech.glide.load.data.HttpUrlFetcher.connectionFactory字段名稱改了耘沼,就歇菜了。朱盐。群嗤。
綜上
個(gè)人還是傾向于方案一,改動(dòng)量很小兵琳,但是在項(xiàng)目中要求不能改動(dòng)第三方代碼狂秘,所以使用了方案三