preface

Recently, I checked the crash caused by Glide loading pictures in a project, and the abnormality was as follows:

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

A bit of background: This is because Glide 4.x is used in both the main and Library modules, and the Generated API in 4.x is used in both modules, and the AppGlideModule is used in both modules. At the same time, GlideApp is used for image loading.

Glide Document has this passage:

The library must not include the AppGlideModule implementation. Doing so will prevent any applications that rely on the library from managing their dependencies or configuring options such as Glide cache size and location. In addition, if both libraries contain AppGlideModule, the application will not be able to compile without relying on both libraries and will have to choose between them.

In other words, the problem I encountered was a wrong usage. So the spirit of grasping the root of the inquiry, the author studied the emergence of the collapse of the specific reason is what? Hence the article.

Mock code and annotation generation code

First we need to prepare two modules and implement the AppGlideModule separately:

  • The main moduleme.xcyoung.study
package me.xcyoung.study;

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

@GlideModule
public class MyAppModule extends AppGlideModule {}Copy the code
  • Lib moduleme.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 {}Copy the code

When compiled, it will generate:

The generated code is annotated here to give you an idea of what will be covered later.

Glide initialization

A brief introduction to Glide initialization. Glide globally maintains a static Glide object as the only environment in which the App loads images. Ps: Application level things, regardless of which module code.

private static volatile Glide glide;
Copy the code

To use it, get the static variable through the get method, and if not, try to initialize it. Ps: This is a lazy loading design

@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;
}
Copy the code

Then we put the focus on the get method getAnnotationGeneratedGlideModules method in the call.

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) {
  ...
Copy the code

Method through reflection, instance of a com bumptech. Glide. GeneratedAppGlideModuleImpl object. This is the annotation generated code described in the mock code and annotation generated code section. The class then triggers our custom global configuration during initialization, which we won’t parse here.

According to the screenshot above can be found, because the two modules are realized AppGlideModule, so both module generates GeneratedAppGlideModuleImpl. So let’s focus on the differences between these two classes.

Two GeneratedAppGlideModuleImpl

  • The main moduleme.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"); }}Copy the code
  • Lib moduleme.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"); }}Copy the code

Simple comparison can be found that generate the same two GeneratedAppGlideModuleImpl package name, the mark is its holdings of AppGlideModule subclasses are not identical, that is, a is defined in the main module, one is in the lib module definition.

But take a closer look at compile apk package can be found that two GeneratedAppGlideModuleImpl. Class exists in different dex file.

Combining previous getAnnotationGeneratedGlideModules method it is not hard to see, in this paper, the problems are found one GeneratedAppGlideModuleImpl JVM loads, leading to the collapse of the follow-up questions.

The location that triggered the crash

Point of collapse:

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

These two GlideRequests are ests in two modules. The package name corresponds to the package name of the module. The parent class is RequestManager. The exception is triggered when a RequestManager object (ps: GlideRequests parent) needs to be retrieved before loading the image. Here is an example of an on-load 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);
}
Copy the code

Here a RequestManager object is instantiated with RequestManagerFactory#build. In simulation code and annotations in the annotations in the generated code, code generation is GeneratedRequestManagerFactory RequestManagerFactory subclasses. Ps: note that there also exist two GeneratedRequestManagerFactory.

private static void initializeGlide(
    @NonNull Context context,
    @NonNull GlideBuilder builder,
    @Nullable GeneratedAppGlideModule annotationGeneratedModule) {
    / / annotationGeneratedModule GeneratedAppGlideModuleImpl namely. RequestManagerRetriever.RequestManagerFactory factory = annotationGeneratedModule ! =null
            ? annotationGeneratedModule.getRequestManagerFactory()
            : null; builder.setRequestManagerFactory(factory); . }Copy the code

The above code annotationGeneratedModule GeneratedAppGlideModuleImpl namely. In Glide initialization, through GeneratedAppGlideModuleImpl get GeneratedRequestManagerFactory and set to Glide object.

Let’s take a look at the main module generated 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 newGlideRequests(glide, lifecycle, treeNode, context); }}Copy the code

What is returned here is the GlideRequests object for the main module, the RequestManager.

conclusion

Here, the emergence of the whole collapse problem is also looming.

  1. Because of the two modulesThe AppGlideModule is also implemented, resulting in formationTwo packages with the same nametheGeneratedAppGlideModuleImplandGeneratedRequestManagerFactory.
  2. two-moduleGeneratedAppGlideModuleImplandGeneratedRequestManagerFactoryBoth contain the code logic of their respective modules, includingCustom AppGlideModule and their generated GlideRequests.
  3. Acquired by reflection when Glide is initializedGeneratedAppGlideModuleImpl, at this momentThe JVM takes the first of the two.Such as access to the main module GeneratedAppGlideModuleImpl. So in the next step,All logic is defined according to the main module.
  4. inThe lib module loads the imageIs called because you need to get the RequestManagerGeneratedRequestManagerFactory#buildMethod,What you should get are the GlideReuqests of the lib module(me.xcyoung.lib.test.GlideRequests). But since the 3 above is obtainedThe main module, soThese are the GlideRequests of the main module(me.xcyoung.study.GlideRequests).
  5. When loading images with GlideApp, you need to get your module’s GlideRequests. Combined with the above analysis, the anomaly of strong turn failure is finally caused.

The last

This article focuses on why the AppGlideModule for glide4.x can only be defined at the Application level module. Focus on the analysis of Glide related design problems. In short, use third-party libraries based on official documentation and understanding. Otherwise it will create similar bad problems.

Reference article:

Glide Note (1) Used by the AppGlideModule

Configuration method of GlideApp(AppGlideModule) in new Glide