为什么Glide4.x中的AppGlideModule不应该出现在Library中

前言

最近排查了一个项目中的因Glide加载图片引起的崩溃,大概的异常时这样的:

com.xxx.xxx.GlideRequests can not cast to com.xxx.xxx.GlideRequests

先说一下背景:这是由于主模块和Library模块中同时依赖了Glide 4.x,并同时使用了4.x中的Generated API,而且两个模块下都使用了AppGlideModule。 且同时使用了GlideApp进行图片加载

Glide文档中有这样一段话:

程序库一定 不要 包含 AppGlideModule 实现。这么做将会阻止依赖该库的任何应用程序管理它们的依赖,或配置诸如 Glide 缓存大小和位置之类的选项。此外,如果两个程序库都包含 AppGlideModule,应用程序将无法在同时依赖两个库的情况下通过编译,而不得不在二者之中做出取舍。

也就是说其实笔者遇到的问题是一个错误的用法。那么秉着查根问底的精神,笔者又研究了一下出现该崩溃的具体原因是什么?所以就有了这篇文章。

模拟代码及注解生成代码

首先我们需要准备两个模块,并分别实现AppGlideModule

  • 主模块 me.xcyoung.study
package me.xcyoung.study;

import com.bumptech.glide.annotation.GlideModule;
import com.bumptech.glide.module.AppGlideModule;

@GlideModule
public class MyAppModule extends AppGlideModule {
}
复制代码
  • lib模块 me.xcyoung.study.lib.test
package me.xcyoung.lib.test;

import com.bumptech.glide.annotation.GlideModule;
import com.bumptech.glide.module.AppGlideModule;

@GlideModule
public class MyLibModule extends AppGlideModule {
}
复制代码

编译后会在各自模块中生成: image.png

image.png

这里注解生成的代码,可以先有个印象,后面有涉及到

Glide初始化

简单介绍一下Glide的初始化。Glide会全局维护一个静态的Glide对象,作为App加载图片的唯一环境。ps:属于Application级别的事情,不区分是哪个module的代码

private static volatile Glide glide;
复制代码

在使用的时候,先通过get方法获取该静态变量,如果没有就尝试初始化。ps:这里是一个懒加载的设计

@NonNull
public static Glide get(@NonNull Context context) {
  if (glide == null) {
    // 1
    GeneratedAppGlideModule annotationGeneratedModule =
        getAnnotationGeneratedGlideModules(context.getApplicationContext());
    synchronized (Glide.class) {
      if (glide == null) {
        checkAndInitializeGlide(context, annotationGeneratedModule);
      }
    }
  }

  return glide;
}
复制代码

接下来我们把关注点放在get方法中的调用的getAnnotationGeneratedGlideModules方法。

private static GeneratedAppGlideModule getAnnotationGeneratedGlideModules(Context context) {
  GeneratedAppGlideModule result = null;
  try {
    Class<GeneratedAppGlideModule> clazz =
        (Class<GeneratedAppGlideModule>)
            Class.forName("com.bumptech.glide.GeneratedAppGlideModuleImpl");
    result =
        clazz.getDeclaredConstructor(Context.class).newInstance(context.getApplicationContext());
  } catch (ClassNotFoundException e) {
  ...
复制代码

方法通过反射,实例了一个com.bumptech.glide.GeneratedAppGlideModuleImpl对象。这个也就是在模拟代码及注解生成代码一节中说到的注解生成的代码。紧接着该类就会在初始化的过程中触发我们自定义的全局配置,这里就不详细解析了。

根据上面的截图可以发现,由于两个模块都实现了AppGlideModule,所以两个模块中都生成GeneratedAppGlideModuleImpl。所以接下来我们重点来看看这两个类的区别。

两个GeneratedAppGlideModuleImpl

  • 主模块 me.xcyoung.study
final class GeneratedAppGlideModuleImpl extends GeneratedAppGlideModule {
  private final MyAppModule appGlideModule;

  public GeneratedAppGlideModuleImpl(Context context) {
    appGlideModule = new MyAppModule();
    if (Log.isLoggable("Glide", Log.DEBUG)) {
      Log.d("Glide", "Discovered AppGlideModule from annotation: me.xcyoung.study.MyAppModule");
    }
  }
复制代码
  • lib模块 me.xcyoung.study.lib.test
final class GeneratedAppGlideModuleImpl extends GeneratedAppGlideModule {
  private final MyLibModule appGlideModule;

  public GeneratedAppGlideModuleImpl(Context context) {
    appGlideModule = new MyLibModule();
    if (Log.isLoggable("Glide", Log.DEBUG)) {
      Log.d("Glide", "Discovered AppGlideModule from annotation: me.xcyoung.lib.test.MyLibModule");
    }
  }
复制代码

简单对比可以发现,生成包名相同的两个GeneratedAppGlideModuleImpl,区别点在于其持有的AppGlideModule子类不相同,即一个是在主模块定义的,一个是在lib模块定义的。

而仔细查看编译的apk包内可以发现,两个GeneratedAppGlideModuleImpl.class存在于不同的dex文件中。

image.png

image.png

再结合之前的getAnnotationGeneratedGlideModules方法不难看出,本文遇到的问题是JVM加载时找到了其中一个GeneratedAppGlideModuleImpl,从而导致了后续的崩溃问题

触发崩溃的位置

崩溃的点:

com.xxx.xxx.GlideRequests can not cast to com.xxx.xxx.GlideRequests

这两个GlideRequests分别在两个模块中包名对应的是各自模块的包名,其父类为RequestManager。而触发该异常的位置在于在加载图片前需要获取一个RequestManager的对象(ps:GlideRequests的父类)。这里以FragmentActivity在载的情况为例。

@NonNull
private RequestManager supportFragmentGet(
    @NonNull Context context,
    @NonNull FragmentManager fm,
    @Nullable Fragment parentHint,
    boolean isParentVisible) {
  SupportRequestManagerFragment current =
      getSupportRequestManagerFragment(fm, parentHint, isParentVisible);
  RequestManager requestManager = current.getRequestManager();
  if (requestManager == null) {
    Glide glide = Glide.get(context);
    // Tag 1
    requestManager =
        factory.build(
            glide, current.getGlideLifecycle(), current.getRequestManagerTreeNode(), context);
    current.setRequestManager(requestManager);
  }
  return requestManager;
}

// Tag 2
public interface RequestManagerFactory {
  @NonNull
  RequestManager build(
      @NonNull Glide glide,
      @NonNull Lifecycle lifecycle,
      @NonNull RequestManagerTreeNode requestManagerTreeNode,
      @NonNull Context context);
}
复制代码

这里会通过RequestManagerFactory#build实例一个RequestManager对象。在模拟代码及注解生成代码一节中的注解生成代码中,GeneratedRequestManagerFactory就是RequestManagerFactory子类。ps:注意这里也是存在两个GeneratedRequestManagerFactory

private static void initializeGlide(
    @NonNull Context context,
    @NonNull GlideBuilder builder,
    @Nullable GeneratedAppGlideModule annotationGeneratedModule) {
    // annotationGeneratedModule即GeneratedAppGlideModuleImpl
    ...
    RequestManagerRetriever.RequestManagerFactory factory =
        annotationGeneratedModule != null
            ? annotationGeneratedModule.getRequestManagerFactory()
            : null;
    builder.setRequestManagerFactory(factory);
    ...
}
复制代码

上述代码中annotationGeneratedModuleGeneratedAppGlideModuleImpl。在Glide初始化中,会通过GeneratedAppGlideModuleImpl获取到GeneratedRequestManagerFactory并设置到Glide对象中。

我们来看看主模块中生成的GeneratedRequestManagerFactory

import me.xcyoung.study.GlideRequests;

/**
 * Generated code, do not modify
 */
final class GeneratedRequestManagerFactory implements RequestManagerRetriever.RequestManagerFactory {
  @Override
  @NonNull
  public RequestManager build(@NonNull Glide glide, @NonNull Lifecycle lifecycle,
      @NonNull RequestManagerTreeNode treeNode, @NonNull Context context) {
    return new GlideRequests(glide, lifecycle, treeNode, context);
  }
}
复制代码

这里返回的是主模块的GlideRequests对象,即RequestManager。

总结

到这里,整个崩溃问题的出现也就呼之欲出了。

  1. 由于两个模块同时实现了AppGlideModule,导致了生成两个相同包名GeneratedAppGlideModuleImplGeneratedRequestManagerFactory
  2. 两个模块的GeneratedAppGlideModuleImplGeneratedRequestManagerFactory都包含各自模块的代码逻辑,包括自定义AppGlideModule以及各自生成的GlideRequests类等
  3. 在Glide初始化时,通过反射获取GeneratedAppGlideModuleImpl,这时JVM从两个中获取最先出现的一个譬如获取到主模块的GeneratedAppGlideModuleImpl。所以在后续操作中,一切逻辑都会根据主模块的定义
  4. lib模块加载图片时,由于需要获取RequestManager,所以会调用到GeneratedRequestManagerFactory#build方法,理应获取到的是lib模块的GlideReuqestsme.xcyoung.lib.test.GlideRequests)。但由于上述3中获取的是主模块的,所以获取到的是主模块的GlideRequestsme.xcyoung.study.GlideRequests)。
  5. 使用GlideApp加载图片时,需要获取自己模块的GlideRequests对象。结合上述几点的分析,最终就造成强转失败的异常。

最后

本文主要分析了为什么Glide4.x的AppGlideModule只能在Application级别模块定义的问题。重点分析涉及到Glide的设计问题。总而言之,使用第三方库还是要根据官方文档,并自己加以理解。否则就会造成类似的不良问题。

参考文章:

Glide笔记(一)AppGlideModule使用

Glide 新版中GlideApp(AppGlideModule)配置方法

猜你喜欢

转载自juejin.im/post/7074774758063079437