在《Glide 4.x之ModelLoader简单分析》(在阅读本篇博客之前,强烈建议先读这篇博客,否则有点上下文不衔接的感觉)解析我们知道了ModelLoader的一些工厂类都通过都是通过Registry这个类来登记注册的,且通过我们的怼ModelLoader的讲解,我们知道了Glide访问网络数据是通过UrlConnection,且其核心配置为
registry.append(Uri.class, InputStream.class, new HttpUriLoader.Factory())
但是开发过程中我们想要使用别的网络库怎么办呢?在我们实际的项目中用的Glide经常会遇到timeout的问题导致图片加载不出来,当时用的Glide3.7.0,看了其源码发现该版本的超市时间时候写死的2500ms,对外也没有提供超时的时间接口,而且我手头项目中使用的是OKhttp来发起网络请求,且我们在项目中对OKttp做了二次封装,为此我们就使用okhttp来发起glide的图片请求。
因为项目中使用的是3.x的版本,先从3.x版本分析,然后还回到4.2.0版本进行分析,不过在这里先提前说一句Glide3.x版本通过实现GlideModule接口来添加自定义组件,而Glide4.x版本则摒弃了GlideModule接口转而使用LibraryGlideModule接口,具体的后面在说明。
Glide集成Okhttp的方法很简单也就两步(详细见官网)
1、在Gradle配置配置如下一句:
dependencies {
compile 'com.github.bumptech.glide:okhttp3-integration:1.5.0@aar'
}
2、在AndroidMainfext.xml添加如下代码:
data
android:name="com.bumptech.glide.integration.okhttp.OkHttpGlideModule"
android:value="GlideModule" />
看完这两个步骤,其实思路也很简单,无非就是在初始化的时候解析AndroidManifest.xml文件将OkHttpGlideModule加载进来,观察glide初始化代码就可以知道。Glide 3.7.0的初始化Glide方法如下:
public static Glide get(Context context) {
if (glide == null) {
synchronized (Glide.class) {
if (glide == null) {
//1、解析清单文件获取GlideModule集合
List modules = new ManifestParser(applicationContext).parse();
//2、初始化构建者模式对象
GlideBuilder builder = new GlideBuilder(applicationContext);
//3、循环集合调用GlideModule的applyOptions方法
for (GlideModule module : modules) {
module.applyOptions(applicationContext, builder);
}
//4、创建glide对象
glide = builder.createGlide();
//5、注册自定义组件
for (GlideModule module : modules) { module.registerComponents(applicationContext, glide);
}
}
}
}
return glide;
}
上面的代码核心逻辑就是解析AndroidMainfest.xml文件获取用户配置的GlideModule对象集合,然后遍历GlideModule集合,先运行GlideModule接口的applyOptions方法往builder里面构建自己的组件,而后调用registerComponents方法来将组件注册到glide里。
以上文提到的OkHttpGlideModule为例,其源码为:
public class OkHttpGlideModule implements GlideModule {
@Override
public void applyOptions(Context context, GlideBuilder builder) {
// 此处什么都没做,冗余空方法.
}
@Override
public void registerComponents(Context context, Glide glide) {
//注册使用Okhttp发起网络访问
glide.register(GlideUrl.class, InputStream.class, new OkHttpUrlLoader.Factory());
}
}
如果阅读了博主的《Glide 4.x之ModelLoader简单分析》文章的话不难理解上面registerComponents方法都做了些什么,此处就不在赘述。也就是说Glide3.x版本让客户端实现GlideModule接口来添加自己的组件,该接口也很简单:
public interface GlideModule {
//该方法先执行
void applyOptions(Context context, GlideBuilder builder);
//该方法后执行
void registerComponents(Context context, Glide glide);
}
applyOptions就是参数里面有一个GlideBuider对象,构建者对象的作用不言而喻,我们可以通过GlideBuider对象来添加自己的配置。此方法执行过后Glide对象并没有初始化出来,在执行该方法过后如上面get方法所以会初始化glide,然后将初始化的glide传递给registerComponents方法执行,这样我们就可以操控glide对象添加自己想添加的东西了。也就是说applyOptions允许我们操控GlideBuilder对象,通过构建者模式不断构建我们所需的组件,而registerComponents允许我们直接操控glide对象来完成一些操作,比如上面okhttp的集成。
在此我提一个问题:必须通过配置android MainFest文件来完成上述的操作吗?答案当然不是的,因为对于插件话的开发来说很可能没有MainFest.xml文件(博主在和客户对接SDK的时候就遇到过这种问题,博主开发中使用的版本是3.7,0),而get方法又要对清淡文件进行扫描解析,因为没有清单文件导致调用get方法的时候报错,app直接崩溃。解决这个方法很简单,遍静态注册为动态注册,初始化Glide的代码在get方法之前完成,下面几行代码就搞定了没有清单文件,而又想使用Okhttp的操作:
GlideBuilder builder = new GlideBuilder(context);
/*初始化Glide的单利对象*/
Glide.setup(builder);
OkHttpUrlLoader.Factory factory = new OkHttpUrlLoader.Factory();
/*此时调用get方法因为glide对象已初始化,不会再走
get方法内部的初始化,从而避免了对清单文件的扫描
*/
Glide.get(context).register(GlideUrl.class, InputStream.class, factory);
不过从Okhttp的集成来看,GlideModule这个接口的设计并不是很合理或者说没有做到单一职责,因为我可能只需要对GlideBuider进行操作,而不需要直接操作Glide。换句话说就是我可能只需要applyOptions方法就行了,不需要registerComponents。也可能是只需要registerComponents不需要applyOptions。从OkHttpGlideModule来看我们只需要registerComponents,而根本不需要applyOptions方法,造成applyOptions的冗余。这一设计的缺陷在在Glide4.X的时候得到的改正:
Glide4.X 版本添加自定义组件
4.x版本使用需要如下配置:
dependencies {
compile 'com.github.bumptech.glide:glide:4.3.1'
annotationProcessor 'com.github.bumptech.glide:compiler:4.3.1’
}
此处还是以集成OKhttp来说明情况,如果需要集成Okhttp的话还需要添加一句:
compile "com.github.bumptech.glide:okhttp3-integration:4.3.1”
是不是感觉so easy?在Okhttp工作的地方打个断点,debug一把发现glide不想跟我“说话”,并且丢给了我一行bug:
Failed to find GeneratedAppGlideModule. You should include an annotationProcessor compile dependency on com.github.bumptech.glide:compiler in your application and a @GlideModule annotated AppGlideModule implementation or LibraryGlideModules will be silently ignored
排查发现经过在Glide类的getAnnotationGeneratedGlideModules方法在找不到GeneratedAppGlideModuleImpl类时会有这个异常log输出:
try {
Class clazz =
(Class) Class.forName("com.bumptech.glide.GeneratedAppGlideModuleImpl");
result = clazz.newInstance();
} catch (ClassNotFoundException e) {
if (Log.isLoggable(TAG, Log.WARN)) {
Log.w(TAG, "Failed to find GeneratedAppGlideModule. You should include an"
+ " annotationProcessor compile dependency on com.github.bumptech.glide:compiler"
+ " in your application and a @GlideModule annotated AppGlideModule implementation or"
+ " LibraryGlideModules will be silently ignored");
}
GeneratedAppGlideModule是一个抽象类,且需要通过类名来获取GeneratedAppGlideModuleImpl的对象,但是在glide的包里面并没有找到这个类,然后Google了一会儿还没搜到解决方案,但是我突然想到上面的gradle里面配置了annotationProcessor这个家伙(关于这个的简单说明可以参考此篇博文然后就知道大致原因了,然后看官网有这么一段配置:
果断应用的一个包里面添加MyAppGlideModule这个类,然后rebuild一下项目果然不出所料GeneratedAppGlideModuleImpl出来了,如图:
此时在请求一次http图片请求,果然走的是OKhttp的逻辑,下面就来抽丝拨茧,说下其内部的原理:
看看GeneratedAppGlideModuleImpl的源码发现我们定义的MyAppGlideModule已经作GeneratedAppGlideModuleImp类的一个引用在里面了。该上文讨论3.X的时候我们为了不扫描Manifest.xml文件不得不改变Glide的初始化方式,也就是说如果使用3.x默认的初始化方式的话无论如何都避免不了清单文件的扫描,但是这种情况在4.x中得到了改善,解决方法就是在GeneratedAppGlideModuleImpl类里面有一个isManifestParsingEnabled方法,如果改方法返回了false则不对Mainfest文件进行扫描解析:
@Override
public boolean isManifestParsingEnabled() {
return appGlideModule.isManifestParsingEnabled();
}
在详细GeneratedAppGlideModuleImpl之前有必要说说GeneratedAppGlideModuleImpl继承关系(偷个懒不画UMLl了):
GeneratedAppGlideModuleImpl->AppGlideModule->LibraryGlideModule—>AppliesOptions
LibraryGlideModule—> RegistersComponents
还记得上文说到GlideModule这个接口设计的不合理吗,在4.x版本就将3.x的GlideModule接口拆分为两个接口RegistersComponents和AppliesOptions。根据上面对3.x的讲解我们来重点看看GeneratedAppGlideModuleImpl的applyOptions和registerComponents方法:
public void applyOptions(Context context, GlideBuilder builder) {
appGlideModule.applyOptions(context, builder);
}
该方法直接调用appGlideModule.的applyOptions方法,此方法可以为空,但是此方法清楚的告诉我们一个信息:我们可以直接操控GilideBuilder对象搞事情:比如通过builder对象来配置自己的缓存策略等。
下面来分析GeneratedAppGlideModuleImpl 对象的第二个重要方法registerComponents
public void registerComponents(Context context, Glide glide, Registry registry) {
//配置OKhttp组件
new OkHttpLibraryGlideModule().registerComponents(context, glide, registry);
//配置其他的插件
appGlideModule.registerComponents(context, glide, registry);
}
如上所示上面代码显示了配置OKhttp的地方,如果想配置Okhttp则需要调用OkHttpLibraryGlideModule的registerComponents方法,所以让我们看看该方法都做了些啥:
public final class OkHttpLibraryGlideModule extends LibraryGlideModule {
public void registerComponents(Context context, Glide glide, Registry registry) {
//添加自己的网络请求库
registry.replace(GlideUrl.class, InputStream.class, new OkHttpUrlLoader.Factory());
}
}
直接调用了registry的replace方法,有关Registry类的初步说明情参考上篇博文,上面调用了replace方法,那么replace了谁呢?本篇博文开头说了Glide发起网络请求的配置是就是:
append(GlideUrl.class, InputStream.class, new HttpGlideUrlLoader.Factory())
那么我们有理由判断这个replace替换的就是HttpGlideUrlLoader.Factory这个东东。用代码来说话,看看replace方法:
public Registry replace(Class modelClass, Class dataClass,
ModelLoaderFactory factory) {
modelLoaderRegistry.replace(modelClass, dataClass, factory);
return this;
}
然后调用了modelLoaderRegistry的replace方法,如果不明白modelLoaderRegistry,可以参考这个图片:
public synchronized <Model, Data> void replace(Class<Model> modelClass, Class<Data> dataClass,
ModelLoaderFactory<Model, Data> factory) {
//1、调用multiModelLoaderFactory的replace方法
List<ModelLoaderFactory<Model, Data>> factories = multiModelLoaderFactory.replace(modelClass, dataClass, factory)
//2、遍历factories集合
tearDown(factories);
cache.clear();
}
上面的代码调用了multiModelLoaderFactory对象的replace方法,继续深入之:
synchronized <Model, Data> List<ModelLoaderFactory<Model, Data>> replace(Class<Model> modelClass,
Class<Data> dataClass, ModelLoaderFactory<Model, Data> factory) {
//根据modelClass和dataClass来匹配删除glide本身注册factory对象
//因为remove的逻辑比较简单,就不在贴具体实现的源码
List<ModelLoaderFactory<Model, Data>> removed = remove(modelClass, dataClass);
//添加我们自定义的factory对象
append(modelClass, dataClass, factory);
return removed;
}
到此为止,Okhttp的组件库已经成功的添加到了glide中,但是什么时候开始扫描GeneratedAppGlideModule的呢?肯定是初始化Glide的时候开始扫描,要不然图片都加载出来了在扫描添加Okhttp还搞毛,所以我们看看Glide的初始化方法initializeGlide:
private static void initializeGlide(Context context) {
Context applicationContext = context.getApplicationContext();
/*根据注解获取GeneratedAppGlideModuleImpl对像*/
GeneratedAppGlideModule annotationGeneratedModule = getAnnotationGeneratedGlideModules();
List<com.bumptech.glide.module.GlideModule> manifestModules = Collections.emptyList();
/*是否扫描Mainfest.mxl*/
if (annotationGeneratedModule == null || annotationGeneratedModule.isManifestParsingEnabled()) {
manifestModules = new ManifestParser(applicationContext).parse();
}
/*省略大量代码*/
if (annotationGeneratedModule != null) {
/*执行GeneratedAppGlideModuleImpl的applyOptions*/ annotationGeneratedModule.applyOptions(applicationContext, builder);
}
/*初始化Glide*/
Glide glide = builder.build(applicationContext);
/*省略部分代码*/
if (annotationGeneratedModule != null) {
/*真正添加自己的组件的地方,比如OKhttp*/ annotationGeneratedModule.registerComponents(applicationContext, glide, glide.registry);
}
}
上面的代码去掉与本文无关的逻辑之后主要是:
1、获取GeneratedAppGlideModuleImpl对象,该对象的产生过程见上文
2、执行GeneratedAppGlideModuleImpl的applyOptions方法
3、执行GeneratedAppGlideModuleImpl的registerComponents将Okhttp等自定义的组件添加到glide中。
其实还有一个更简单的添加自定义组件的方法,根本不用配置GeneratedAppGlideModuleImpl或者扫描Mainfest.xml这么麻烦,只需要如下四个步骤即可:
1、创建GlideBuider对象:
GlideBuilder builder = new GlideBuilder();
2、创建Glide对象
Glide glide = builder.build(applicationContext);
3、因为Glide完成初始化的时候Registry对象也初始化完毕,并且我们可以通过glide.getRegistry()获取到该对象,所以通过操作glide对象直接添加Okhttp等自定义组件:
glide.getRegistry().replace(GlideUrl.class, InputStream.class, new OkHttpUrlLoader.Factory());
4、最后别忘了最重要的一句:
Glide.init(glide)
其实不论时通过注解还是上面的三个操作步骤都是殊途同归,其内部原理就是操作Registry对象,然后将自定义组件通过Registry提供的append或者replace等方法来添加而已。
到此位置,本篇博文结束,整体感觉有点啰嗦,不当之处欢迎批评指正,共同学习。