Glide 4.x添加自定义组件原理

在《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接口,具体的后面在说明。

3.X版本集成Okhttp的方法步骤

Glide集成Okhttp的方法很简单也就两步(详细见官网)

1、在Gradle配置配置如下一句:

dependencies {
    compile 'com.github.bumptech.glide:okhttp3-integration:1.5.0@aar'
}

2、在AndroidMainfext.xml添加如下代码:

<meta-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<GlideModule> 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的操作:

扫描二维码关注公众号,回复: 1754423 查看本文章
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<GeneratedAppGlideModule> clazz =
      (Class<GeneratedAppGlideModule>)           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 <Model, Data> Registry replace(Class<Model> modelClass, Class<Data> dataClass,
    ModelLoaderFactory<Model, Data> 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等方法来添加而已。

到此位置,本篇博文结束,整体感觉有点啰嗦,不当之处欢迎批评指正,共同学习。

猜你喜欢

转载自blog.csdn.net/chunqiuwei/article/details/78493288