Mainstream open source framework source code in-depth understanding of the third – Glide source analysis. (Source version 4.9.0)

preface

This paper analyzes from the following directions:

  1. Glide basic use
  2. Glide complete loading process
  3. Glide Target, Transformation

The basic usage of Glide

  1. Add Gradle dependencies:

    API ‘com. Making. Bumptech. Glide: glide: 4.9.0’ / / glide the default load network image using the HttpURLconnection if / / replace OkHttp were added ‘com. Making. Bumptech. Glide: okhttp3 – integration: 4.9.0’ API ‘com. Making. Bumptech. Glide: okhttp3 – integration: 4.9.0’ AnnotationProcessor ‘com. Making. Bumptech. Glide: the compiler: 4.9.0’

  2. The use of the Glide

// Basic use: Glide. With (context).load(iconUrl).apply(getOptions()).into(imageView); // Set the private RequestOptions optiongetOptions() {
        returnnew RequestOptions() .dontAnimate() .placeholder(R.drawable.ic_no_picture) .diskCacheStrategy(DiskCacheStrategy.RESOURCE) .error(R.drawable.ic_no_picture); } / /"Note: There are two ways to implement a custom GlideModule:"1. Add <meta-data // custom GlideModule path under androidmanifest.xml Application tag android:name="com.android.baselibrary.manager.glide.MyAppGlideModule"
            android:value="GlideModule"/> // GlideModule 2. @glidemodule // @glidemodule // @glidemodule public Class MyAppGlideModule extends AppGlideModule { @Override public booleanisManifestParsingEnabled() {
        return false; } @override public void applyOptions(Context Context, int int, int int, int int, int int) GlideBuilder builder) {PREFER_RGB_565 (PREFER_ARGB_8888); builder.setDefaultRequestOptions( new RequestOptions() .format(DecodeFormat.PREFER_RGB_565) ); // The following three Settings can be customized size, And completely custom implementation / / memory buffer MemorySizeCalculator calculator = new MemorySizeCalculator. Builder (context). SetMemoryCacheScreens (2) .setBitmapPoolScreens(3) .build(); builder.setMemoryCache(new LruResourceCache(calculator.getMemoryCacheSize())); / / Bitmap pool builder. SetBitmapPool (new LruBitmapPool (calculator. GetBitmapPoolSize ())); Int diskCacheSizeBytes = 1024 * 1024 * 100; //100 MB builder.setDiskCache(new InternalCacheDiskCacheFactory(context, diskCacheSizeBytes)); } @Override public void registerComponents(Context context, Glide glide, Registry registry) { // registry.replace(GlideUrl.class, InputStream.class, new NetworkDisablingLoader.Factory()); }}Copy the code

Two, Glide complete loading process

  1. Glide.with(context)
  2. Load (T) (i.e., requestManager.load (T))
  3. Apply (options) (i.e. Requestmanager.apply (options))
  4. Into (T) (i.e. : RequestBuilder. Into (T))
  5. Engine. Load
  6. Data source processing and display

Let’s start with a sequence diagram:

1. Glide.with(context)

  • With definition: a static method in Glide class with multiple overloaded methods

  • The role with:

    1. Get the RequestManager object
    2. Bind the lifecycle of Glide image loading to the lifecycle of the Activity/Fragment based on the parameters passed to the with() method to automatically execute the request and pause the operation
  • There are several important classes involved in this section:

    1. RequestManager: glide RequestManager, binding Activity/Fragment lifecycle (load, pause, resume, clear the request operation), implement the LifecycleListener interface
    2. RequestManagerRetriever: create RequestManager and RequestManager and custom fragments (for example: SupportRequestManagerFragment) binding, back to life cycle management
    3. SupportRequestManagerFragment/RequestManagerFragment: glide, according to the incoming parameters, create added to the current fragments in the Activity, has reached the life cycle of listening.
  • Source:

public class Glide implements ComponentCallbacks2 {
    
    ......
    
    @NonNull
    public static RequestManager with(@NonNull Context context) {
        return getRetriever(context).get(context);
    }

    @NonNull
    public static RequestManager with(@NonNull Activity activity) {
        return getRetriever(activity).get(activity);
    }

    @NonNull
    public static RequestManager with(@NonNull FragmentActivity activity) {
        return getRetriever(activity).get(activity);
    }

    @NonNull
    public static RequestManager with(@NonNull Fragment fragment) {
        returngetRetriever(fragment.getActivity()).get(fragment); } @nonnull public static RequestManager with(@nonnull android.app.fragment Fragment) {return getRetriever(fragment.getActivity()).get(fragment);
    }

    @NonNull
    public static RequestManager with(@NonNull View view) {
        returngetRetriever(view.getContext()).get(view); }... }Copy the code

GetRetriever (view.getContext()).get(view). GetRetriever (view.getContext()).get(view)

/ / Glide class: @nonNULL Private static RequestManagerRetriever getRetriever(@Nullable Context Context) {// Context cannot be empty 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)."); // Build glide and get the RequestManagerRetriever objectreturn Glide.get(context).getRequestManagerRetriever();
    }
Copy the code

This method first determines whether the context is empty, which throws an exception. Then continue to call Glide. Get (context) getRequestManagerRetriever (), we still see first Glide. Get (context) :

// Glide class: @nonnull public static Glide get(@nonnull Context Context)if (glide == null) {
            synchronized (Glide.class) {
                if(glide == null) { checkAndInitializeGlide(context); }}}returnglide; } private static void checkAndInitializeGlide(@nonnull Context Context)if (isInitializing) {
            throw new IllegalStateException("You cannot call Glide.get() in registerComponents(),"
                    + " use the provided Glide instance instead"); } // initializing isInitializing =true; // initializeGlide initializeGlide(context); // Initialization complete isInitializing =false; } private static void initializeGlide(@nonnull Context Context) {// Initialize the GlideBuilder. new GlideBuilder()); } @SuppressWarnings("deprecation") private static void initializeGlide(@NonNull Context context, @ NonNull GlideBuilder builder) {/ / access application global Context Context applicationContext. = the Context getApplicationContext (); // Get the custom GlideModule generated by the annotations (i.e. The basic usage of Glide 】 【 custom GlideModule method 2) GeneratedAppGlideModule annotationGeneratedModule = getAnnotationGeneratedGlideModules(); List<com.bumptech.glide.module.GlideModule> manifestModules = Collections.emptyList(); // Make the GlideModule null; Or if it is not null, its function isManifestParsingEnabled() returnstrueAs a general rule, the isManifestParsingEnabled method should be returned when you customize the GlideModule using annotationsfalseTo prevent continuing to load declarations in AndroidManifest.xml and improve performance)if(annotationGeneratedModule == null || annotationGeneratedModule.isManifestParsingEnabled()) { // Get the custom GlideModule declared in the application androidManifest.xml (i.e. ManifestModules = New ManifestParser(applicationContext).parse(); manifestModules = New ManifestParser(applicationContext).parse(); } // Remove the same GlideModule from the manifest file if the Module generated by the annotations is compared to the GlideModule obtained from the manifest fileif(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); } // Remove the same GlideModule iterator.remove(); }} // Print the valid GlideModule obtained from the manifest fileif (Log.isLoggable(TAG, Log.DEBUG)) {
            for (com.bumptech.glide.module.GlideModule glideModule : manifestModules) {
                Log.d(TAG, "Discovered GlideModule from manifest: "+ glideModule.getClass()); } // Obtain the RequestManagerFactory from the custom GlideModule. If the custom GlideModule is empty, It returns null RequestManagerRetriever. RequestManagerFactory factory = annotationGeneratedModule! = null ? annotationGeneratedModule.getRequestManagerFactory() : null; / / set the factory builder. SetRequestManagerFactory (factory); // Iterate over the valid GlideModule from all the manifest files and add all the properties set in the GlideModule to the Builderfor (com.bumptech.glide.module.GlideModule module : manifestModules) {
            module.applyOptions(applicationContext, builder);
        }
        if(annotationGeneratedModule ! = null) {// annotate the custom GlideModule properties created in this way, Added to the builder annotationGeneratedModule. ApplyOptions (applicationContext, builder); = build.build (applicationContext); /** * Call the custom GlideModule's registerComponents and pass in the current Glide instance to get the user to register his/her component. * The default component is registered during Glide instantiation. If the user defines the same component, So it's going to replace the previous one. * * The purpose of registering the component is to tell Glide what to do when we call the load(XXXX) method to get the resource that XXXX points to. Therefore, we can see that the first parameter of register is the type of our load(XXXX), the second parameter is the corresponding input stream, and the third parameter defines how to get the resource. * * /for(com.bumptech.glide.module.GlideModule module : manifestModules) { module.registerComponents(applicationContext, glide, glide.registry); } / / same as aboveif(annotationGeneratedModule ! = null) { annotationGeneratedModule.registerComponents(applicationContext, glide, glide.registry); } /** * register a component callback to the Application to check for Config changes and low memory usage * is a fine-grained memory reclamation management callback. }}}}}}}}}}}}}}}}}}}}}}}}}}} * Response to onTrimMemory callbacks: the developer's app will benefit directly, it will benefit the user experience, and the system is more likely to keep the app alive for longer. * Does not respond to onTrimMemory callbacks: the system is more likelykill* ComponentCallback (ComponentCallbacks2); * The system calls back onConfigurationChanged() if the device configuration information changes while the component is running. * onLowMemory () is called when the entire system is out of memory. The running process should adjust the memory usage. * * */ applicationContext.registerComponentCallbacks(glide); Glide.glide = glide; } @Nullable @SuppressWarnings({"unchecked"."deprecation"."TryWithIdenticalCatches"})
    private static GeneratedAppGlideModule getAnnotationGeneratedGlideModules() { GeneratedAppGlideModule result = null; Try {/ / get GeneratedAppGlideModuleImpl object reflection, / / such to implement custom GlideModule annotation way, Class<GeneratedAppGlideModule> clAZz = (Class<GeneratedAppGlideModule>) Class<GeneratedAppGlideModule> Class.forName("com.bumptech.glide.GeneratedAppGlideModuleImpl");
            result = clazz.getDeclaredConstructor().newInstance();
        } catch (ClassNotFoundException e) {
            ...
        }
        return result;
    }
Copy the code

To summarize the source code for this section:

  1. The singleton pattern gets glide instances
  2. If glide is empty, the glide is initialized, and the GlideBuilder object is initialized
  3. Gets the custom GlideModule
  4. If a custom GlideModule exists, add a custom configuration about Glide to the GlideBuilder object
  5. Build glide instance
  6. Registers a component callback to the Application to detect system Config changes and low memory usage signals

GlideBuilder build

Let’s take a look at Glide’s builer.build (applicationContext) :

Public final class GlideBuilder {/** * public final class GlideBuilder {/** * private final Map< class <? >, TransitionOptions<? ,? >> defaultTransitionOptions = new ArrayMap<>(); // Image loading Engine, which is responsible for starting (sending network requests for images) and managing cache resource activities; // Private BitmapPool BitmapPool is automatically cleared when memory is insufficient; Private ArrayPool ArrayPool; private ArrayPool ArrayPool; // Private MemoryCache MemoryCache is automatically cleared when memory is insufficient; // The thread pool is used to find the memory cache. The maximum number of threads is 4, depending on the CPU private GlideExecutorsourceExecutor; // Thread pool to find the local disk cache. The maximum number of threads is 4, depending on the CPU private GlideExecutor diskCacheExecutor; /** * Factory used to create the local DiskCache object * the default local DiskCache size is 250M */ private diskcache.factory diskCacheFactory; // A memory calculator, which calculates the best size of the memory cache for the current situation by getting the phone hardware constants and the phone screen density, width, and length; / / used in the production of factory private network status monitoring events ConnectivityMonitorFactory ConnectivityMonitorFactory; // Set GlidelogPrint grade Private intlogLevel = Log.INFO; Private RequestOptions defaultRequestOptions = new RequestOptions(); Nullable private RequestManagerFactory RequestManagerFactory; The default number of threads is 1 or 2. The maximum number depends on the number of cores in the CPU. /** * Whether to recycle the reserved image resource data. Default isfalse* Note that, set totrueWill lead to larger memory consumption, increase the risk of crash * / private Boolean isActiveResourceRetentionAllowed; @Nullable private List<RequestListener<Object>> defaultRequestListeners; private boolean isLoggingRequestOriginsEnabled; . @ NonNull Glide build (@ NonNull Context Context) {/ / -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- to initialize the member attribute start -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --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(),
                            GlideExecutor.newAnimationExecutor(),
                            isActiveResourceRetentionAllowed);
        }

        if (defaultRequestListeners == null) {
            defaultRequestListeners = Collections.emptyList();
        } else{ defaultRequestListeners = Collections.unmodifiableList(defaultRequestListeners); } / / -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- to initialize the member attributes end -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- / / initialization requestManagerRetriever requestManagerRetriever object requestManagerRetriever = new RequestManagerRetriever(requestManagerFactory); / / build Glidereturn new Glide(
                context,
                engine,
                memoryCache,
                bitmapPool,
                arrayPool,
                requestManagerRetriever,
                connectivityMonitorFactory,
                logLevel, defaultRequestOptions.lock(), defaultTransitionOptions, defaultRequestListeners, isLoggingRequestOriginsEnabled); } // RequestManagerRetriever class: Public RequestManagerRetriever(@nullable RequestManagerFactory Factory) {// If factory is null, Set the default factory this.factory = factory! = null ? factory : DEFAULT_FACTORY; handler = new Handler(Looper.getMainLooper(), this /* Callback */); } private static final RequestManagerFactory DEFAULT_FACTORY = newRequestManagerFactory() { @NonNull @Override public RequestManager build(@NonNull Glide glide, @NonNull Lifecycle lifecycle, @nonnull RequestManagerTreeNode RequestManagerTreeNode, @nonnull Context Context) {// Initialize RequestManagerreturnnew RequestManager(glide, lifecycle, requestManagerTreeNode, context); }};Copy the code

It is clear that the Glide build process initializes many properties:

  • SourceExecutor: Get image request thread pool GlideExecutor
  • DiskCacheExecutor: Loads the image thread pool GlideExecutor from the disk cache
  • AnimationExecutor: GlideExecutor, the thread pool that executes the animation
  • MemorySizeCalculator: Memory calculator
  • MemoryCache: specifies the memoryCache policy, LruResourceCache
  • DiskCacheFactory: diskCacheFactory
  • Engine: image loading engine
  • .
  • RequestManagerRetriever: create RequestManager and RequestManager and custom fragments (for example: SupportRequestManagerFragment) binding, back to life cycle management. And if the Factory passed in when the requestManagerRetriever object is initialized is empty, the RequestManager is built using the default DEFAULT_FACTORY.

Glide construction method

Let’s take a look at what is done in Glide construction:

/ / Glide class:  Glide( @NonNull Context context, @NonNull Engine engine, @NonNull MemoryCache memoryCache, @NonNull BitmapPool bitmapPool, @NonNull ArrayPool arrayPool, @NonNull RequestManagerRetriever requestManagerRetriever, @NonNull ConnectivityMonitorFactory connectivityMonitorFactory, intlogLevel, @NonNull RequestOptions defaultRequestOptions, @NonNull Map<Class<? >, TransitionOptions<? ,? >> defaultTransitionOptions, @NonNull List<RequestListener<Object>> defaultRequestListeners, Boolean isLoggingRequestOriginsEnabled) {/ / thread pool from the Builder, buffer pool, hold this. Engine = engine; this.bitmapPool = bitmapPool; this.arrayPool = arrayPool; this.memoryCache = memoryCache; this.requestManagerRetriever = requestManagerRetriever; this.connectivityMonitorFactory = connectivityMonitorFactory; / / decoding object format DecodeFormat DecodeFormat = defaultRequestOptions. GetOptions () get (Downsampler. DECODE_FORMAT); bitmapPreFiller = new BitmapPreFiller(memoryCache, bitmapPool, decodeFormat); final Resources resources = context.getResources(); registry = new Registry(); registry.register(new DefaultImageHeaderParser()); // Now we only use this parser for HEIF images, which are only supported on OMR1 +. // If we need this for other file types, we should consider removing this restriction.if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) { registry.register(new ExifInterfaceImageHeaderParser()); } List<ImageHeaderParser> imageHeaderParsers = registry.getImageHeaderParsers(); Downsampler downsampler = new Downsampler( imageHeaderParsers, resources.getDisplayMetrics(), bitmapPool, arrayPool); // Decoder class: decodes InputStream into GIF ByteBufferGifDecoder byteBufferGifDecoder =
                new ByteBufferGifDecoder(context, imageHeaderParsers, bitmapPool, arrayPool); // Decoder class: Decodes Video to bitmap ResourceDecoder<ParcelFileDescriptor, Bitmap> parcelFileDescriptorVideoDecoder = VideoDecoder.parcel(bitmapPool); // Decoder class: decodes ByteBuffer into bitmap ByteBufferBitmapDecoder ByteBufferBitmapDecoder = new ByteBufferBitmapDecoder(downsampler); StreamBitmapDecoder = new StreamBitmapDecoder(downsampler, arrayPool); Drawable ResourceDrawableDecoder ResourceDrawableDecoder = New ResourceDrawableDecoder(context); / / decoding: convert resource files into an InputStream ResourceLoader. StreamFactory resourceLoaderStreamFactory = new ResourceLoader.StreamFactory(resources); / / convert resource files into URI ResourceLoader UriFactory resourceLoaderUriFactory = new ResourceLoader. UriFactory (resources); / / will res in the resource file is converted into ParcelFileDescriptor ResourceLoader. FileDescriptorFactory resourceLoaderFileDescriptorFactory = new ResourceLoader.FileDescriptorFactory(resources); / / convert resource files in the Asset into ParcelFileDescriptor ResourceLoader. AssetFileDescriptorFactory resourceLoaderAssetFileDescriptorFactory  = new ResourceLoader.AssetFileDescriptorFactory(resources); BitmapEncoder = new BitmapEncoder(arrayPool); Btye [] BitmapBytesTranscoder BitmapBytesTranscoder = new BitmapBytesTranscoder(); // GifDrawable 通 btye[]类 GifDrawableBytesTranscoder gifDrawableBytesTranscoder = new GifDrawableBytesTranscoder(); ContentResolver contentResolver = context.getContentResolver(); // Use Registry to register Encoder and Decoder for Glide. new ByteBufferEncoder()) .append(InputStream.class, New StreamEncoder(arrayPool)) new StreamEncoder(arrayPool)) new StreamEncoder(arrayPool)) byteBufferBitmapDecoder) .append(Registry.BUCKET_BITMAP, InputStream.class, Bitmap.class, streamBitmapDecoder) .append( Registry.BUCKET_BITMAP, ParcelFileDescriptor.class, Bitmap.class, parcelFileDescriptorVideoDecoder) .append( Registry.BUCKET_BITMAP, AssetFileDescriptor.class, Bitmap.class, VideoDecoder.asset(bitmapPool)) .append(Bitmap.class, Bitmap.class, UnitModelLoader.Factory.<Bitmap>getInstance()) .append( Registry.BUCKET_BITMAP, Bitmap.class, Bitmap.class, new UnitBitmapDecoder()) .append(Bitmap.class, Append (Registry.BUCKET_BITMAP_DRAWABLE, byteBuffer.class, bitmapEncoder) BitmapDrawable.class, new BitmapDrawableDecoder<>(resources, byteBufferBitmapDecoder)) .append( Registry.BUCKET_BITMAP_DRAWABLE, InputStream.class, BitmapDrawable.class, new BitmapDrawableDecoder<>(resources, streamBitmapDecoder)) .append( Registry.BUCKET_BITMAP_DRAWABLE, ParcelFileDescriptor.class, BitmapDrawable.class, new BitmapDrawableDecoder<>(resources, parcelFileDescriptorVideoDecoder)) .append(BitmapDrawable.class, new BitmapDrawableEncoder(bitmapPool, Append (Registry.BUCKET_GIF, inputStream.class, GifDrawable.class,
                        new StreamGifDecoder(imageHeaderParsers, byteBufferGifDecoder, arrayPool))
                .append(Registry.BUCKET_GIF, ByteBuffer.class, GifDrawable.class,
                        byteBufferGifDecoder)
                .append(GifDrawable.class, new GifDrawableEncoder())
                /* GIF Frames */
                // Compilation with Gradle requires the type to be specified forUnitModelLoader // adds to decode GIFs into bitmap.append (GifDecoder.class, GifDecoder.class,
                        UnitModelLoader.Factory.<GifDecoder>getInstance())
                .append(
                        Registry.BUCKET_BITMAP,
                        GifDecoder.class,
                        Bitmap.class,
                        new GifAppend (uri.class, Drawable. Class, Drawable. Class, Drawable. resourceDrawableDecoder) .append( Uri.class, Bitmap.class, new ResourceBitmapDecoder(resourceDrawableDecoder, BitmapPool)) / / add File processing class. Register (new ByteBufferRewinder. Factory ()), append (File. Class, ByteBuffer class, new ByteBufferFileLoader.Factory()) .append(File.class, InputStream.class, new FileLoader.StreamFactory()) .append(File.class, File.class, new FileDecoder()) .append(File.class, ParcelFileDescriptor.class, new FileLoader.FileDescriptorFactory()) // Compilation with Gradle requires thetype to be specified forUnitModelLoader // here. .append(File.class, File.class, Unitmodelloader.factory.<File>getInstance())) // Add a conversion class (convert any complex data model into a specific data type, and then process it through the DataFetcher to get the corresponding available resources).register(new) InputStreamRewinder. Factory (arrayPool)) / / by resource files into an InputStream. Append (int. Class, InputStream. Class, ResourceLoaderStreamFactory) / / through resource files into ParcelFileDescriptor. Append (int. Class, ParcelFileDescriptor. Class, ResourceLoaderFileDescriptorFactory) / / through resource files into uris. Append (Integer. A class, an InputStream. Class, resourceLoaderStreamFactory) .append( Integer.class, ParcelFileDescriptor.class, resourceLoaderFileDescriptorFactory) .append(Integer.class, Uri.class, resourceLoaderUriFactory) .append( int.class, AssetFileDescriptor.class, resourceLoaderAssetFileDescriptorFactory) .append( Integer.class, AssetFileDescriptor.class, resourceLoaderAssetFileDescriptorFactory) .append(int.class, Uri.class, Inputstream.append (String. Class, inputStream.class, inputStream.class, inputStream.class, New DataUrlLoader. StreamFactory < String > ()) / / by Uri into InputStream. Append (Uri) class, InputStream. Class, New DataUrlLoader. StreamFactory > < Uri ()) / / through the String into an InputStream. Append (String class, InputStream. Class, New StringLoader. StreamFactory ()) / / by String into ParcelFileDescriptor append (String class, ParcelFileDescriptor. Class, New StringLoader. FileDescriptorFactory ()) / / by String into AssetFileDescriptor append (String class, AssetFileDescriptor. Class, new StringLoader. AssetFileDescriptorFactory ()) / / Uri through the network into an InputStream. Append (Uri) class, Inputstream.class, new Httpuriloader.factory ()) New AssetUriLoader. StreamFactory (context. GetAssets ())) / / by Uri into ParcelFileDescriptor. Append (Uri) class, ParcelFileDescriptor.class, New AssetUriLoader. FileDescriptorFactory (context. GetAssets ())) / / by image Uri into InputStream. Append (Uri) class, InputStream. Class, new MediaStoreImageThumbLoader. Factory (context)) / / by video Uri into InputStream. Append (Uri) class, InputStream. Class, new MediaStoreVideoThumbLoader. Factory (context)) / / by Uri into InputStream. Append (Uri) class, Inputstream.class, new Uriloader.streamFactory (contentResolver)) // Convert parcelFileDescription.append (URI.class, ParcelFileDescriptor.class, New UriLoader. FileDescriptorFactory (contentResolver)) / / by Uri into AssetFileDescriptor append (Uri) class, AssetFileDescriptor.class, New UriLoader. AssetFileDescriptorFactory (contentResolver)) / / via HTTP/HTTPS Uris, into an InputStream. Append (Uri) class, InputStream. Class, new UrlUriLoader. StreamFactory ()) / / by java.net.URL into InputStream. Append (URL) class, Inputstream.class, new urLLoader.streamFactory ()) // Convert a multimedia File uri to a file.append (URI.class, file.class, New MediaStoreFileLoader. Factory (context)) / / via HTTP/HTTPS url into InputStream. Append (GlideUrl. Class, InputStream. Class, New HttpGlideUrlLoader. Factory ()) / / through the array into a ByteBuffer. Append (byte [] class, ByteBuffer. Class, New ByteArrayLoader. ByteBufferFactory ()) / / by array into InputStream. Append (byte [] class, InputStream. Class, new ByteArrayLoader.StreamFactory()) .append(Uri.class, Uri.class, UnitModelLoader.Factory.<Uri>getInstance()) .append(Drawable.class, Drawable.class, UnitModelLoader.Factory.<Drawable>getInstance()) .append(Drawable.class, Drawable.class, New UnitDrawableDecoder()) // Register bitmap to bitMapDrawable.register (bitmap.class, bitMapDrawable.class, bitmap.class, New BitmapDrawableTranscoder(resources)) // Bitmap [].register(bitmap.class, byte[].class, Drawable []. Register (Drawable. Class, byte[].class, Drawable. new DrawableBytesTranscoder( bitmapPool, bitmapBytesTranscoder, gifDrawableBytesTranscoder)) // GifDrawable Transcode into byte[]. Register (GifDrawable.class, byte[].class, gifDrawableBytesTranscoder); // Initialize the ImageViewTargetFactory to produce the correct Target, ImageViewTargetFactory = new ImageViewTargetFactory(); // Create a Glide context glideContext = new glideContext (Context, arrayPool, Registry, imageViewTargetFactory, defaultRequestOptions, defaultTransitionOptions, defaultRequestListeners, engine, isLoggingRequestOriginsEnabled,logLevel);
    }
Copy the code

As you can see, Glide initialization is very complex, let’s summarize:

  1. Save data from GlideBuilder to member properties
  2. Build a Registry that registers a number of codecs
  3. Initializes the ImageViewTargetFactory to use as the correct production Target for later into (e.g. BitmapImageViewTarget, DrawableImageViewTarget, or throw exception)
  4. Initialize the glideContext to describe the context of its data resources

Let’s briefly introduce two classes:

  1. GlideContext: Global glide context that provides components for the glide process (registries, load engine, imageViewTargetFactory, etc.)

  2. DataFetcher(the parent of the last parameter of Registry. Append in Glide’s initialization) : : Fetcher means fetch, so this class can be called a data grabber.

    Function: Extract and decode data resources based on different data sources (local, network, Asset, etc.) and how they are read (Stream, ByteBuffer, etc.)

    The implementation class is as follows:

    • AssetPathFetcher: Loads Asset data
    • HttpUrlFetcher: Loads network data
    • LocalUriFetcher: loads local data
    • Other implementation classes…

Finally Glide initialization analysis over, we returned to Glide. Get (context). GetRequestManagerRetriever getRequestManagerRetriever in () () :

Glide class: @nonnull public RequestManagerRetrievergetRequestManagerRetriever() {
    return requestManagerRetriever;
  }
Copy the code

In fact getRequestManagerRetriever () is returned to us in the process of building a Glide (GlideBuilder. The build ()) initialization requestManagerRetriever. So we finally get back to the get(context) part of getRetriever(context).get(context) :

@nonnull public RequestManager get(@nonnull Context Context) {if (context == null) {
            throw new IllegalArgumentException("You cannot start a load on a null Context");
        } else if(Util.isOnMainThread() && ! (Context instanceof Application)) {// If you are on the main thread and the context is not of the Application type, then call the corresponding method by determining the true type of the contextif (context instanceof FragmentActivity) {
                return get((FragmentActivity) context);
            } else if (context instanceof Activity) {
                return get((Activity) context);
            } else if(Context Instanceof ContextWrapper) {// Get the underlying context and call this method recursivelyreturnget(((ContextWrapper) context).getBaseContext()); }} // Call this method if it is not on the main thread or if the context is of type ApplicationContextreturngetApplicationManager(context); } @nonnull public RequestManager get(@nonnull FragmentActivity activity) {// Background thread, The with parameter type is applicationContextif (Util.isOnBackgroundThread()) {
            return get(activity.getApplicationContext());
        } else {
            assertNotDestroyed(activity);
            FragmentManager fm = activity.getSupportFragmentManager();
            returnsupportFragmentGet( activity, fm, /*parentHint=*/ null, isActivityVisible(activity)); }} @nonnull public RequestManager get(@nonnull Fragment Fragment) { Preconditions.checkNotNull(fragment.getActivity(),"You cannot start a load on a fragment before it is attached or after it is " +"destroyed"); // Background thread, with parameter type applicationContextif (Util.isOnBackgroundThread()) {
            return get(fragment.getActivity().getApplicationContext());
        } else {
            FragmentManager fm = fragment.getChildFragmentManager();
            returnsupportFragmentGet(fragment.getActivity(), fm, fragment, fragment.isVisible()); } // Call this method with an Activity parameter @suppresswarnings ()"deprecation") @nonnull public RequestManager get(@nonnull Activity Activity) {// Background thread, with parameter type applicationContextif (Util.isOnBackgroundThread()) {
            return get(activity.getApplicationContext());
        } else {
            assertNotDestroyed(activity);
            android.app.FragmentManager fm = activity.getFragmentManager();
            returnfragmentGet( activity, fm, /*parentHint=*/ null, isActivityVisible(activity)); } // Call this method @suppresswarnings (with View type)"deprecation") @nonnull public RequestManager get(@nonnull View View) {// Background thread, with parameter type applicationContextif (Util.isOnBackgroundThread()) {
            return get(view.getContext().getApplicationContext());
        }

        Preconditions.checkNotNull(view);
        Preconditions.checkNotNull(view.getContext(),
                "Unable to obtain a request manager for a view without a Context"); Activity Activity = findActivity(view.getContext()); // The view may be located in another location, such as a service. If there is no activity, then the context is considered to be of type.applicationContextif (activity == null) {
            returnget(view.getContext().getApplicationContext()); } // Check whether there is a Fragment in the v4 package (via android.R.ICtent)if (activity instanceof FragmentActivity) {
            Fragment fragment = findSupportFragment(view, (FragmentActivity) activity);
            returnfragment ! = null ? get(fragment) : get(activity); } // Check whether there is a Fragment in the app(standard) package (by android.R.ICd.c ontent) android.app.Fragment Fragment = findFragment(view, activity);if (fragment == null) {
            returnget(activity); } // Treat it as if the with parameter type is Fragmentreturnget(fragment); } @nonnull private RequestManager getApplicationManager(@nonnull Context Context) {// If it is not on the main thread or the context is of type ApplicationContextif (applicationManager == null) {
            synchronized (this) {
                if(applicationManager == null) {// Build manager at the application level and pass ApplicationLifecycle // // The image will be loaded after the application starts, and the cache will not be cleared until the application ends. Center/fragments in the activity or even memory is not visible, such as glide will not clear data glide glide = glide. Get (context) getApplicationContext ()); applicationManager = factory.build( glide, new ApplicationLifecycle(), new EmptyRequestManagerTreeNode(), context.getApplicationContext()); }}}return applicationManager;
    }
Copy the code

The get(context) method has a number of overloads, which are handled differently depending on the parameters passed in, because glide’s periodic callback is meaningless if you’re not currently on the main thread or if the context is ApplicationContext. So without looking specifically at the getApplicationManager method, the remaining methods end up calling two methods:

fragmentGet(fragment.getActivity(), fm, fragment, fragment.isVisible())
supportFragmentGet(activity, fm, /*parentHint=*/ null, isActivityVisible(activity))
Copy the code

One method returns the standard Fragment in the app package, and the other returns the Fragment in the V4 package. The two methods are similar, so let’s select supportFragmentGet to see how it works.

/ / RequestManagerRetriever class:  @NonNull private RequestManager supportFragmentGet( @NonNull Context context, @NonNull FragmentManager fm, @Nullable Fragment parentHint, Boolean isParentVisible) {/ / initializes the custom SupportRequestManagerFragment SupportRequestManagerFragment current = getSupportRequestManagerFragment(fm, parentHint, isParentVisible); / / get into requestManager, for the first time for empty requestManager requestManager = current. GetRequestManager ();if(requestManager == null) { // TODO(b/27524013): Factor out this Glide.get() call. Glide glide = Glide.get(context); // Build requestManager, And the Fragment's lifecycle (ActivityFragmentLifecycle) was introduced into requestManager = factory. Build (glide, current getGlideLifecycle (), current.getRequestManagerTreeNode(), context); / / will build requestManager Settings to the current, is used to directly obtained except for the first time after the current. SetRequestManager (requestManager); }returnrequestManager; } @NonNull private SupportRequestManagerFragment getSupportRequestManagerFragment( @NonNull final FragmentManager fm, @nullable Fragment parentHint, Boolean isParentVisible) {// Check whether Fragment exists by Tag, To prevent repeated add fragments SupportRequestManagerFragment current = (SupportRequestManagerFragment) fm.findFragmentByTag(FRAGMENT_TAG);if(current = = null) {/ / from pendingRequestManagerFragments cache to get a current = pendingSupportRequestManagerFragments. Get (FM);if(current = = null) {/ / create SupportRequestManagerFragment current = new SupportRequestManagerFragment (); current.setParentFragmentHint(parentHint);if(isParentVisible) { current.getGlideLifecycle().onStart(); } / / add the fragments into pendingSupportRequestManagerFragments cache pendingSupportRequestManagerFragments put (FM, current); // Set the Fragment TAG and add it to the current activity fm.beginTransaction().add(current, FRAGMENT_TAG).commitAllowingStateloss (); // Send a message to clear the Fragment that has just been saved. Handler. ObtainMessage (ID_REMOVE_SUPPORT_FRAGMENT_MANAGER, FM).sendToTarget(); }}return current;
    }
    
    @Override
    public boolean handleMessage(Message msg) {
        boolean handled = true;
        Object removed = null;
        Object key = null;
        switch (msg.what) {
            caseID_REMOVE_SUPPORT_FRAGMENT_MANAGER: FragmentManager supportFm = (FragmentManager) msg.obj; key = supportFm; / / remove fragments removed = pendingSupportRequestManagerFragments. Remove (supportFm);break;
            default:
                handled = false;
                break;
        }
        
        returnhandled; } / / SupportRequestManagerFragment categories: publicSupportRequestManagerFragment() {initialization ActivityFragmentLifecycle this (new ActivityFragmentLifecycle ()); } @VisibleForTesting @SuppressLint("ValidFragment") public SupportRequestManagerFragment (@ NonNull ActivityFragmentLifecycle lifecycle) {/ / assign values to the member attribute in the lifecycle this.lifecycle = lifecycle; } @ NonNull / / will return to ActivityFragmentLifecycle ActivityFragmentLifecyclegetGlideLifecycle() {
        return lifecycle;
    }
Copy the code

Now that we’ve looked at the source code for the get(context) part of getRetriever(context).get(context), Mainly in the Activity in the page to add a RequestManagerFragment/SupportRequestManagerFragment instance, so that is used to monitor the Activity of life cycle, Then inject the Fragment with a RequestManager. And in order to prevent the same Activity repeatedly create fragments, glide not only use the Tag to Tag, but also use the pendingRequestManagerFragments cache, in order to make sure will not be repeated to create fragments, Once the Fragment is successfully generated, clear it from the map.

N = gowyr_ = gowyr_ = gowyr_ = gowyr_ = gowyr_ Now that we have seen all the source code for this part, let’s move on!!

2. Load (T) (i.e. : requestManager.load (T))

  • The load definition: Glide. With (T) returns the RequestManager object, so the next call is requestManager.load (T) to determine the source of the load(URL string, image local path, etc.). Since there are multiple sources, load has multiple overload methods.

  • Load: Pre-created objects that perform a series of operations (load, codec, transcode) on images and all encapsulated in RequestBuilder objects.

  • RequestBuilder: a generic class that handles setting options and startup loads for general resource types. Used to start the download and download resources into the specified image resource object, and then the image resource object device in the specified control, for example, the outer layer of a Drawable type, the download of the original resource into a Drawable image resource object.

    The following raw resource conversion types are currently available:

    • 1.Drawable(default type) and its subclasses
    • 2.Bitmap
    • 3.GifDrawable
    • 4.File(unknown type)
    • @param TransCodeType The type of image resource to parse into. Default is Drawable
  • The load source

RequestManager: @nonnull @checkResult @override public RequestBuilder<Drawable> load(@nullable Uri Uri) {return asDrawable().load(uri);
    }

    @NonNull
    @CheckResult
    @Override
    public RequestBuilder<Drawable> load(@Nullable File file) {
        return asDrawable().load(file);
    }

    @NonNull
    @CheckResult
    @Override
    public RequestBuilder<Drawable> load(@Nullable byte[] model) {
        return asDrawable().load(model);
    }

    @NonNull
    @CheckResult
    @Override
    public RequestBuilder<Drawable> load(@Nullable Object model) {
        returnasDrawable().load(model); }... Drawable @nonnull @checkResult public RequestBuilder<Drawable>asDrawable() {// The resource type is drawable.classreturnas(Drawable.class); Public RequestBuilder<Bitmap>asBitmap() {
        returnas(Bitmap.class).apply(DECODE_TYPE_BITMAP); } // Specify to load Gif
    public RequestBuilder<GifDrawable> asGif() {
        return as(GifDrawable.class).apply(DECODE_TYPE_GIF);
    }

    @NonNull
    @CheckResult
    public <ResourceType> RequestBuilder<ResourceType> as(
            @NonNull Class<ResourceType> resourceClass) {
        return new RequestBuilder<>(glide, this, resourceClass, context);
    }
Copy the code

The asDrawable() method is called to obtain the constructor for a RequestBuilder request. Glide turns downloaded image resources into Drawable resource objects by default. If there are other specified requirements for resource conversion, we can use asBitmap() or asGif() before the LAOD method, and their internal code is the same. The Class passed in is just different. The load method is then called with parameters.

// RequestBuilder class: @nonnull @override @checkResult public RequestBuilder<TranscodeType> load(@nullable String String) {return loadGeneric(string);
    }

    @NonNull
    @CheckResult
    @Override
    public RequestBuilder<TranscodeType> load(@Nullable Uri uri) {
        return loadGeneric(uri);
    }

    @NonNull
    @CheckResult
    @Override
    public RequestBuilder<TranscodeType> load(@Nullable File file) {
        return loadGeneric(file);
    }

    @NonNull
    @CheckResult
    @SuppressWarnings("unchecked")
    @Override
    public RequestBuilder<TranscodeType> load(@Nullable Object model) {
        return loadGeneric(model);
    }

    @NonNull
    private RequestBuilder<TranscodeType> loadGeneric(@Nullable Object model) {
        this.model = model;
        isModelSet = true;
        return this;
    }
Copy the code

The loadGeneric method is called in the RequestBuilder. The main data source for this method is stored in the member Model of the RequestBuilder. And set isModelSet to true to indicate that the model has been set; Finally, return the object itself, this, to be called later.

3. Apply (options) (i.e. : requestManager.apply (options))

  • Apply definition: Sets the option parameters
  • Apply: sets option parameters, such as cache policy, image display (placeholder map, error map, scaleType, etc.), whether to animate, etc
  • The source code
/ / RequestBuilder class:  @NonNull @CheckResult @Override public RequestBuilder<TranscodeType> apply(@NonNull BaseRequestOptions<? > requestOptions) { Preconditions.checkNotNull(requestOptions);returnsuper.apply(requestOptions); Equestoptions: private static final int UNSET = -1; equestoptions: private static final int UNSET = -1; private static final int SIZE_MULTIPLIER = 1 << 1; private static final int DISK_CACHE_STRATEGY = 1 << 2; private static final int PRIORITY = 1 << 3; private static final int ERROR_PLACEHOLDER = 1 << 4; private static final int ERROR_ID = 1 << 5; private static final int PLACEHOLDER = 1 << 6; private static final int PLACEHOLDER_ID = 1 << 7; private static final int IS_CACHEABLE = 1 << 8; private static final int OVERRIDE = 1 << 9; private static final int SIGNATURE = 1 << 10; private static final int TRANSFORMATION = 1 << 11; private static final int RESOURCE_CLASS = 1 << 12; private static final int FALLBACK = 1 << 13; private static final int FALLBACK_ID = 1 << 14; private static final int THEME = 1 << 15; private static final int TRANSFORMATION_ALLOWED = 1 << 16; private static final int TRANSFORMATION_REQUIRED = 1 << 17; private static final int USE_UNLIMITED_SOURCE_GENERATORS_POOL = 1 << 18; private static final int ONLY_RETRIEVE_FROM_CACHE = 1 << 19; private static final int USE_ANIMATION_POOL = 1 << 20; private static boolean isSet(int fields, int flag) {return(fields & flag) ! = 0; } @NonNull @CheckResult public T apply(@NonNull BaseRequestOptions<? > o) {if (isAutoCloneEnabled) {
            return clone().apply(o); } BaseRequestOptions<? > other = o;if (isSet(other.fields, SIZE_MULTIPLIER)) {
            sizeMultiplier = other.sizeMultiplier;
        }
        if (isSet(other.fields, USE_UNLIMITED_SOURCE_GENERATORS_POOL)) {
            useUnlimitedSourceGeneratorsPool = other.useUnlimitedSourceGeneratorsPool;
        }
        if (isSet(other.fields, USE_ANIMATION_POOL)) {
            useAnimationPool = other.useAnimationPool;
        }
        if (isSet(other.fields, DISK_CACHE_STRATEGY)) {
            diskCacheStrategy = other.diskCacheStrategy;
        }
        if (isSet(other.fields, PRIORITY)) {
            priority = other.priority;
        }
        if (isSet(other.fields, ERROR_PLACEHOLDER)) {
            errorPlaceholder = other.errorPlaceholder;
            errorId = 0;
            fields &= ~ERROR_ID;
        }
        if (isSet(other.fields, ERROR_ID)) {
            errorId = other.errorId;
            errorPlaceholder = null;
            fields &= ~ERROR_PLACEHOLDER;
        }
        if (isSet(other.fields, PLACEHOLDER)) {
            placeholderDrawable = other.placeholderDrawable;
            placeholderId = 0;
            fields &= ~PLACEHOLDER_ID;
        }
        if (isSet(other.fields, PLACEHOLDER_ID)) {
            placeholderId = other.placeholderId;
            placeholderDrawable = null;
            fields &= ~PLACEHOLDER;
        }
        if (isSet(other.fields, IS_CACHEABLE)) {
            isCacheable = other.isCacheable;
        }
        if (isSet(other.fields, OVERRIDE)) {
            overrideWidth = other.overrideWidth;
            overrideHeight = other.overrideHeight;
        }
        if (isSet(other.fields, SIGNATURE)) {
            signature = other.signature;
        }
        if (isSet(other.fields, RESOURCE_CLASS)) {
            resourceClass = other.resourceClass;
        }
        if (isSet(other.fields, FALLBACK)) {
            fallbackDrawable = other.fallbackDrawable;
            fallbackId = 0;
            fields &= ~FALLBACK_ID;
        }
        if (isSet(other.fields, FALLBACK_ID)) {
            fallbackId = other.fallbackId;
            fallbackDrawable = null;
            fields &= ~FALLBACK;
        }
        if (isSet(other.fields, THEME)) {
            theme = other.theme;
        }
        if (isSet(other.fields, TRANSFORMATION_ALLOWED)) {
            isTransformationAllowed = other.isTransformationAllowed;
        }
        if (isSet(other.fields, TRANSFORMATION_REQUIRED)) {
            isTransformationRequired = other.isTransformationRequired;
        }
        if (isSet(other.fields, TRANSFORMATION)) {
            transformations.putAll(other.transformations);
            isScaleOnlyOrNoTransform = other.isScaleOnlyOrNoTransform;
        }
        if (isSet(other.fields, ONLY_RETRIEVE_FROM_CACHE)) {
            onlyRetrieveFromCache = other.onlyRetrieveFromCache;
        }

        // Applying options with dontTransform() is expected to clear our transformations.
        if(! isTransformationAllowed) { transformations.clear(); fields &= ~TRANSFORMATION; isTransformationRequired =false;
            fields &= ~TRANSFORMATION_REQUIRED;
            isScaleOnlyOrNoTransform = true; } fields |= other.fields; // Add all configurations to the Options object, This object is initialized directly in the member properties // This object internally maintains an ArrayMap of type CachedHashCodeArrayMap(a subclass of ArrayMap) to store option configurations options.putall (other.options);return selfOrThrowIfLocked();
    }
Copy the code

Requestmanager.apply (options) will finally be called to the apply method of the parent BaseRequestOptions class. Notice that BaseRequestOptions has an int member named fields. The apply method calls the isSet() method all the time. The isSet() method says that if the RequestOptions object passed in isSet to XXX by the other attribute, the request object will take place on the equestoptions object. The Options object replaces the existing properties and, in special cases, removes the other flags, eventually adding all the configuration to the Options object. Fields are used to indicate whether attributes have been assigned. We have placeholder() methods for analysis. There are two overloaded methods for placeholder() :

    public RequestOptions placeholder(@Nullable Drawable drawable) {
        if (isAutoCloneEnabled) {
            return clone().placeholder(drawable); } this.placeholderDrawable = drawable; fields |= PLACEHOLDER; }} // Placeholder ID = 0 when empty; fields &= ~PLACEHOLDER_ID; // Place the id flag bit to 0return selfOrThrowIfLocked();
    }

    public RequestOptions placeholder(@DrawableRes int resourceId) {
        if (isAutoCloneEnabled) {
            return clone().placeholder(resourceId); } this.placeholderId = resourceId; fields |= PLACEHOLDER_ID; } // Placeholder placeholder drawable = null;} // Placeholder placeholder drawable = null; fields &= ~PLACEHOLDER; // Set the drawable placeholder flag to 0return selfOrThrowIfLocked();
    }
Copy the code

Placeholder (drawable) and placeholder(resourceId) cannot affect the placeholder at the same time, So there will be a fields | = PLACEHOLDER and fields & = ~ PLACEHOLDER_ID such code,

The system defines 21 flag bits, each of which indicates whether a RequestOptions attribute is assigned to or not. The system uses bit-operation to represent 21 bool logics as an int.

4. into(T) (i.e. : RequestBuilder. Into (T))

  • Into definition: the last step to display images in normal loading Settings
  • Into: constructs a network request object and executes the network request
  • The source code
Public <Y extends Target<TranscodeType>> Y into(@nonnull Y Target) { The incoming Executors. MainThreadExecutor () the main thread schedulerreturninto(target, /*targetListener=*/ null, Executors.mainThreadExecutor()); } @NonNull public ViewTarget<ImageView, TranscodeType> into(@NonNull ImageView view) { Util.assertMainThread(); Preconditions.checkNotNull(view); BaseRequestOptions<? > requestOptions = this;if(! requestOptions.isTransformationSet() && requestOptions.isTransformationAllowed() && view.getScaleType() ! = null) {// clone a RequestOptions based on the View scaleType and configure a scaleType for the View. Switch (view.getScaleType()) {case CENTER_CROP:
                    requestOptions = requestOptions.clone().optionalCenterCrop();
                    break;
                case CENTER_INSIDE:
                    requestOptions = requestOptions.clone().optionalCenterInside();
                    break;
                case FIT_CENTER:
                case FIT_START:
                case FIT_END:
                    requestOptions = requestOptions.clone().optionalFitCenter();
                    break;
                case FIT_XY:
                    requestOptions = requestOptions.clone().optionalCenterInside();
                    break;
                case CENTER:
                caseMATRIX: default: // Do nothing.}} // Call into method with 4 argumentsreturnInto (. / / call GlideContext buildImageViewTarget build a ViewTarget GlideContext. BuildImageViewTarget (view, transcodeClass), /*targetListener=*/ null, requestOptions, // Note: The incoming Executors. MainThreadExecutor () the main thread scheduler Executors. MainThreadExecutor ()); } @NonNull @Synthetic <Y extends Target<TranscodeType>> Y into( @NonNull Y target, @Nullable RequestListener<TranscodeType> targetListener, Executor callbackExecutor) {returninto(target, targetListener, /*options=*/ this, callbackExecutor); } private <Y extends Target<TranscodeType>> Y into( @NonNull Y target, @Nullable RequestListener<TranscodeType> targetListener, BaseRequestOptions<? > options, Executor callbackExecutor) { ...... }Copy the code

Into (target) has multiple overloads, but if we look closely, there are only two into methods available for developers to use: into(@nonnull Y target) and into(@nonnull ImageView View). We can either set the ImageView method to display the image directly, or we can set a custom Target to monitor the resource callback. We can set the image display in the callback.

Glide is a concept that can be transformed into something else. Into (@nonnull ImageView view) configures the view’s scaleType option. Continuing with the into method above, we can see that the last method called is the last into method with four arguments:

// GlideContext class: private final ImageViewTargetFactory ImageViewTargetFactory; public GlideContext( ... , @NonNull ImageViewTargetFactory imageViewTargetFactory, ...) {... this.imageViewTargetFactory = imageViewTargetFactory; . } @NonNull public <X> ViewTarget<ImageView, X> buildImageViewTarget( @NonNull ImageView imageView, @nonnull Class<X> transcodeClass) {// Call the factory Class to create a ViewTarget for the imageViewreturn imageViewTargetFactory.buildTarget(imageView, transcodeClass);
    }
    
public class ImageViewTargetFactory {
    @NonNull
    @SuppressWarnings("unchecked") public <Z> ViewTarget<ImageView, Z> buildTarget(@nonnull ImageView View, @nonnull Class<Z> clazz) { So ViewTarget here is DrawableImageViewTargetif (Bitmap.class.equals(clazz)) {
            return (ViewTarget<ImageView, Z>) new BitmapImageViewTarget(view);
        } else if (Drawable.class.isAssignableFrom(clazz)) {
            return (ViewTarget<ImageView, Z>) new DrawableImageViewTarget(view);
        } else {
            throw new IllegalArgumentException(
                    "Unhandled class: " + clazz + ", try .as*(Class).transcode" + "(ResourceTranscoder)"); } // RequestBuilder class:  private <Y extends Target<TranscodeType>> Y into( @NonNull Y target, @Nullable RequestListener<TranscodeType> targetListener, BaseRequestOptions<? > options, Executor callbackExecutor) { Preconditions.checkNotNull(target);if(! isModelSet) { throw new IllegalArgumentException("You must call #load() before calling #into()"); Request = buildRequest(target, targetListener, options, callbackExecutor); Null Request previous = target.getrequest (); // If the current ImageView has been set (request == previous)if(request.isEquivalentTo(previous) && ! isSkipMemoryCacheWithCompletePreviousRequest(options, previous)) { request.recycle(); // If the previous request was completed, starting again will ensure that the result is retransmitted, triggering RequestListeners and Targets. // If the request fails, restart the request again and give it another chance to complete. // If the request is already running, we can let it continue without interruption.if(! Preconditions. CheckNotNull (previous). Set the ()) {/ / using the previous request is not a new request to allow optimization, / / skip set placeholder, for example, tracking, and cancel the tracking target, and obtain the view size. previous.begin(); }returntarget; } requestManager.clear(Target); // Set the request to the target View (save) target.setrequest (request); // Call the requestManager.track method to execute the request requestManager.track(target, request);return target;
    }
Copy the code

You can see that the GlideContext is used in the Into (imageView) method to create a ViewTarget for the imageView using the factory class. It describes the View target that will be used after the image processing is complete. We’ll leave the ViewTarget analysis to the Glide Target section later. Now that we’ve built the ViewTarge, we go to the last into, You can see that buildRequest is called to build a Glide Request, and the process of building it is also very interesting, ending with SingleRequest. Obtain building a Request instance object, Then you call requestManager.track to distribute and execute it.

Let’s move on to the requestManager.track(Target, Request) implementation:

// RequestManager: synchronized void track(@nonnull Target<? > target, @nonnull Request Request) {// targetTracker: save the target set currently active for the RequestManager and forward lifecycle events. targetTracker.track(target); // requestTracker, which is initialized when the RequestManager is initialized // Function: a class for tracking, canceling, and restarting ongoing, completed, and failed requests. This class is not thread-safe and must be accessed on the main thread. requestTracker.runRequest(request); } // RequestTracker class: public void runRequest(@nonnull Request Request) {request.add (Request);if(! isPaused) { request.begin(); }else {
            request.clear();
            if (Log.isLoggable(TAG, Log.VERBOSE)) {
                Log.v(TAG, "Paused, delaying request"); } pendingRequests.add(request); }}Copy the code

As you can see, the track method adds the target to the Targets set of the targetTracker class and then runs the Request begin method. Begin to trace the source code. Request is just an interface, so we need to find its implementation class.

The construction of a Request

Let’s take a look at the buildRequest of its Request to build the Request:

/ / RequestBuilder class:  private Request buildRequest( Target<TranscodeType> target, @Nullable RequestListener<TranscodeType> targetListener, BaseRequestOptions<? > requestOptions, Executor callbackExecutor) {// build the request recursive methodreturnbuildRequestRecursive( target, targetListener, /*parentCoordinator=*/ null, transitionOptions, requestOptions.getPriority(), requestOptions.getOverrideWidth(), requestOptions.getOverrideHeight(), requestOptions, callbackExecutor); } private Request buildRequestRecursive( Target<TranscodeType> target, @Nullable RequestListener<TranscodeType> targetListener, @Nullable RequestCoordinator parentCoordinator, TransitionOptions<? ,? super TranscodeType> transitionOptions, Priority priority, int overrideWidth, int overrideHeight, BaseRequestOptions<? > requestOptions, Executor callbackExecutor) {// If necessary, build the ErrorRequestCoordinator so that we can update the parentCoordinator. ErrorRequestCoordinator errorRequestCoordinator = null;if(errorBuilder ! = null) { errorRequestCoordinator = new ErrorRequestCoordinator(parentCoordinator); parentCoordinator = errorRequestCoordinator; } / / main demand Request mainRequest = / / build thumbnails Request buildThumbnailRequestRecursive recursive method (target, targetListener, parentCoordinator, transitionOptions, priority, overrideWidth, overrideHeight, requestOptions, callbackExecutor);if (errorRequestCoordinator == null) {
            returnmainRequest; } / / build errors request int errorOverrideWidth = errorBuilder. GetOverrideWidth (); int errorOverrideHeight = errorBuilder.getOverrideHeight();if(Util.isValidDimensions(overrideWidth, overrideHeight) && ! errorBuilder.isValidOverride()) { errorOverrideWidth = requestOptions.getOverrideWidth(); errorOverrideHeight = requestOptions.getOverrideHeight(); } Request errorRequest = errorBuilder.buildRequestRecursive( target, targetListener, errorRequestCoordinator, errorBuilder.transitionOptions, errorBuilder.getPriority(), errorOverrideWidth, errorOverrideHeight, errorBuilder, callbackExecutor); errorRequestCoordinator.setRequests(mainRequest, errorRequest);returnerrorRequestCoordinator; } private Request buildThumbnailRequestRecursive( Target<TranscodeType> target, RequestListener<TranscodeType> targetListener, @Nullable RequestCoordinator parentCoordinator, TransitionOptions<? ,? super TranscodeType> transitionOptions, Priority priority, int overrideWidth, int overrideHeight, BaseRequestOptions<? > requestOptions, Executor callbackExecutor) {// default to null, Unless you manually call the thumbnail(@Nullable RequestBuilder<TranscodeType> thumbnailRequest) method of the // RequestBuilder class.if(thumbnailBuilder ! = null) {// Recursive case: contains a possible recursive thumbnail request builder.if (isThumbnailBuilt) {
                throw new IllegalStateException("You cannot use a request as both the main " +
                        "request and a "
                        + "thumbnail, consider using clone() on the request(s) passed to " +
                        "thumbnail()"); } TransitionOptions<? ,? super TranscodeType> thumbTransitionOptions = thumbnailBuilder.transitionOptions; // By default, apply our transformation to the thumbnail request, but avoid overwriting custom options that may already be explicitly applied to the thumbnail requestif(thumbnailBuilder.isDefaultTransitionOptionsSet) { thumbTransitionOptions = transitionOptions; } / / thumbnail cable Priority thumbPriority = thumbnailBuilder. IsPrioritySet ()? thumbnailBuilder.getPriority() : getThumbnailPriority(priority); / / thumbnail wide high int thumbOverrideWidth = thumbnailBuilder. GetOverrideWidth (); int thumbOverrideHeight = thumbnailBuilder.getOverrideHeight(); // Check the width and heightif(Util.isValidDimensions(overrideWidth, overrideHeight) && ! thumbnailBuilder.isValidOverride()) { thumbOverrideWidth = requestOptions.getOverrideWidth(); thumbOverrideHeight = requestOptions.getOverrideHeight(); } // The thumbnail request protocol, At the same time coordinate the original request with thumbnails ThumbnailRequestCoordinator coordinator = new ThumbnailRequestCoordinator (parentCoordinator); // fullRequest = obtainRequest(target, targetListener, requestOptions, coordinator, transitionOptions, priority, overrideWidth, overrideHeight, callbackExecutor); isThumbnailBuilt =true; / / generated thumbnail recursive request / / call buildRequestRecursive, or will be transferred to the buildThumbnailRequestRecursive, This is a recursive method Request thumbRequest = thumbnailBuilder. BuildRequestRecursive (target, targetListener, coordinator, thumbTransitionOptions, thumbPriority, thumbOverrideWidth, thumbOverrideHeight, thumbnailBuilder, callbackExecutor); isThumbnailBuilt =false; / / the two request packaging to ThumbnailRequestCoordinator coordinator. SetRequests (fullRequest thumbRequest);returncoordinator; // Default to null, otherwise call the RequestBuilder class thumbnail(floatSizeMultiplier) method}else if(thumbSizeMultiplier ! = null) {/ / according to specified scaling factor loading thumbnail ThumbnailRequestCoordinator coordinator = new ThumbnailRequestCoordinator(parentCoordinator); Request fullRequest = obtainRequest( target, targetListener, requestOptions, coordinator, transitionOptions, priority, overrideWidth, overrideHeight, callbackExecutor); BaseRequestOptions<? > thumbnailOptions = requestOptions.clone().sizeMultiplier(thumbSizeMultiplier); Request thumbnailRequest = obtainRequest( target, targetListener, thumbnailOptions, coordinator, transitionOptions, getThumbnailPriority(priority), overrideWidth, overrideHeight, callbackExecutor); coordinator.setRequests(fullRequest, thumbnailRequest);return coordinator;
        } else{// Only load the original imagereturnobtainRequest( target, targetListener, requestOptions, parentCoordinator, transitionOptions, priority, overrideWidth, overrideHeight, callbackExecutor); } } private Request obtainRequest( Target<TranscodeType> target, RequestListener<TranscodeType> targetListener, BaseRequestOptions<? > requestOptions, RequestCoordinator requestCoordinator, TransitionOptions<? ,? super TranscodeType> transitionOptions, Priority priority, int overrideWidth, int overrideHeight, Executor callbackExecutor) {returnSingleRequest. Obtain (context, glideContext, model, // Load (URL), TranscodeClass, requestOptions, overrideWidth, // overrideHeight, // High priority, target, targetListener, RequestListeners requestCoordinator, glideContext. GetEngine (), / / global load engine transitionOptions getTransitionFactory (), callbackExecutor); }Copy the code

From the source, if we don’t call the Thumbnail () method of the RequestBuilder class, the last request to build is SingleRequest, so take a look at its begin method:

// Class: @override public synchronized voidbegin() { assertNotCallingCallbacks(); stateVerifier.throwIfRecycled(); startTime = LogTime.getLogTime(); // If the image source is not set, the load will failif (model == null) {
            if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
                width = overrideWidth;
                height = overrideHeight;
            }
            int logLevel = getFallbackDrawable() == null ? Log.WARN : Log.DEBUG;
            onLoadFailed(new GlideException("Received null model"), logLevel);
            return;
        }

        if (status == Status.RUNNING) {
            throw new IllegalArgumentException("Cannot restart a running request"); } // If we restart after completion (usually with the same request in the same Target or View via notifyDataSetChanged), // we can simply use the last retrieved resource and size and skip getting the new size, start the new load, etc. // This means that users who want to restart the load because they expect the view size to have changed need to explicitly clear the view or target before starting the new load.if (status == Status.COMPLETE) {
            onResourceReady(resource, DataSource.MEMORY_CACHE);
            return; } // Restart requests that are neither complete nor running can be treated as new requests and can be restarted from scratch. status = Status.WAITING_FOR_SIZE; // If the Target's width and height are valid, proceed to the next stepif (Util.isValidDimensions(overrideWidth, overrideHeight)) {
            onSizeReady(overrideWidth, overrideHeight);
        } else{// getSize(this); // getSize(this); // getSize(this); }if((status = = status. RUNNING | | status = = status. WAITING_FOR_SIZE) && canNotifyStatusChanged ()) {/ / set placeholder figure target.onLoadStarted(getPlaceholderDrawable()); }if (IS_VERBOSE_LOGGABLE) {
            logV("finished run method in "+ LogTime.getElapsedMillis(startTime)); } // ViewTarget class:  @CallSuper @Override public void getSize(@NonNull SizeReadyCallback cb) { sizeDeterminer.getSize(cb); } / / ViewTarget SizeDeterminer classes: void getSize (@ NonNull SizeReadyCallback cb) {int currentWidth = getTargetWidth (); int currentHeight = getTargetHeight(); // View state and size are validif(isViewStateAndSizeValid(currentWidth, Cb. onSizeReady(currentWidth, currentHeight);return; } // We want to notify callbacks in the order they are added. We only want to add one or more callbacks at a time, so List is a reasonable choice. // If the width and height or state obtained in the previous step are invalid, save the CB in the list and call onSizeReady after measuring the effective width and heightif(! cbs.contains(cb)) { cbs.add(cb); }if(layoutListener == null) {// get the size of the ViewTreeObserver after the View is drawn. Prevents getting to 0 ViewTreeObserver observer = view.getViewTreeObserver(); layoutListener = new SizeDeterminerLayoutListener(this); // Add a pre-draw listener, Mainly is to monitor the View on the interface mapped / / and then call its own onPreDraw () method -- "sizeDeterminer. CheckCurrentDimens () / / -- > notifyCbs (currentWidth, CurrentHeight) to inform all execute sequentially onSizeReady CBS set the observer. AddOnPreDrawListener (layoutListener); }}Copy the code

SingleRequest implements the SizeReadyCallback interface by calling the current View size back and forth. Ok, let’s summarize the implementation in the begin method:

  1. Check whether the image source is NULL. If it is null, the loading fails
  2. Check whether the width and height of the Target saved in the Request are valid. If yes, use the onSizeReady method
  3. If 2 is invalid, go to ViewTarget to get width and height
  4. If the width and height obtained in ViewTraget are valid and the view state is valid, the onSizeReady method in SingleRequest is called directly
  5. If the “get” in 4 is invalid and the View is not valid, then the ViewTreeObserver listens to the completion of the View drawing, and finally executes the onSizeReady method in SingleRequest

By summarizing the begin method, it is clear that the next step is the onSizeReady method in SingleRequest:

/ / SingleRequest class:  @Override public synchronized void onSizeReady(int width, int height) { stateVerifier.throwIfRecycled();if (IS_VERBOSE_LOGGABLE) {
            logV("Got onSizeReady in " + LogTime.getElapsedMillis(startTime));
        }
        if(status ! = Status.WAITING_FOR_SIZE) {return; } status = Status.RUNNING; // Calculate the size of the thumbnailfloat sizeMultiplier = requestOptions.getSizeMultiplier();
        this.width = maybeApplySizeMultiplier(width, sizeMultiplier);
        this.height = maybeApplySizeMultiplier(height, sizeMultiplier);

        if (IS_VERBOSE_LOGGABLE) {
            logV("finished setup for calling load in "+ LogTime.getElapsedMillis(startTime)); } / / real load flow loadStatus = engine load (glideContext, model, requestOptions getSignature (), enclosing width, enclosing height, requestOptions.getResourceClass(), transcodeClass, priority, requestOptions.getDiskCacheStrategy(), requestOptions.getTransformations(), requestOptions.isTransformationRequired(), requestOptions.isScaleOnlyOrNoTransform(), requestOptions.getOptions(), requestOptions.isMemoryCacheable(), requestOptions.getUseUnlimitedSourceGeneratorsPool(), requestOptions.getUseAnimationPool(), requestOptions.getOnlyRetrieveFromCache(), this, callbackExecutor); . }Copy the code

Now comes the real loading process!!

5. Engine. Load

public synchronized <R> LoadStatus load( GlideContext glideContext, Object model, Key signature, int width, int height, Class<? > resourceClass, Class<R> transcodeClass, Priority priority, DiskCacheStrategy diskCacheStrategy, Map<Class<? >, Transformation<? >> transformations, boolean isTransformationRequired, boolean isScaleOnlyOrNoTransform, Options options, boolean isMemoryCacheable, boolean useUnlimitedSourceExecutorPool, boolean useAnimationPool, boolean onlyRetrieveFromCache, ResourceCallback cb, Executor callbackExecutor) { long startTime = VERBOSE_IS_LOGGABLE ? LogTime.getLogTime() : 0; EngineKey = keyfactory.buildKey (Model, signature, width, height, doubling, doubling, doubling, doubling, doubling, doubling) resourceClass, transcodeClass, options); // look up the EngineResource for this key from the in-memory ActiveResources(internally maintained HashMap) cache and try to reuse EngineResource<? > active = loadFromActiveResources(key, isMemoryCacheable);if(active ! Cb. onResourceReady(active, DataSource.MEMORY_CACHE); cB. onResourceReady(active, DataSource.MEMORY_CACHE);if (VERBOSE_IS_LOGGABLE) {
                logWithTimeAndKey("Loaded resource from active resources", startTime, key);
            }
            returnnull; } // Try to find this resource from LruResourceCache, try to reuse it, if it exists, cache it to an EngineResource<? > cached = loadFromCache(key, isMemoryCacheable);if(cached ! Cb. onResourceReady(cached, datasource.memory_cache);if (VERBOSE_IS_LOGGABLE) {
                logWithTimeAndKey("Loaded resource from cache", startTime, key);
            }
            returnnull; } // EngineJob<? > current = jobs.get(key, onlyRetrieveFromCache);if(current ! = null) {current.addCallback(cb, callbackExecutor);if (VERBOSE_IS_LOGGABLE) {
                logWithTimeAndKey("Added to existing load", startTime, key); } // Return the loaded statereturnnew LoadStatus(cb, current); } // This is a new task. Task is to build a new engine EngineJob < R > EngineJob = engineJobFactory. Build (key, isMemoryCacheable useUnlimitedSourceExecutorPool, useAnimationPool, onlyRetrieveFromCache); <R> DecodeJob = decodeJobFactory. Build (glideContext, model, key, signature, width, height, resourceClass, transcodeClass, priority, diskCacheStrategy, transformations, isTransformationRequired, isScaleOnlyOrNoTransform, onlyRetrieveFromCache, options, engineJob); Jobs.put (key, engineJob); // engineJob. AddCallback (cb, callbackExecutor); // Execute the task enginejob. start(decodeJob);if (VERBOSE_IS_LOGGABLE) {
            logWithTimeAndKey("Started new load", startTime, key);
        }
        return new LoadStatus(cb, engineJob);
    }
Copy the code

So to summarize what’s going on in Engine.Load? :

  1. Build the key for this request

  2. Find the resource corresponding to the key from the cache. If there is one, call onResourceReady directly to indicate that the resource is ready

    • From the ActiveResources cache
    • Look for it from the LruResourceCache cache, if it exists, it is cached to ActiveResources, and then returned
  3. Find the task corresponding to the key from the task cache

    If yes, you do not need to obtain resources again

  4. If no cache task exists in 3, a new task is built

    • Build engine task EngineJob
    • The engine’s task is DecodeJob
    • Add tasks to the cache to prevent multiple builds
    • Add callbacks and pass in the main thread scheduler
    • Perform a task

To understand a few concepts:

Source file (unprocessed resource) Source file (unprocessed resource)Copy the code

Enginejob. start(decodeJob)

Public synchronized void start(DecodeJob<R> DecodeJob) {this.synchronized = DecodeJob; // There are three types of Executor, DiskCacheExecutor, DiskCacheExecutor,sourceExecutor,sourceUnlimitedExecutor GlideExecutor executor = decodeJob.willDecodeFromCache() ? diskCacheExecutor : getActiveSourceExecutor(); Execute (decodeJob); // Execute the task executor.execute(decodeJob); } // DecodeJob class: @suppresswarnings ("PMD.AvoidRethrowingException")
    @Override
    public void run() {
        GlideTrace.beginSectionFormat("DecodeJob#run(model=%s)", model); DataFetcher<? >localFetcher = currentFetcher; Try {// If it has been cancelled, the loading failsif (isCancelled) {
                notifyFailed();
                return; } // Call runWrapped runWrapped(); } catch (CallbackException e) { ...... } } private voidrunWrapped() {switch (runReason) {// The default state is INITISLIZEcaseINITIALIZE: // Gets the enumeration class where the data is being decoded stage = getNextStage(stage.initialize); // Get DataFetcherGenerator for the current task. CurrentGenerator = getNextGenerator(); // Execute the task runGenerators();break;
            case SWITCH_TO_SOURCE_SERVICE:
                runGenerators();
                break;
            case DECODE_DATA:
                decodeFromRetrievedData();
                break;
            default:
                throw new IllegalStateException("Unrecognized run reason: "+ runReason); } // Obtain the execution phase of the task: Private Stage getNextStage(Stage current) {switch (current) {// Initial statecaseINITIALIZE: // Return stage. RESOURCE_CACHE if the cache policy we configured allows reading data from the resource cachereturndiskCacheStrategy.decodeCachedResource() ? Stage.RESOURCE_CACHE : getNextStage(Stage.RESOURCE_CACHE); // Read the converted cachecaseRESOURCE_CACHE: // Return stage. DATA_CACHE if the cache policy we configured allows reading data from the source data cachereturndiskCacheStrategy.decodeCachedData() ? Stage.DATA_CACHE : getNextStage(Stage.DATA_CACHE); // Read the original file cachecaseDATA_CACHE: // If the user chooses to retrieve the resource only from the cache, the load is skipped from the source.return onlyRetrieveFromCache ? Stage.FINISHED : Stage.SOURCE;
            caseSOURCE: // Remote image loadingcaseFINISHED: // End statusreturn Stage.FINISHED;
            default:
                throw new IllegalArgumentException("Unrecognized stage: " + current);
        }
    }

    private DataFetcherGenerator getNextGenerator() {// Get the data extractor generator based on the current type of the enum class that is trying to decode the data.caseRESOURCE_CACHE: // Resource cache generator that reads files from cache from hard diskreturn new ResourceCacheGenerator(decodeHelper, this);
            caseDATA_CACHE: // Data cache generator, loaded from the original file cachereturn new DataCacheGenerator(decodeHelper, this);
            caseSOURCE: // SOURCE generator, no cache, remote image resource loader (e.g. network, local file)return new SourceGenerator(decodeHelper, this);
            case FINISHED:
                return null;
            default:
                throw new IllegalStateException("Unrecognized stage: " + stage);
        }
    }

    private void runGenerators() {
        currentThread = Thread.currentThread();
        startFetchTime = LogTime.getLogTime();
        boolean isStarted = false; // Here the Generator. StartNext method is the loading process and returns on successtrueAnd break out of the loop, otherwise the switch Generator continueswhile(! isCancelled && currentGenerator ! = null && ! (isStarted = currentGenerator startNext ())) {/ / retrieves actuators stage = getNextStage (stage); currentGenerator = getNextGenerator(); // If the task is executed to the remote load, and switch the execution environment of the taskif (stage == Stage.SOURCE) {
                reschedule();
                return; } // This request failed, continueif((stage == Stage.FINISHED || isCancelled) && ! isStarted) { notifyFailed(); } } @Override public voidreschedule() {// Change the execution target to the SOURCE service. Of course, this is only called if stage == stage.source. runReason = RunReason.SWITCH_TO_SOURCE_SERVICE; callback.reschedule(this); Callback is the EngineJob. } @override public void reschedule(DecodeJob<? > job) {// You can see that the SourceExecutor is fetched to execute the decodeJob. // We will subtly switch the decodeJob task from cacheExecutor to SourceExecutor to make the division of labor more efficient. getActiveSourceExecutor().execute(job); } // ResourceCacheGenerator class: public BooleanstartNext() {... // Step 1, iteratesourceIds, try to get the cache File, and use getModelLoaders to find out if there are suitable modelLoaders.while(modelLoaders == null || ! hasNextModelLoader()) { resourceClassIndex++;if (resourceClassIndex >= resourceClasses.size()) {
                sourceIdIndex++;
                if (sourceIdIndex >= sourceIds.size()) {
                    return false;
                }
                resourceClassIndex = 0;
            }

            Key sourceId = sourceIds.get(sourceIdIndex); Class<? > resourceClass = resourceClasses.get(resourceClassIndex); Transformation<? > transformation = helper.getTransformation(resourceClass); currentKey = new ResourceCacheKey(// NOPMD AvoidInstantiatingObjectsInLoops helper.getArrayPool(),sourceId, helper.getSignature(), helper.getWidth(), helper.getHeight(), transformation, resourceClass, helper.getOptions()); // obtain data from the DiskCache // the implementation class of diskcache. Factory is DiskLruCacheFactory, which is based on the DiskLruCache implementation tool class. cacheFile = helper.getDiskCache().get(currentKey);if(cacheFile ! = null) {sourceKey = sourceId; modelLoaders = helper.getModelLoaders(cacheFile); modelLoaderIndex = 0; } // Step 2, iterate over moderLaders, generate loadData, and determine whether the task type conversion can be handled, then call loadData#DataFetcher#loadData start task.
        loadData = null;
        boolean started = false;
        while(! started && hasNextModelLoader()) { ModelLoader<File, ? > modelLoader = modelLoaders.get(modelLoaderIndex++); loadData = modelLoader.buildLoadData(cacheFile, helper.getWidth(), helper.getHeight(), helper.getOptions());if(loadData ! = null && helper.hasLoadPath(loadData.fetcher.getDataClass())) { started =true; loadData.fetcher.loadData(helper.getPriority(), this); }}return started;
    }
Copy the code

As you can see, there are several methods that make up Glide’s decoding process: trying to load an image from a converted local resource; Trying to load an image from an unprocessed source Try loading the image remotely. Look for the next loader by switching states until the graph is loaded, returning success, or failure if not found.

To summarize:

  • By default, the diskCacheExecutor is fetched to execute the decodeJob task.
  • The runnable run method of decodeJob is called;
  • Because RunReason is INITIALIZE, then get stage, which returns stage.resource_cache by default
  • This returns the ResourceCacheGenerator loader via getNextGenerator
  • The startNext method of ResourceCacheGenerator is then called to read the resources cached to disk from the converted cache
  • If the resource is successfully obtained, the task is terminated and the result is called back. If not, the task is switched to the DataCacheGenerator. Similarly, if not, the task is switched to the SourceGenerator loader (such as the first load without any cache).
  • Switch to the SourceGenerator environment, wait for it to finish, finish the task, call back the results, and the process is complete.

The code has now moved to the Reschedule in the EngineJob class and executed the task.

Load the network image — SourceGenerator

// SourceGenerator class: /** * SourceGenerator class: /** * SourceGenerator class: /** * SourceGenerator class: /** * SourceGenerator class: Extract and decode data resources based on different data sources (local, network, Asset, etc.) and how they are read (Stream, ByteBuffer, etc.). Loading network data * LocalUriFetcher: Loading local data * Other implementation classes... */ @Override public booleanstartNext() {//1. Check if there is a cache. If there is, load the cache directly (dataToCache is null).if(dataToCache ! = null) { Object data = dataToCache; dataToCache = null; cacheData(data); }if (sourceCacheGenerator ! = null &&sourceCacheGenerator.startNext()) {
            return true;
        }
        sourceCacheGenerator = null;

        loadData = null;
        boolean started = false; // Whether there are more ModelLoaderswhile(! Started && hasNextModelLoader()) {// From the data load collection of DecodeHelper, LoadData = helper.getloaddata ().get(loadDataListIndex++);if(loadData ! = null && (helper.getDiskCacheStrategy().isDataCacheable(loadData.fetcher.getDataSource()) || helper.hasLoadPath(loadData.fetcher.getDataClass()))) { started =true; To choose the appropriate LoadData, / / and use fetcher to pull data LoadData. The fetcher. LoadData (helper, getPriority (), this); }}return started;
    }
Copy the code

See if there’s some ambiguity here, what is ModelLoader?

Now let’s think about whether we had a piece of code (long, long) like this when we started Glide above:

Glide(...) {... registry ... .append(String.class, InputStream.class, new StringLoader.StreamFactory()) .append(Uri.class, InputStream.class, new HttpUriLoader.Factory()) .... .append(GlideUrl.class, InputStream.class, new HttpGlideUrlLoader.Factory()) ... ImageViewTargetFactory imageViewTargetFactory = new ImageViewTargetFactory(); glideContext = new GlideContext( context, arrayPool, registry, imageViewTargetFactory, defaultRequestOptions, defaultTransitionOptions, defaultRequestListeners, engine, isLoggingRequestOriginsEnabled,logLevel);
    }
Copy the code

And the append DataFetcher in this code is very network-specific. Let’s look at the registry class again:

Public class Registry {// Public class Registry {// Load, transform, decode, encrypt, etc. private final ModelLoaderRegistry modelLoaderRegistry; private final EncoderRegistry encoderRegistry; private final ResourceDecoderRegistry decoderRegistry; private final ResourceEncoderRegistry resourceEncoderRegistry; private final DataRewinderRegistry dataRewinderRegistry; private final TranscoderRegistry transcoderRegistry; private final ImageHeaderParserRegistry imageHeaderParserRegistry; . Public <Model, Data> Registry append(Class<Model> modelClass, Class<Data> dataClass, ModelLoaderFactory<Model, Data> factory) { modelLoaderRegistry.append(modelClass, dataClass, factory);returnthis; }... Public synchronized <Model, Data> void append(Class<Model> modelClass, Class<Data> dataClass,ModelLoaderFactory<Model, Data> factory) { multiModelLoaderFactory.append(modelClass, dataClass, factory); cache.clear(); Private <Model, Data> void add(Class<Model> modelClass, Class<Data> dataClass, Class<Data> dataClass, ModelLoaderFactory<Model, Data> factory, boolean append) { Entry<Model, Data> entry = new Entry<>(modelClass, dataClass, factory); Entries are a list. So, at this point, you know that the registered LoaderFactory is cached in the list for later retrieval. entries.add(append ? entries.size() : 0, entry); }}Copy the code

From the above code, you know that ModelLoaderFactory is registered in a list during Glide initialization for later use. On the analysis of DecodeJob code, we use the SourceGenerator loading remote images, and analyze the loadData. Fetcher. LoadData (helper, getPriority (), this); This is where the data is actually loaded. LoadData = helper.getloaddata ().get(loadDataListIndex++); So where did the helper come from? The helper is actually a member property of the DecodeJob and is initialized directly when declared, so let’s look at the implementation of helper.getLoadData() :

//DecodeHelper List<LoadData<? >>getLoadData() {
        if(! isLoadDataSet) { isLoadDataSet =true; loadData.clear(); // Retrieve the List of ModelLoaders from the glideContext registry (List<ModelLoader<Object,? >> modelLoaders = glideContext.getRegistry().getModelLoaders(model); //noinspection ForLoopReplaceableByForEach to improve perffor(int i = 0, size = modelLoaders.size(); i < size; i++) { ModelLoader<Object, ? > modelLoader = modelLoaders.get(i); // Build LoadData LoadData<? > current = modelLoader.buildLoadData(model, width, height, options); // If current is not empty, it is added to the loadData setif(current ! = null) { loadData.add(current); }}}return loadData;
    }
Copy the code

Let’s start with two concepts:

  • ModelLoader: ModelLoader needs to accept two generic types

    . ModelLoader itself is a factory interface. The main work is to transform the complex Data model into the required Data through DataFetcher. LoadData is the inner class of ModelLoader and the encapsulation entity of DataFetcher and Key.
    ,data>
  • LoadData: Comes from the Registry Registry, which is built when Glide is initialized. Function: Obtain the appropriate data grabber (DataFetcher) to obtain data.

You can see that getLoadData() returns a collection of LoadData. And the data grabber related to the network:

.append(String.class, InputStream.class, new StringLoader.StreamFactory()
.append(Uri.class, InputStream.class, new HttpUriLoader.Factory())
.append(GlideUrl.class, InputStream.class, new HttpGlideUrlLoader.Factory())
Copy the code

Obviously, we’re basically passing in String urls, so we’re really using a StringLoader:

public class StringLoader<Data> implements ModelLoader<String, Data> {
    private final ModelLoader<Uri, Data> uriLoader;

    // Public API.
    @SuppressWarnings("WeakerAccess")
    public StringLoader(ModelLoader<Uri, Data> uriLoader) {
        this.uriLoader = uriLoader;
    }

    @Override
    public LoadData<Data> buildLoadData(@NonNull String model, int width, int height,
                                        @NonNull Options options) {
        Uri uri = parseUri(model);
        if(uri == null || ! uriLoader.handles(uri)) {return null;
        }
        returnuriLoader.buildLoadData(uri, width, height, options); }... /** * Factoryforloading {@link InputStream}s from Strings. */ public static class StreamFactory implements ModelLoaderFactory<String, InputStream> { @NonNull @Override public ModelLoader<String, InputStream> build(@nonnull MultiModelLoaderFactory multiFactory) {// Here's the keyreturn new StringLoader<>(multiFactory.build(Uri.class, InputStream.class));
        }

        @Override
        public void teardown() {
            // Do nothing.
        }
    }
}
Copy the code

Obvious can see StringLoader. StreamFactory created StringLoader (implementation ModelLoader interface), and in getLoadData () to obtain a list, Call ModelLoader’s buildLoadData method to build the LoadData, The StringLoader’s buildLoadData method will create a ModelLoader for StringLoader via URi. class and inputStream. class, so the loading function of StringLoader is transferred. And according to the registration relationship know to move to HttpUriLoader. HttpUriLoader, like StringLoader, transfers the loading function to HttpGlideUrlLoader. While HttpGlideUrlLoader. BuildLoadData corresponding LoadData Fetcher is HttpUrlFetcher.

// HttpUrlFetcher class: /** * A network data grabber, generally speaking, is to download images from a server, */ @override public void loadData(@nonnull Priority Priority, @nonnull DataCallback<? super InputStream> callback) { long startTime = LogTime.getLogTime(); Try {// use redirection to load data, InputStream result = loadDataWithRedirects(glideurl.tourl (), 0, null, glideurl.getheaders ()); Callback.ondataready (result); } catch (IOException e) { ... } } private InputStream loadDataWithRedirects(URL url, int redirects , URL lastUrl,Map<String,String> headers) throws IOException {// Too many redirectsif (redirects >= MAXIMUM_REDIRECTS) {
            throw new HttpException("Too many (> " + MAXIMUM_REDIRECTS + ") redirects!");
        } else{// Using the equals method of the URL causes network IO overhead, Usually there is a problem / / try {can refer to http://michaelscharf.blogspot.com/2006/11/javaneturlequals-and-hashcode-make.html.if(lastUrl ! = null && url.toURI().equals(lastUrl.toURI())) { throw new HttpException("In re-direct loop"); }} catch (URISyntaxException e) {// Do nothing, this is the best effort.}} UrlConnection = connectionFactory.build(URL);for (Map.Entry<String, String> headerEntry : headers.entrySet()) {
            urlConnection.addRequestProperty(headerEntry.getKey(), headerEntry.getValue());
        }
        urlConnection.setConnectTimeout(timeout);
        urlConnection.setReadTimeout(timeout);
        urlConnection.setUseCaches(false);
        urlConnection.setDoInput(true);

        urlConnection.setInstanceFollowRedirects(false);
        urlConnection.connect();
        stream = urlConnection.getInputStream();
        if (isCancelled) {
            return null;
        }
        final int statusCode = urlConnection.getResponseCode();
        if (isHttpOk(statusCode)) {
            return getStreamForSuccessfulRequest(urlConnection);
        } else if (isHttpRedirect(statusCode)) {
            String redirectUrlString = urlConnection.getHeaderField("Location");
            if (TextUtils.isEmpty(redirectUrlString)) {
                throw new HttpException("Received empty or null redirect url");
            }
            URL redirectUrl = new URL(url, redirectUrlString);
            // Closing the stream specifically is required to avoid leaking ResponseBodys in 
            // addition
            // to disconnecting the url connection below. See # 2352.
            cleanup();
            return loadDataWithRedirects(redirectUrl, redirects + 1, url, headers);
        } else if (statusCode == INVALID_STATUS_CODE) {
            throw new HttpException(statusCode);
        } else{ throw new HttpException(urlConnection.getResponseMessage(), statusCode); }}Copy the code

Seeing this, we finally found the code for network communication, which is to get the data stream InputStream and return it via HttpUrlConnection. Of course, you can customize OkHttp.

Here’s what you might do next (ha ha, I did it at the beginning) :

6. Data source processing and display

Callback.ondataready (result) is called when the data has been loaded. Callback is called when the data has been loaded. We start loading network data from the SourceGenerator, and the callback should be the SourceGenerator, so let’s look at the onDataReady(result) method of the SourceGenerator:

@Override public void onDataReady(Object data) { DiskCacheStrategy diskCacheStrategy = helper.getDiskCacheStrategy(); // Return if the data is not empty, or if the request should cache the original unmodified datatrue.if(data ! = null && diskCacheStrategy. IsDataCacheable (loadData. Fetcher. GetDataSource ())) {/ / if this piece of data is cached dataToCache = data; // Reload the cb.reschedule(); }else{// not cache, Cb is move this step. OnDataFetcherReady (loadData. SourceKey, data, the loadData fetcher, loadData. The fetcher. GetDataSource (), originalKey); }}Copy the code

Who’s cb here? We are initializing the SourceGenerator from our DecodeJob, so cb is our DecodeJob. Since we are not using the cache but the network, let’s look at the onDataFetcherReady method for our DecodeJob:

// DecodeJob: @override public void onDataFetcherReady(KeysourceKey, Object data, DataFetcher<? > fetcher, DataSource dataSource, Key attemptedKey) { this.currentSourceKey =sourceKey; CurrentData = data; This.currentfetcher = fetcher; This. currentDataSource = dataSource; / / data source: the url for the REMOTE type of enumeration, said from the REMOTE access this. CurrentAttemptingKey = attemptedKey;if(Thread.currentThread() ! = currentThread) {runReason = runReason.DECODE_DATA; // Re-execute the load callback.reschedule(this); }else {
            GlideTrace.beginSection("DecodeJob.decodeFromRetrievedData"); Try {// Call decodeFromRetrievedData decodeFromRetrievedData(); } finally { GlideTrace.endSection(); } } } private voiddecodeFromRetrievedData() {... Resource<R> resource = null; Resource resource = decodeFromData(currentFetcher, currentData, currentData); } catch (GlideException e) { e.setLoggingDetails(currentAttemptingKey, currentDataSource); throwables.add(e); }if(resource ! = null) {// notifyEncodeAndRelease(Resource, currentDataSource); }else{// If resource is null, then reload runGenerators(); } } private <Data> Resource<R> decodeFromData(DataFetcher<? > fetcher, Data data, DataSource dataSource) throws GlideException { try {if (data == null) {
                returnnull; } long startTime = LogTime.getLogTime(); Resource<R> result = decodeFromFetcher(data, dataSource);if (Log.isLoggable(TAG, Log.VERBOSE)) {
                logWithTimeAndKey("Decoded result " + result, startTime);
            }
            returnresult; } finally {// Close the stream and urlConnection. fetcher.cleanup(); } } @SuppressWarnings("unchecked") private <Data> Resource<R> decodeFromFetcher(Data data, DataSource DataSource) throws GlideException {LoadPath LoadPath<Data,? , R> path = decodeHelper.getLoadPath((Class<Data>) data.getClass()); // Parse the data through the parserreturnrunLoadPath(data, dataSource, path); } private <Data, ResourceType> Resource<R> runLoadPath(Data data, DataSource dataSource, LoadPath<Data, ResourceType, R > path) throws GlideException {/ / through hardware configuration Options for the Options Options = getOptionsWithHardwareConfig (dataSource); // Get a data regenerator based on the data type. The data obtained is an InputStream, so it is an instance of InputStreamRewinder"Note: DataRewinder is used to reset the wrapped data stream to its original location and return."DataRewinder<Data> rewinder = glideContext.getRegistry().getRewinder(data); Try {// // Moves the task of resolving the resource to the loadpath.load methodreturnpath.load( rewinder, options, width, height, new DecodeCallback<ResourceType>(dataSource)); } finally { rewinder.cleanup(); }}Copy the code

Retrieveddata –> decodeFromData–> decodeFromFetcher–> runLoadPath–> Load goes into the loadpath. load method to parse the data. This process builds a LoadPath and then creates a DataRewinder of type InputStreamRewinder.

Let’s see how LoadPath comes from.

<Data> LoadPath<Data, ? , Transcode> getLoadPath(Class<Data> dataClass) {return glideContext.getRegistry().getLoadPath(dataClass, resourceClass, transcodeClass);
    }

    @Nullable
    public <Data, TResource, Transcode> LoadPath<Data, TResource, Transcode> getLoadPath(
            @NonNull Class<Data> dataClass, @NonNull Class<TResource> resourceClass,
            @NonNull Class<Transcode> transcodeClass) {
        LoadPath<Data, TResource, Transcode> result =
                loadPathCache.get(dataClass, resourceClass, transcodeClass);
        if (loadPathCache.isEmptyLoadPath(result)) {
            return null;
        } else if(result == null) {// If null // get DecodePath"Note: DecodePath is similar to LoadData from the Registry Registry, which resolves the class"
            List<DecodePath<Data, TResource, Transcode>> decodePaths =
                    getDecodePaths(dataClass, resourceClass, transcodeClass);
            if (decodePaths.isEmpty()) {
                result = null;
            } else{// create a LoadPaht result = new LoadPath<>(dataClass, resourceClass, transcodeClass, decodePaths, throwableListPool); // Create a LoadPaht result = new LoadPath (dataClass, resourceClass, transcodeClass, decodePaths, throwableListPool); } // put into cache loadPathCache.put(dataClass, resourceClass, transcodeClass, result); }return result;
    }
Copy the code

If it is null, get a DecodePath set first. When we create Glide, we use the various parse methods of Append in Registry. GetDecodePaths is to get the corresponding parse class according to the parameters we pass in. Then create a LoadPath, pass in the newly created DecodePath, and put it into the cache.

Next we go to loadpath.load:

/ / LoadPath class:  public Resource<Transcode> load(DataRewinder<Data> rewinder, @NonNull Options options, int width, int height, DecodePath.DecodeCallback<ResourceType> decodeCallback) throws GlideException { List<Throwable> throwables = Preconditions.checkNotNull(listPool.acquire()); try {returnloadWithExceptionList(rewinder, options, width, height, decodeCallback, throwables); } finally { listPool.release(throwables); } } private Resource<Transcode> loadWithExceptionList(DataRewinder<Data> rewinder, @NonNull Options options,int width, int height,DecodePath.DecodeCallback<ResourceType> decodeCallback, List<Throwable> exceptions) throws GlideException { Resource<Transcode> result = null; // Try to iterate through all decodePath to decodefor(int i = 0, size = decodePaths.size(); i < size; i++) { DecodePath<Data, ResourceType, Transcode> path = decodePaths.get(i); Result = path.decode(rewinder, width, height, options, decodeCallback); } catch (GlideException e) { exceptions.add(e); }if(result ! = null) {break; }}if(result == null) { throw new GlideException(failureMessage, new ArrayList<>(exceptions)); } // Break out of the loopreturnresult; } // DecodePath class:  public Resource<Transcode> decode(DataRewinder<DataType> rewinder, int width , Int height, @nonnull Options Options,DecodeCallback<ResourceType> callback) throws GlideException {// Call decodeResource Resource<ResourceType> decoded = decodeResource(rewinder, width, height, options); . / / call DecodeCallback onResourceDecoded processing resources, processing into a Drawable) among the Resource < ResourceType > transformed = callback.onResourceDecoded(decoded); / / call ResourceTranscoder. Transcode middle resources into the Target (Target)returntranscoder.transcode(transformed, options); } @NonNull private Resource<ResourceType> decodeResource(DataRewinder<DataType> rewinder , int width,int height, @NonNull Options options) throws GlideException { List<Throwable> exceptions = Preconditions.checkNotNull(listPool.acquire()); Try {// Call decodeResourceWithListreturndecodeResourceWithList(rewinder, width, height, options, exceptions); } finally { listPool.release(exceptions); } } @NonNull private Resource<ResourceType> decodeResourceWithList(DataRewinder<DataType> rewinder ,int width,int height, @NonNull Options options,List<Throwable> exceptions) throws GlideException { Resource<ResourceType> result = null; // Iterate through the set of decoders to get the ResourceDecoder (including BitmapDrawableDecoder, G)ifFrameResourceDecoder //, FileDecoder, etc.), then get the InputStream via rewinder.rewindandget (), then call the decoder.decode methodfor (int i = 0, size = decoders.size(); i < size; i++) {
            ResourceDecoder<DataType, ResourceType> decoder = decoders.get(i);
            try {
                DataType data = rewinder.rewindAndGet();
                if(decoder.handles(data, options)) { data = rewinder.rewindAndGet(); InputStream result = decoder.decode(data, width, height, options); } } catch (IOException | RuntimeException | OutOfMemoryError e) {if (Log.isLoggable(TAG, Log.VERBOSE)) {
                    Log.v(TAG, "Failed to decode data for " + decoder, e);
                }
                exceptions.add(e);
            }

            if(result ! = null) {break; }}if (result == null) {
            throw new GlideException(failureMessage, new ArrayList<>(exceptions));
        }
        return result;
    }
Copy the code

From the above code, the data parsing task is finally performed through DecodePath, which has three internal operations:

  • Call decodeResource to parse the source InputStream into a Bitmap
  • Call DecodeCallback. OnResourceDecoded processing resources (CenterCrop, FitCenter, etc.)
  • Call ResourceTranscoder. Transcode to target resources (BitmapDrawable)

Let’s look at each of these operations separately:

Since the source data of this process is InputStream, its parser is StreamBitmapDecoder:

@Override
    public Resource<Bitmap> decode(@NonNull InputStream source, int width, int height,
                                   @NonNull Options options)
            throws IOException {

        final RecyclableBufferedInputStream bufferedStream;
        final boolean ownsBufferedStream;
        if (source instanceof RecyclableBufferedInputStream) {
            bufferedStream = (RecyclableBufferedInputStream) source;
            ownsBufferedStream = false;
        } else {
            bufferedStream = new RecyclableBufferedInputStream(source, byteArrayPool);
            ownsBufferedStream = true; } // to retrieve the exception thrown on the read. ExceptionCatchingInputStream exceptionStream = ExceptionCatchingInputStream.obtain(bufferedStream); // Used to read data. Make sure we can always reset the image title after reading it, so that even if the header decoding fails or overflows, our read buffer can still try to decode the entire image. MarkEnforcingInputStream invalidatingStream = new MarkEnforcingInputStream(exceptionStream); UntrustedCallbacks callbacks = new UntrustedCallbacks(bufferedStream, exceptionStream); }}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}return downsampler.decode(invalidatingStream, width, height, options, callbacks);
        } finally {
            exceptionStream.release();
            if(ownsBufferedStream) { bufferedStream.release(); }}}Copy the code

You can see that it collects the Bitmap of the stream internally by using the downSampler.decode method (the sampling strategy we passed in when we built the Request).

Let’s take a look at how we handle the Resource once we have it:

Can be seen from the source of resources processing calls the callback. OnResourceDecoded (decoded) processing resources, and the callback is actually our DecodeJob. DecodeCallback the callback class:

/ / DecodeJob. DecodeCallback class: @nonnull @override public Resource<Z> onResourceDecoded(@nonnull Resource<Z> decoded) {// Call the onResourceDecoded method of the external classreturnDecodeJob.this.onResourceDecoded(dataSource, decoded); } // DecodeJob class:  @Synthetic @NonNull <Z> Resource<Z> onResourceDecoded(DataSource dataSource, @nonnull Resource<Z> decoded) {// Get the data Resource type @suppresswarnings ("unchecked") Class<Z> resourceSubClass = (Class<Z>) decoded.get().getClass(); Transformation<Z> appliedTransformation = null; Resource<Z> transformed = decoded; // If the data source is not obtained from the resource disk cache, the resource is transformedif(dataSource ! = datasource.resource_disk_cache) {// Get the converter by type (for example: CenterCrop, FitCenter) appliedTransformation = decodeHelper. GetTransformation (resourceSubClass); transformed = appliedTransformation.transform(glideContext, decoded, width, height); } // TODO: Make this the responsibility of the Transformation.if(! decoded.equals(transformed)) { decoded.recycle(); } // Final EncodeStrategy EncodeStrategy; final ResourceEncoder<Z> encoder;if (decodeHelper.isResourceEncoderAvailable(transformed)) {
            encoder = decodeHelper.getResultEncoder(transformed);
            encodeStrategy = encoder.getEncodeStrategy(options);
        } else{ encoder = null; encodeStrategy = EncodeStrategy.NONE; <Z> result = transformed; boolean isFromAlternateCacheKey = ! decodeHelper.isSourceKey(currentSourceKey);if (diskCacheStrategy.isResourceCacheable(isFromAlternateCacheKey, dataSource,
                encodeStrategy)) {
            if (encoder == null) {
                throw new Registry.NoResultEncoderAvailableException(transformed.get().getClass());
            }
            final Key key;
            switch (encodeStrategy) {
                caseSOURCE: // Key of SOURCE data key = New DataCacheKey(currentSourceKey, signature);break;
                case TRANSFORMED:
                    key = new ResourceCacheKey(
                                    decodeHelper.getArrayPool(),
                                    currentSourceKey,
                                    signature,
                                    width,
                                    height,
                                    appliedTransformation,
                                    resourceSubClass,
                                    options);
                    break;
                default:
                    throw new IllegalArgumentException("Unknown strategy: "+ encodeStrategy); LockedResource<Z> lockedResult = LockedResource. Obtain (transformed); deferredEncodeManager.init(key, encoder, lockedResult); result = lockedResult; } // Return the bitmap after the transformreturn result;
    }
Copy the code

In the onResourceDecoded method, the main logic varies according to the parameters we set, that is, if we use parameters like centerCrop, it will be processed here. Transformation is an interface, and a series of its implementations correspond to parameters such as scaleType. For example, CenterCrop, FitCenter, CenterInside and so on are all scaling converters that implement Transformation interface.

Since our target format is Drawable, its converter is BitmapDrawableTranscoder, While ResourceTranscoder transcode this three-step actually use BitmapDrawableTranscoder transcode () method returns the Resouces.

Thus, when these three methods are completed, our fork in the road method will be analyzed. And then it’s just going to keep going up return. So, we go back to the decodeFromRetrievedData() method of DecodeJob as follows:

    private void decodeFromRetrievedData() {... Resource<R> resource = null; Resource resource = decodeFromData(currentFetcher, currentData, currentData); } catch (GlideException e) { e.setLoggingDetails(currentAttemptingKey, currentDataSource); throwables.add(e); }if(resource ! = null) {// notifyEncodeAndRelease(Resource, currentDataSource); }else{// If resource is null, then reload runGenerators(); }}Copy the code

At this point the decodeFromData method is completely finished and notifyEncodeAndRelease follows:

private void notifyEncodeAndRelease(Resource<R> resource, DataSource dataSource) { ...... // The notifyComplete(result, dataSource) is ready; . Try {// 2. Cache data to diskif(deferredEncodeManager.hasResourceToEncode()) { deferredEncodeManager.encode(diskCacheProvider, options); } } finally { ... } } private Callback<R> callback; private void notifyComplete(Resource<R> resource, DataSource dataSource) { ...... // We know the DecodeJob Callback is EngineJob Callback. onResourceReady(resource, dataSource); }Copy the code

This method consists of two steps: 1. Notify the resource is ready 2. Cache data to disk. Let’s focus on callback. OnResourceReady in notifyComplete:

/ / EngineJob class:  @Override public void onResourceReady(Resource<R> resource, DataSource dataSource) { synchronized (this) { this.resource = resource; this.dataSource = dataSource; } notifyCallbacksOfResult(); } voidnotifyCallbacksOfResult() {
        ResourceCallbacksAndExecutors copy;
        Key localKey; EngineResource<? >localResource; synchronized (this) { ...... engineResource = engineResourceFactory.build(resource, isCacheable); // The duration of our callback below holds the resource, so we don't reclaim whether it was released synchronously by one of the callbacks in the notification. // Get it under the lock so that any newly added callback performed under the next lock section does not reclaim the resource before we call the callback. hasResource =true;
            copy = cbs.copy();
            incrementPendingCallbacks(copy.size() + 1);

            localKey = key;
            localResource = engineResource; } / / 1. Notice the upper Engine task completed the listener. OnEngineJobComplete (this,localKey, localResource); // 2. Call back to ImageViewTarget to display the resourcefor(final ResourceCallbackAndExecutor entry : copy) { entry.executor.execute(new CallResourceReady(entry.cb)); } // Engine: @override public synchronized void onEngineJobComplete(EngineJob<? > engineJob, Key key, EngineResource<? > resource) {if(resource ! = null) { resource.setResourceListener(key, this); // Add the loaded resource to the memory cacheif (resource.isCacheable()) {
                activeResources.activate(key, resource);
            }
        }

        jobs.removeIfCurrent(key, engineJob);
    }

private class CallResourceReady implements Runnable {

    private final ResourceCallback cb;

    CallResourceReady(ResourceCallback cb) {
        this.cb = cb;
    }

    @Override
    public void run() {
        synchronized (EngineJob.this) {
            if(cbs.contains(cb)) { engineResource.acquire(); / / call resources ready callback callCallbackOnResourceReady (cb); removeCallback(cb); } decrementPendingCallbacks(); } // EngineJob class:  @Synthetic synchronized void callCallbackOnResourceReady(ResourceCallback cb) { try { // Call back the onResourceReady method in SingleRequest cb.onResourceReady(engineResource, dataSource); } catch (Throwable t) { throw new CallbackException(t); }} @override public synchronized void onResourceReady(Resource<? > resource, DataSource dataSource) { ...... OnResourceReady ((Resource<R>) Resource, (R) received, dataSource); } private synchronized void onResourceReady(Resource<R> resource, R result, DataSource dataSource) { ... isCallingCallbacks =true;
        try {
            boolean anyListenerHandledUpdatingTarget = false;
            if(requestListeners ! = null) {for(RequestListener<R> listener : requestListeners) { anyListenerHandledUpdatingTarget |= listener.onResourceReady(result, model, target, dataSource, isFirstResource); } } anyListenerHandledUpdatingTarget |= targetListener ! = null && targetListener.onResourceReady(result, model, target, dataSource, isFirstResource);if(! anyListenerHandledUpdatingTarget) { Transition<? super R> animation = animationFactory.build(dataSource, isFirstResource); Target. OnResourceReady (result, animation); } } finally { isCallingCallbacks =false;
        }

        notifyLoadSuccess();
    }
Copy the code

This code is a little too much and a little messy, so I’m going to say it step by step:

  1. EngineJob. OnResourceReady method calls the notifyCallbacksOfResult continue to inform the callbacks and results in this class
  2. NotifyCallbacksOfResult method does two things: 1. 2. Call back to the ImageViewTarget to display the resource.
  3. The onEngineJobComplete method of the Engine class is called to add the loaded resource to the memory cache. At this point, both disk and memory cache are added
  4. The 2.2 callback ImageViewTarget in Step 2 displays the resource by first iterating through the run method that executes CallResourceReady(type Runnable) in the EngineJob class
  5. Step 4 CallResourceReady (Runnable type) in the run method of continue callCallbackOnResourceReady method, from the name “ready to dial the call resources” can be seen, the display resources.
  6. Step 5, EngineJob callCallbackOnResourceReady method of class, the callback SingleRequest onResourceReady method of a class
  7. The onResourceReady method of the SingleRequest class is called again in its onResourceReady method
  8. You finally see the target.onResourceready (Result, animation) step in the overloaded onResourceReady method in step 7.
  9. The target in step 8 is actually the ImageViewTarget. The specific ImageViewTarget call onResourceReady to display the resource is referred to in the next section “Glide Target”.

Glide at this point, finally a complete request to show that the analysis has been completed, but this is just Glide’s most basic process, and Glide supports Gif, video loading, binary, Assets and other operations, you can imagine its internal processing of how much logic code, such a complex process, nested so many callbacks, It’s scary to think that the final part of parsing resources is mostly borrowed, mainly because its internal implementation is really complicated.

Now that the process is complete, recall from the sequence diagram:

The Target of Glide

  • What is Target?
  • What implementation classes does Target have?
  • What does Target do?

Before talking about the above three issues, let’s think about Glide’s behavior on ImageView.

1. Obtain the size of ImageView 2. Schedule whether to reload or cancel the image loading according to the life cycle of ImageView 3. ImageView the default image displayed during load, the image displayed when load fails, whether Bitmap or Drawable is displayed when load succeeds, and the action effectsCopy the code

According to the single responsibility principle in the design pattern, the Request handles only the image loading logic; Target is the interface that handles the related behavior associated with the ImageView, which answers the first question.

Let’s look at the main related class diagram relationships for Target:

  • BaseTarget: This is an abstract class whose value defines a Request member variable that holds the associated image-loading Request, making it easy to load the image when the ImageView is bound to the window or to cancel the image loading when it is unloaded from the window.
  • ViewTarget: It does two things: a, get the size of the ImageView and b, listen to the binding relationship with the Window
A, get a View with a size of getWidth(), layoutparam.width, and no size when the View is not drawn. The size can be obtained by using the Activity's onWindowFocusChanged or ViewTreeObserver to monitor when the View is drawn and then calling getWidth. Glide is less likely to be acquired via onWindowFocusChanged, only ViewTreeObserver is left, so Glide is acquired via ViewTreeObserver. / / ViewTarget class: Public void getSize(@nonnull SizeReadyCallback cb) {// Call a member iterating over sizesize.getSize (cb) of sizeDeterminer; } / / ViewTarget SizeDeterminer class: Void getSize(@nonnull SizeReadyCallback cb) {// Get the width of the current view int currentWidth = getTargetWidth(); Int currentHeight = getTargetHeight();if(isViewStateAndSizeValid(currentWidth, currentHeight)) {// If the size of the View is greater than 0 // the callback tells the size of the View cb. OnSizeReady (currentWidth, currentHeight) currentHeight);return;
        }

        if(! Cbs.contains (cb)) {// Add the size observer cbs.add(cb); }if(layoutListener == null) {// get ViwTreeObserver ViewTreeObserver observer = view.getViewTreeObserver(); layoutListener = new SizeDeterminerLayoutListener(this); / / to monitor the View preDraw behavior observer. AddOnPreDrawListener (layoutListener); } // Get width private intgetTargetWidth() { int horizontalPadding = view.getPaddingLeft() + view.getPaddingRight(); LayoutParams layoutParams = view.getLayoutParams(); int layoutParamSize = layoutParams ! = null ? layoutParams.width : PENDING_SIZE;returngetTargetDimen(view.getWidth(), layoutParamSize, horizontalPadding); } private Boolean isViewStateAndSizeValid(int width, int height) {returnisDimensionValid(width) && isDimensionValid(height); } // Determine whether the specified size is greater than 0 or == integer.max_value private Boolean isDimensionValid(int size) {returnsize > 0 || size == SIZE_ORIGINAL; } / / ViewTarget SizeDeterminerLayoutListener categories: public BooleanonPreDraw() {
        SizeDeterminer sizeDeterminer = sizeDeterminerRef.get();
        if(sizeDeterminer ! = null) { sizeDeterminer.checkCurrentDimens(); }return true; } / / ViewTarget SizeDeterminer categories: voidcheckCurrentDimens() {
        if (cbs.isEmpty()) {
            return; } // Get width int currentWidth = getTargetWidth(); Int currentHeight = getTargetHeight();if(! IsViewStateAndSizeValid (currentWidth, currentHeight)) {// If the width and height are less than 0, the view has not been measuredreturn; } notifyCbs(notiftwidth, currentHeight); / / remove the listener clearCallbacksAndListener (); } onSizeReady onSizeReady onSizeReady onSizeReady onSizeReady onSizeReady onSizeReady onSizeReady Otherwise, the ViewTreeObserver listens to the onPreDraw behavior of the View to get the size of the View and tells the listener that the size of the View has been measured. B. Listen to the binding relationship between view and Window. By listening to the binding relationship between view and Window, the image loading is scheduled to initiate the loading request or cancel the loading request. Public final ViewTarget<T, Z>clearOnDetach() {
        if(attachStateListener ! = null) {returnthis; } attachStateListener = newOnAttachStateChangeListener() {@override public void onViewAttachedToWindow(View v) {// bind to Window resumeMyRequest(); } @override public void onViewDetachedFromWindow(View v) {// Unbind pauseMyRequest() from window; }}; maybeAddAttachStateListener();returnthis; } // Set the listener of the view bound to the window state private voidmaybeAddAttachStateListener() {
        if (attachStateListener == null || isAttachStateListenerAdded) {
            return; } / / add binding state the eavesdroppers addOnAttachStateChangeListener (attachStateListener); isAttachStateListenerAdded =true;
    }

    @Synthetic
    void resumeMyRequestRequest request request = getRequest();if(request ! = null && request.iscleared () {// Start the request to load request.begin(); } } @SuppressWarnings("WeakerAccess")
    @Synthetic
    void pauseMyRequest() {// get the image load object when untying the window request request request = getRequest();if(request ! = null) { isClearedByUs =true; Request.clear (); isClearedByUs =false; }}Copy the code
  • ImageViewTarget:
// a, set the original image resource to null, Public void onLoadStarted(@nullable Drawable placeHolder){super.onloadStarted (placeHolder);setResourceInternal(null);
        setDrawable(placeholder);
    }

    public void setDrawable(Drawable drawable){ view.setImageDrawable(drawable); } // b, set the image to display when loading failed, set the original image resource to empty, Public void onLoadFailed(@nullable Drawable errorDrawable) { super.onLoadFailed(errorDrawable);setResourceInternal(null);
        setDrawable(errorDrawable); } // when the image is loaded successfully, if there is no action call, the image is loaded successfullysetResource sets the image Resource that was loaded successfully, // andsetResource is an abstract method, which is implemented in DrawableImageViewTarget and BitmapImageVIewTarget. If there is an action, use maybeUpdateAnimatable to implement the action logic. public void onResourceReady(@NonNull Z resource, @Nullable Transition<? super Z> transition) {if(transition == null || ! Transition.transition (resource, this)) {// No animation // callsetResourceInternal Sets the successfully loaded imagesetResourceInternal(resource);
        } else{// maybeUpdateAnimatable(resource); } } private voidsetResourceInternal(@nullable Z resource) {// callsetResource Sets the image ResourcesetResource(resource); MaybeUpdateAnimatable (resource); } protected abstract voidsetResource(@Nullable Z resource); // select * from Animatable; // select * from Animatable; // select * from Animatable; private void maybeUpdateAnimatable(@Nullable Z resource) {if (resource instanceof Animatable) {
            animatable = (Animatable) resource;
            animatable.start();
        } else{ animatable = null; }}Copy the code
  • BitmapImageViewTarget: set the image resources in the form of Bitmap, in the analysis of the ImageViewTarget specified that the image resources are set in the subclass setResource to achieve.
    protected void setResource(Bitmap resource) {
        view.setImageBitmap(resource);
    }
Copy the code
  • DrawableImageViewTarget: Sets the image resource in Drawable form, same as BitmapImageViewTarget
    protected void setResource(@Nullable Drawable resource) {
        view.setImageDrawable(resource);
    }
Copy the code

It’s a Glide

[4. Into (T) (i.e. RequestBuilder.into(T))] we know that the into(imageView) method configures the scaling options of the current imageView based on the scaleType of the current imageView. To use the CenterCrop effect as an example, first look at a configuration scaling effect: requestOptions.clone().optionalCenterCrop();

/ / BaseRequestOptions class: // This object internally maintains an ArrayMap of type CachedHashCodeArrayMap(a subclass of ArrayMap) to store option configurations (applied to memory and disk cache keys) private Options Options = new Options();  private Map<Class<? >, Transformation<? >> transformations = new CachedHashCodeArrayMap<>(); @NonNull @CheckResult public ToptionalCenterCrop() {// DownsampleStrategy describes the strategy for downsampling compression // Initialize the CenterCrop object to describe how the image changesreturn optionalTransform(DownsampleStrategy.CENTER_OUTSIDE, new CenterCrop());
    }

    @SuppressWarnings({"WeakerAccess"."CheckResult"})
    @NonNull
    final T optionalTransform(@NonNull DownsampleStrategy downsampleStrategy,
                              @NonNull Transformation<Bitmap> transformation) {
        if (isAutoCloneEnabled) {
            return clone().optionalTransform(downsampleStrategy, transformation); } // Add the downsampleStrategy to options downsample(downsampleStrategy); // Add the changes in the images to the translationsreturn transform(transformation, /*isRequired=*/ false);
    }

    @NonNull
    @CheckResult
    public T downsample(@NonNull DownsampleStrategy strategy) {
        return set(DownsampleStrategy.OPTION, Preconditions.checkNotNull(strategy));
    }

    @NonNull
    @CheckResult
    public <Y> T set(@NonNull Option<Y> option, @NonNull Y value) {
        if (isAutoCloneEnabled) {
            return clone().set(option, value); } Preconditions.checkNotNull(option); Preconditions.checkNotNull(value); options.set(option, value); // Save to optionsreturn selfOrThrowIfLocked();
    }

    @NonNull
    T transform(
            @NonNull Transformation<Bitmap> transformation, boolean isRequired) {
        if (isAutoCloneEnabled) {
            return clone().transform(transformation, isRequired); } DrawableTransformation DrawableTransformation DrawableTransformation = new DrawableTransformation(transformation, isRequired); Transform (bitmap.class, transformation, isRequired); // Transform (bitmap.class, transformation, isRequired); transform(Drawable.class, drawableTransformation, isRequired); transform(BitmapDrawable.class, drawableTransformation.asBitmapDrawable(), isRequired); transform(GifDrawable.class, new GifDrawableTransformation(transformation), isRequired);
        return selfOrThrowIfLocked();
    }

    @NonNull
    <Y> T transform(
            @NonNull Class<Y> resourceClass,
            @NonNull Transformation<Y> transformation,
            boolean isRequired) {
        if (isAutoCloneEnabled) {
            return clone().transform(resourceClass, transformation, isRequired); } Preconditions.checkNotNull(resourceClass); Preconditions.checkNotNull(transformation); // You've added a doubling in the doubling cache. Put (resourceClass, transformation); .return selfOrThrowIfLocked();
    }

Copy the code

You can see that in addition to the image change operation, the zoom configuration also sets the sampling mode, which is saved in doubling and options, respectively.

Several new classes appear in this code:

  • Transformations cache
  • Transformation
  • DrawableTransformation
  • BitmapTransformation(New CenterCrop() inherited from BitmapTransformation)

What does Transformation do?

Usually in development, we often will be loaded on the network image processing, such as Glide with centerCrop(), fitCenter() processing, custom circle, rounded corners, blur and so on are completed through Transformation.Copy the code

Let’s take a look at the source code for CenterCrop:

public class CenterCrop extends BitmapTransformation {
    private static final String ID = "com.bumptech.glide.load.resource.bitmap.CenterCrop"; private static final byte[] ID_BYTES = ID.getBytes(CHARSET); // Realize the graph transformation, @override protected Bitmap transform(@nonnull Bitmap pool pool, @nonnull Bitmap toTransform, int outWidth, int outHeight) {returnTransformationUtils.centerCrop(pool, toTransform, outWidth, outHeight); } // Override epquals and hashcode to ensure that the Object is unique from other image transformations @override public Boolean equals(Object o) {return o instanceof CenterCrop;
    }

    @Override
    public int hashCode() {
        returnID.hashCode(); } @override public void updateDiskCacheKey(@nonnull MessageDigest MessageDigest) { messageDigest.update(ID_BYTES); }}Copy the code
  1. CenterCrop inherits from BitmapTransformation, which is a must. Our custom Transformation will also inherit from this class. Because the entire image transformation function is based on this inheritance structure.
  2. The most important method is the transform() method. This is the key method of our custom Transformation, and our processing logic is implemented in this method. The transform() method takes four parameters.
    • This is the BitmapPool cache pool in Glide. It is used to reuse Bitmap objects, otherwise it would be very memory consuming to recreate Bitmap objects every time the image is transformed.
    • ToTransform, this is the Bitmap of the original image, and that’s what we’re going to do with the image transform.
    • The width of the image after transformation
    • The height of the image after transformation

We can see that the transform() processing is all in TransformationUtils, so let’s look at the details of the transform() method.

// Class TransformationUtils: public Bitmap centerCrop(@nonnull BitmapPool pool, @nonnull BitmapinBitmap, int width, int height) {// Simple checkif (inBitmap.getWidth() == width && inBitmap.getHeight() == height) {
            return inBitmap; } // From ImageView/Bitmap.createScaledBitmap. Calculate the scale of the canvas and the offset value finalfloat scale;
        final float dx;
        final float dy;
        Matrix m = new Matrix();
        if (inBitmap.getWidth() * height > width * inBitmap.getHeight()) {
            scale = (float) height / (float) inBitmap.getHeight();
            dx = (width - inBitmap.getwidth () * scale) * 0.5f; dy = 0; }else {
            scale = (float) width / (float) inBitmap.getWidth();
            dx = 0;
            dy = (height - inBitmap.getheight () * scale) * 0.5f; } m.setScale(scale, scale); M.p ostTranslate ((int) (dx + 0.5 f), (int) (dy + 0.5 f)); Bitmap result = pool.get(width, height, getNonNullConfig())inBitmap)); / / copies the original alpha value Bitmap object to tailor the Bitmap object above TransformationUtils. SetAlpha (inBitmap, result); // Crop the Bitmap object for drawing and return the final result to applyMatrix(inBitmap, result, m);
        return result;
    }
Copy the code

For equals, hashCode, and updateDiskCacheKey, the most important method is transform().

You can customize or use The Glide Library, which provides a wide range of transforms such as crop, color, blur, and so on. You can transforma wide variety of images very easily.

This analysis does not include Glide cache part, interested partners first their own understanding of it, it is a deep source like the sea!

Reference links:

www.jianshu.com/p/2f520af84…

Juejin. Im/post / 684490…

Blog.csdn.net/f409031mn/a…

Blog.csdn.net/weixin_3437…

www.jianshu.com/p/043c3c1e1…

Blog.csdn.net/ApkCore/art…

Blog.csdn.net/say_from_we…

Note: If there is any error, please correct me. Look forward to your likes!!