Singleton mode of Glide design mode

Singleton definition

It belongs to the creation mode; Only one object is created globally.

Glide usage form

Glide.with(context). . . 

Glide single case writing

context check

/**
   * @see #with(android.app.Activity)
   * @see #with(android.app.Fragment)
   * @see #with(androidx.fragment.app.Fragment)
   * @see #with(androidx.fragment.app.FragmentActivity)
   */
  @NonNull
  public static RequestManager with(@NonNull Context context) {
    return getRetriever(context).get(context);
  }
  . . . . . . 

@NonNull
  private static RequestManagerRetriever getRetriever(@Nullable Context context) {
    // Context could be null for other reasons (ie the user passes in null), but in practice it will
    // only occur due to errors with the Fragment lifecycle.
    Preconditions.checkNotNull(
        context,
        "You cannot start a load on a not yet attached View or a Fragment where getActivity() "
            + "returns null (which usually occurs when getActivity() is called before the Fragment "
            + "is attached or after the Fragment is destroyed).");
    return Glide.get(context).getRequestManagerRetriever();
  }

Glide.get(context) single instance start

 @GuardedBy("Glide.class")
  private static volatile Glide glide;

/**
   * Get the singleton.
   *
   * @return the singleton
   */
  @NonNull
  // Double checked locking is safe here.
  @SuppressWarnings("GuardedBy")
  public static Glide get(@NonNull Context context) {
    if (glide == null) {
      GeneratedAppGlideModule annotationGeneratedModule =
          getAnnotationGeneratedGlideModules(context.getApplicationContext());
      synchronized (Glide.class) {
        if (glide == null) {
          checkAndInitializeGlide(context, annotationGeneratedModule);
        }
      }
    }

    return glide;


Volatile: in the lazy loading singleton mode with only DCL but no volatile, there is still a concurrency trap. Interested can see the reference< Singleton mode volatile >Or search "volatile singleton mode". I believe the principles of concurrency and memory model will make you shout wonderful

@Nullable
  @SuppressWarnings({"unchecked", "TryWithIdenticalCatches", "PMD.UnusedFormalParameter"})
  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) {
      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");
      }
      // These exceptions can't be squashed across all versions of Android.
    } catch (InstantiationException e) {
      throwIncorrectGlideModule(e);
    } catch (IllegalAccessException e) {
      throwIncorrectGlideModule(e);
    } catch (NoSuchMethodException e) {
      throwIncorrectGlideModule(e);
    } catch (InvocationTargetException e) {
      throwIncorrectGlideModule(e);
    }
    return result;
  }

The first time I saw the GeneratedAppGlideModule annotationGeneratedModule=
getAnnotationGeneratedGlideModules(context.getApplicationContext());
I was surprised that there was no in the previous version. I saw the log of the source code:

"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"

Obviously, it is to verify whether dependencies and annotations are added
Examples of dependencies are as follows:

annotationProcessor 'com.github.bumptech.glide:compiler:4.12.0'

Examples of notes are as follows:

@GlideModule
public final class MyAppGlideModule extends AppGlideModule {}

Therefore, it can be seen that this is to check the use environment. Establish escort for subsequent singleton objects.

Singleton object creation

 @GuardedBy("Glide.class")
  private static void checkAndInitializeGlide(
      @NonNull Context context, @Nullable GeneratedAppGlideModule generatedAppGlideModule) {
    // In the thread running initGlide(), one or more classes may call Glide.get(context).
    // Without this check, those calls could trigger infinite recursion.
    if (isInitializing) {
      throw new IllegalStateException(
          "You cannot call Glide.get() in registerComponents(),"
              + " use the provided Glide instance instead");
    }
    isInitializing = true;
    initializeGlide(context, generatedAppGlideModule);
    isInitializing = false;
  }

Initialize the identification check, and then proceed to:

@GuardedBy("Glide.class")
  private static void initializeGlide(
      @NonNull Context context, @Nullable GeneratedAppGlideModule generatedAppGlideModule) {
    initializeGlide(context, new GlideBuilder(), generatedAppGlideModule);
  }
@GuardedBy("Glide.class")
  @SuppressWarnings("deprecation")
  private static void initializeGlide(
      @NonNull Context context,
      @NonNull GlideBuilder builder,
      @Nullable GeneratedAppGlideModule annotationGeneratedModule) {
    Context applicationContext = context.getApplicationContext();
    //Create an empty collection object. Avoid NullPointObject [initialization of empty object list by suggestion list]. Refer to "Appendix 1" for details
    List<com.bumptech.glide.module.GlideModule> manifestModules = Collections.emptyList();

//
    if (annotationGeneratedModule == null || annotationGeneratedModule.isManifestParsingEnabled()) {
      manifestModules = new ManifestParser(applicationContext).parse();
    }
//Glide Generated API can be extended in Application and Library. Extensions use annotated static methods to add new options, modify existing options, and even add additional type support
    if (annotationGeneratedModule != null
        && !annotationGeneratedModule.getExcludedModuleClasses().isEmpty()) {
      Set<Class<?>> excludedModuleClasses = annotationGeneratedModule.getExcludedModuleClasses();
      Iterator<com.bumptech.glide.module.GlideModule> iterator = manifestModules.iterator();
      while (iterator.hasNext()) {
        com.bumptech.glide.module.GlideModule current = iterator.next();
        if (!excludedModuleClasses.contains(current.getClass())) {
          continue;
        }
        if (Log.isLoggable(TAG, Log.DEBUG)) {
          Log.d(TAG, "AppGlideModule excludes manifest GlideModule: " + current);
        }
        iterator.remove();
      }
    }

    if (Log.isLoggable(TAG, Log.DEBUG)) {
      for (com.bumptech.glide.module.GlideModule glideModule : manifestModules) {
        Log.d(TAG, "Discovered GlideModule from manifest: " + glideModule.getClass());
      }
    }
//The static method annotated by @ GlideType is used to extend RequestManager. Methods annotated by @ GlideType allow you to add support for new resource types, including specifying default options.
    RequestManagerRetriever.RequestManagerFactory factory =
        annotationGeneratedModule != null
            ? annotationGeneratedModule.getRequestManagerFactory()
            : null;
    builder.setRequestManagerFactory(factory);
    //The static method annotated with @ GlideOption is used to extend RequestOptions. GlideOption can:

Define a Application A collection of frequently used options in a module.
Create new options, usually with Glide of Option Class.
    for (com.bumptech.glide.module.GlideModule module : manifestModules) {
      module.applyOptions(applicationContext, builder);
    }
    if (annotationGeneratedModule != null) {
      annotationGeneratedModule.applyOptions(applicationContext, builder);
    }
//Build object
    Glide glide = builder.build(applicationContext);
    //Register custom manifestModules
    for (com.bumptech.glide.module.GlideModule module : manifestModules) {
      try {
        module.registerComponents(applicationContext, glide, glide.registry);
      } catch (AbstractMethodError e) {
        throw new IllegalStateException(
            "Attempting to register a Glide v3 module. If you see this, you or one of your"
                + " dependencies may be including Glide v3 even though you're using Glide v4."
                + " You'll need to find and remove (or update) the offending dependency."
                + " The v3 module name is: "
                + module.getClass().getName(),
            e);
      }
    }
    //Register the default Module
    if (annotationGeneratedModule != null) {
      annotationGeneratedModule.registerComponents(applicationContext, glide, glide.registry);
    }
    //Registering Callbacks 
    applicationContext.registerComponentCallbacks(glide);
    Glide.glide = glide;
  }

Um! The creation of this singleton object is complicated. Its functions from top to bottom are as follows:

  1. The integration library can extend custom options for the Generated API.
  2. In the Application module, common option groups can be packaged into one option for use in the Generated API
  3. GlideOption - adds a custom option for RequestOptions.
  4. GlideType - adds support for new resource types (GIF, SVG, etc.).

Annotationgenerated module validation judgment and application settings

if (annotationGeneratedModule == null || annotationGeneratedModule.isManifestParsingEnabled()) {
      manifestModules = new ManifestParser(applicationContext).parse();
    }

List parsing

To simplify the migration process, although the manifest resolution and the old GlideModule interface have been deprecated, they are still supported in the v4 version. AppGlideModule, LibraryGlideModule and obsolete GlideModule can coexist in one application.

However, in order to avoid checking the performance ceiling (and related bugs) of metadata, you can disable list parsing after migration and copy a method in your AppGlideModule:

@GlideModule
public class GiphyGlideModule extends AppGlideModule {
  @Override
  public boolean isManifestParsingEnabled() {
    return false;
  }

  ...
}

Listing parsing is not the focus of this singleton mode parsing. Therefore, the ManifestParser description will not be expanded for the time being

The construction process of Glide specific parameters is as follows

com.bumptech.glide.GlideBuilder

private final Map<Class<?>, TransitionOptions<?, ?>> defaultTransitionOptions = new ArrayMap<>();
  private final GlideExperiments.Builder glideExperimentsBuilder = new GlideExperiments.Builder();
  private Engine engine;
  private BitmapPool bitmapPool;
  private ArrayPool arrayPool;
  private MemoryCache memoryCache;
  private GlideExecutor sourceExecutor;
  private GlideExecutor diskCacheExecutor;
  private DiskCache.Factory diskCacheFactory;
  private MemorySizeCalculator memorySizeCalculator;
  private ConnectivityMonitorFactory connectivityMonitorFactory;
  private int logLevel = Log.INFO;
  private RequestOptionsFactory defaultRequestOptionsFactory =
      new RequestOptionsFactory() {
        @NonNull
        @Override
        public RequestOptions build() {
          return new RequestOptions();
        }
      };
  @Nullable private RequestManagerFactory requestManagerFactory;
  private GlideExecutor animationExecutor;
  private boolean isActiveResourceRetentionAllowed;
  @Nullable private List<RequestListener<Object>> defaultRequestListeners;

  ......
  
@NonNull
  Glide build(@NonNull Context context) {
    if (sourceExecutor == null) {
      sourceExecutor = GlideExecutor.newSourceExecutor();
    }

    if (diskCacheExecutor == null) {
      diskCacheExecutor = GlideExecutor.newDiskCacheExecutor();
    }

    if (animationExecutor == null) {
      animationExecutor = GlideExecutor.newAnimationExecutor();
    }

    if (memorySizeCalculator == null) {
      memorySizeCalculator = new MemorySizeCalculator.Builder(context).build();
    }

    if (connectivityMonitorFactory == null) {
      connectivityMonitorFactory = new DefaultConnectivityMonitorFactory();
    }

    if (bitmapPool == null) {
      int size = memorySizeCalculator.getBitmapPoolSize();
      if (size > 0) {
        bitmapPool = new LruBitmapPool(size);
      } else {
        bitmapPool = new BitmapPoolAdapter();
      }
    }

    if (arrayPool == null) {
      arrayPool = new LruArrayPool(memorySizeCalculator.getArrayPoolSizeInBytes());
    }

    if (memoryCache == null) {
      memoryCache = new LruResourceCache(memorySizeCalculator.getMemoryCacheSize());
    }

    if (diskCacheFactory == null) {
      diskCacheFactory = new InternalCacheDiskCacheFactory(context);
    }

    if (engine == null) {
      engine =
          new Engine(
              memoryCache,
              diskCacheFactory,
              diskCacheExecutor,
              sourceExecutor,
              GlideExecutor.newUnlimitedSourceExecutor(),
              animationExecutor,
              isActiveResourceRetentionAllowed);
    }

    if (defaultRequestListeners == null) {
      defaultRequestListeners = Collections.emptyList();
    } else {
      defaultRequestListeners = Collections.unmodifiableList(defaultRequestListeners);
    }

    GlideExperiments experiments = glideExperimentsBuilder.build();
    RequestManagerRetriever requestManagerRetriever =
        new RequestManagerRetriever(requestManagerFactory, experiments);

    return new Glide(
        context,
        engine,
        memoryCache,
        bitmapPool,
        arrayPool,
        requestManagerRetriever,
        connectivityMonitorFactory,
        logLevel,
        defaultRequestOptionsFactory,
        defaultTransitionOptions,
        defaultRequestListeners,
        experiments);
  }

There are many construction parameters, but most of them are known by name. It's not too difficult to understand.
Here, the whole singleton mode usage process is resolved!!!

summary

Single case aspect

The use process of Glide singleton has many references. The author's ability is limited. If you have unclear and unclear expression, please leave a message for discussion. Welcome to correct it!
The design idea is as follows:
Single instance build environment check:
There are many ways to check the environment

  1. context check preconditions checkNotNull. checkNotNull(@Nullable T arg, @NonNull
    String message)
  2. First check if in singleton mode (glide = = null)
  3. Check annotationGeneratedModule customization
  4. Single case second examination synchronized (Glide.class){
  5. Identifier checking isInitializing
  6. excludedModuleClasses extension load
  7. RequestManagerFactory loading, etc

In many cases, the deeper and more careful the inspection of the use environment, the normal use of the function can be guaranteed. Glide's insight into the environment is worth pondering and learning and practicing for SDK developers.

For scenarios that often use singleton mode, I suggest that we also learn from Glide's upward and downward compatibility after years of Android version update. In the source code, you can clearly see that the use structure of singleton mode has also jumped many times.

Through this analysis, we can realize the external inclusiveness of glide and make deep customization. I believe that the children's shoes developed have been used or designed SDK integration package by myself. Glide's simple use is worth learning. The design and opening of functional modules can also promote decoupling.

Empty list collections emptyList()

This is very practical for Android developers. It is often used in listview, RecyclerView and other lists. If collections. Is used during initialization emptyList(). The null pointing exception is avoided
size(), isEmpty(), contains(), containsAll() and other methods can avoid tedious null judgment.

Null object extension (null object mode, also a kind of design mode)

If the empty judgment operation on a class or object is frequent, I suggest that you can consider extending the empty object if the empty judgment operation is more than 3 times. Of course, it is also combined with the actual situation. For example, there are requirements for the number of classes (some requirements in the integration process of the project are always caught off guard and unreasonable).

If the use scene of empty object mode is enriched later, the author will write a new article and dig a hole here first.

Appendix 1:

Collections.emptyList()
java.util.Collections

The specific process is as follows:

public static final Set EMPTY_SET = new Collections.EmptySet();
    public static final List EMPTY_LIST = new Collections.EmptyList();
    public static final Map EMPTY_MAP = new Collections.EmptyMap();


public static final <T> List<T> emptyList() {
        return EMPTY_LIST;
    }

private static class EmptyList<E> extends AbstractList<E> implements RandomAccess, Serializable {
        private static final long serialVersionUID = 8842843931221139166L;

        private EmptyList() {
        }

        public Iterator<E> iterator() {
            return Collections.emptyIterator();
        }

        public ListIterator<E> listIterator() {
            return Collections.emptyListIterator();
        }

        public int size() {
            return 0;
        }

        public boolean isEmpty() {
            return true;
        }

        public boolean contains(Object var1) {
            return false;
        }

        public boolean containsAll(Collection<?> var1) {
            return var1.isEmpty();
        }

        public Object[] toArray() {
            return new Object[0];
        }

        public <T> T[] toArray(T[] var1) {
            if (var1.length > 0) {
                var1[0] = null;
            }

            return var1;
        }

        public E get(int var1) {
            throw new IndexOutOfBoundsException("Index: " + var1);
        }

        public boolean equals(Object var1) {
            return var1 instanceof List && ((List)var1).isEmpty();
        }

        public int hashCode() {
            return 1;
        }

        public boolean removeIf(Predicate<? super E> var1) {
            Objects.requireNonNull(var1);
            return false;
        }

        public void replaceAll(UnaryOperator<E> var1) {
            Objects.requireNonNull(var1);
        }

        public void sort(Comparator<? super E> var1) {
        }

        public void forEach(Consumer<? super E> var1) {
            Objects.requireNonNull(var1);
        }

        public Spliterator<E> spliterator() {
            return Spliterators.emptySpliterator();
        }

        private Object readResolve() {
            return Collections.EMPTY_LIST;
        }
    }

Keywords: Android Design Pattern Singleton pattern

Added by mwkdesign on Fri, 17 Dec 2021 13:35:46 +0200