preface

Glide can be said to be the most commonly used picture loading frame, Glide chain call easy to use, performance can also meet the use of most scenarios, Glide source code and principle is a frequent interview. But Glide source content is more, want to learn its source code is often a thousand threads, can not grasp the point. This article to Glide to do what optimizations as a starting point, introduce and learn Glide source code and principles, if it helps you, welcome to like.

What optimizations have been made for Glide?

To answer this question, we can first think about what we would think if we were to implement an image loading framework ourselves. 1. Image download is a time-consuming process, the first thing we need to consider is the image cache problem 2. Image loading is also a memory consuming operation, and many OOM’s are caused by image loading, so we also need to consider memory optimization 3. When the image is halfway loaded, the page closes, and the image loading should stop, this again involves the problem of life cycle management 4. Also, does the image loading framework support large image loading? What’s the problem with the big picture?

The above is we put forward a few questions about Glide, so that we can easily get the main contents of this article includes 1.Glide image load overall process introduction 2.Glide cache mechanism to do what optimization? 3. What memory optimizations did Glide make? How does Glide manage the life cycle? 5. How to do Glide large image loading?

Below with the question into the text ~

1.GlideThis section describes the overall process of image loading

At the beginning of understandingGlideAnd before we do that, let’s look atGlidePicture loading overall process to do a simple introduction, let you first have an overall concept.

At the same time face backGlideDo the optimization of the specific step can also be convenient to know.

In summary, image loading includes encapsulation, parsing, downloading, decoding, transformation, caching, display and other operations, as shown in the figure below:

  • 1. Encapsulate parameters: There may be many processes between specifying the source and output results, so the first thing is to encapsulate parameters, which will run through the whole process;
  • 2. Parsing path: There are many sources of pictures and their formats are different, which need to be normalized;
  • 3. Read cache: To reduce computation, it is common to do cache. With the same request, get an image from the cache (Bitmap);
  • 4. Find/download files: If it is a local file, directly decode it; If the image is from the Internet, download it first.
  • 5. Decoding: This step is one of the most complicated in the process, with a lot of detail;
  • 6. Transform: Decode outBitmapAfter that, you may need to do some transformations (rounded corners, filters, etc.);
  • 7. Cache: After the final bitmap is obtained, it can be cached so that the next request can directly fetch the result;
  • 8. Display: Display the results, possibly with some animation (fade in, crossFade, etc.).

Glide image loading above is the overall process, here is only a brief introduction, details visible: talk about Glide in the interview those things

2.GlideWhat optimizations have been made to the caching mechanism?

As we know, downloading images is very resource-intensive, so the image caching mechanism is an important part of the image loading framework. Here is a table to explain Glide cache.

Cache type The cache on behalf of instructions
Activity in the cache ActiveResources If the current corresponding image resource was obtained from the memory cache, the image is stored in the active resource.
Memory cache LruResourceCache When the image has been parsed and recently loaded, it is put into memory
Disk cache – Resource type DiskLruCacheWrapper The decoded picture is written to a disk file
Disk cache – Raw data DiskLruCacheWrapper The original data is cached on disk after the network request succeeds

Before going into specific caches, let’s take a look at the order in which the load caches are executed to get a general idea



GlideCaching mechanism, mainly divided into two types of cache, one is memory cache, one is disk cache.

The memory cache is used to prevent the application from repeatedly reading images into memory, which can waste memory resources.

The reason for using disk caching is to prevent applications from repeatedly downloading and reading data from the network or elsewhere.

Because of the combination of these two caches, it is formedGlideExcellent cache effect.

2.1 Memory Cache

Glide is enabled by default and can also be disabled by skipMemoryCache. As you can see above, memory cache is actually divided into two parts: ActiveResource cache and LRU cache. Use ActiveResources to cache in-use images to protect these images from being recycled by the LruCache algorithm

The memory cache is loaded in the following order: 1. Generate key 2 based on the image address, width and height, transform, and signature. The active cache was not obtained on the first load. 3. Then load the memory cache, clean up the memory cache, and add the active cache. 4. The second load active cache already exists. 5. When the current image reference is 0, clean up the active resources and add the memory resources. 6. It’s back to the first step, and then it goes on and on.

It is summarized as the flowchart as follows:



The source code is not posted here. If you want to see the source code, please refer to:Glide cache strategy is analyzed from the perspective of source code

We’ve summarized the Glide memory cache loading process above, and it’s easy to wonder why Glide has two memory caches.

2.1.1 Why are two Types of memory caches designed?

The LruCache algorithm uses a LinkedHashMap to cache objects. Every time the cache is exceeded, the least recently used cache is removed. Therefore, it is possible that the cache that I am using is removed. Therefore, this weak reference may be a kind of protection for the image in use, which is removed from the LruCache when it is used, and then added back to the cache when it is used.

For example



For example, weLruMemory cachesizeSet up to hold 99 pictures on the slideRecycleViewIf we just slide to 100, then we will recycle the first one we have loaded out. At this time, if we return to slide to the first one, we will judge whether there is a memory cache. If there is no memory cache, we will open a new oneRequestRequest, obviously if you clean up the first image that’s not what we want. So when resource data is retrieved from the memory cache, it is actively added to the active resource, and the memory cache resource is cleared. The obvious benefit is to protect images that don’t want to be recycledLruCacheThe algorithm recycles and makes full use of the resources.

2.1.1 summary

This section mainly summarizes the Glide memory cache loading process 1. First to get the active cache, if the load is directly returned, not to go to the next step 2. The LRU cache is then fetched, and when fetched, it is removed from the LRU and added to the active cache 3. The next load can load the active cache directly 4. When the image reference is 0, it is cleared from the active cache and added to the LRU cache 5. The reason for designing two types of memory caches is to prevent images being loaded from being reclaimed by THE LRU

2.2 Disk Caching

First, look at the disk caching strategy

  • DiskCacheStrategy.NONE: indicates that no content is cached.
  • DiskCacheStrategy.RESOURCE: Data is written to disk cache after resource decoding, that is, image resources after scaling and other conversion.
  • DiskCacheStrategy.DATA: Writes raw data to the disk cache before decoding the resource.
  • DiskCacheStrategy.ALLUse:DATAandRESOURCECache remote data using onlyRESOURCETo cache local data.
  • DiskCacheStrategy.AUTOMATIC: It tries to use the best strategy for both local and remote images. When you load remote data,AUTOMATICThe policy will only store raw data that has not been modified by your loading process, because downloading remote data is much more expensive than adjusting data that already exists on disk. For local data,AUTOMATICThe strategy stores only the transformed thumbnails, because even if you need to generate another image of another size or type, it’s easy to get back the original data. This caching policy is used by default

In understanding the disk cache we mainly need to clear a concept, is when we use Glide to load an image, Glide default will not show the original picture, but the image compression and conversion, in short, after a series of operations to get the picture, called after the conversion of the picture. We can cache both the original image before transformation and the image after transformation

2.2.1 Why are two types of disk caches needed

Has been said above, DiskCacheStrategy. The RESOURCE cache is transformed resources, DiskCacheStrategy. The DATA cache’s resources before the transformation For example, with a picture, we first show, in View of 100 * 100 is If you do not cache the transformed type, you will have to perform a transformation operation each time. If you do not cache the original data, you will have to re-download the data each time. As can be seen below, the keys of the two caches are different

DiskCacheStrategy.RESOURCE
currentKey = newResourceCacheKey(helper.getArrayPool(),sourceId,helper.getSignature(),helper.getWidth(),helper.getHeight(),transformatio n,resourceClass,helper.getOptions()); DiskCacheStrategy.DATA DataCacheKey newOriginalKey =new DataCacheKey(loadData.sourceKey, helper.getSignature());
Copy the code

2.2.2 summary

This section mainly introduces several Glide disk cache strategies and introduces why two kinds of disk cache is needed. There is no source code here, if you want to see the source code of students can refer to: From the perspective of source code analysis Glide cache strategy

3.GlideWhat memory optimizations have been made?

Glide memory optimization is mainly for Bitmap optimization, before answering this question, we can think about what are the common Bitmap optimization means 1. InSampleSize can be used for sizing optimization when the image size is not consistent with the View size 2. Image memory is the width and height of each pixel memory size, different modes each pixel memory size is different, we can use inpreferredConfig configuration 3 Bitmpa memory is relatively large, if frequently create reclaim Bitmap memory may cause memory jitter, We can use inBitmap to use Bitmap memory 4. Memory cache, we have already described Glide’s weak reference cache and LRU cache

In fact, there are only a few common Bitmap memory optimizations, but we rarely use them directly in our work. Here’s how they are used in Glide.

3.1 Size optimization

When the image container such as ImageView is only 100*100 and the resolution of the image is 800 * 800, placing the image directly on the container is easy to OOM, and it is also a waste of image and memory resources. When the width and height of the container are very smaller than the width and height of the image, in fact, it is necessary to compress the size of the image, the resolution of the image is adjusted to the size of the ImageView width and height, on the one hand, it will not affect the quality of the image, but also can greatly reduce the memory occupation

We usually use inSampleSize to scale a Bitmap

If inSampleSize is set to a value greater than 1, then the decoder is asked to subsample the original bitmap image and return a smaller image to reduce memory usage. For example, inSampleSize == 4, then the sampled image is 1/4 of the original image width and 1/16 of the original image pixel value. That is to say, the memory of the sampled image is 1/16 of the memory of the original image; When inSampleSize <=1, it is treated as 1, which is the same size as the original image. The last sentence also states that the value of inSampleSize is always a power of 2, such as 1,2,4,8. Any other values are also rounded to the nearest power of two.

    / / 1
    int widthScaleFactor = orientedSourceWidth / outWidth;
    int heightScaleFactor = orientedSourceHeight / outHeight;
    / / 2
    int scaleFactor =
        rounding == SampleSizeRounding.MEMORY
            ? Math.max(widthScaleFactor, heightScaleFactor)
            : Math.min(widthScaleFactor, heightScaleFactor);

    int powerOfTwoSampleSize;
    / / 3
    if (Build.VERSION.SDK_INT <= 23
        && NO_DOWNSAMPLE_PRE_N_MIME_TYPES.contains(options.outMimeType)) {
      powerOfTwoSampleSize = 1;
    } else {
      / / 4
      powerOfTwoSampleSize = Math.max(1, Integer.highestOneBit(scaleFactor));
      / / 5
      if (rounding == SampleSizeRounding.MEMORY
      	  // exactScaleFactor is rewritten by each cropping policy such as CenterCrop. Details are visible in the code
          && powerOfTwoSampleSize < (1.f / exactScaleFactor)) {
        powerOfTwoSampleSize = powerOfTwoSampleSize << 1;
      }
    }
    options.inSampleSize = powerOfTwoSampleSize;
Copy the code

The above is the Glide image size scaling related code 1. First calculate the image and View of the aspect ratio 2. According to the scaling strategy is memory saving or high quality, decide to take the maximum or minimum aspect ratio of 3. When build.version.sdk_int <=23, some formats of the image cannot be scaled 4. The highestOneBit function is to round the ratio we calculate to the nearest power of 2, 5. If the scaling strategy is memory saving and we calculate SampleSize

The above is the Glide image load to do size optimization logic

3.2 Image format optimization

As we know, the memory size of Bitmap is determined by width * height * memory size per pixel. The above size optimization determines the width and height, and the image format optimization determines the memory size per pixel

In API29, Bitmap is divided into ALPHA_8, RGB_565, ARGB_4444, ARGB_8888, RGBA_F16, and HARDWARE.

  • ALPHA_8: Does not store color information, each pixel occupies 1 byte;
  • RGB_565: only storesRGBChannel, 2 bytes per pixel, rightBitmapColor is not high demand, can use this mode;
  • ARGB_4444Deprecated, in useARGB_8888Instead of;
  • ARGB_8888: Each pixel takes 4 bytes to maintain high quality color fidelity. This mode is used by default.
  • RGBA_F16: Each pixel takes 8 bytes, suitable for a wide gamut andHDR;
  • HARDWARE: a special configuration that reduces memory usage and speeds it upBitmapIn the drawing.

Each pixel at each level takes up different bytes and stores different color information. ARGB_8888 takes up 400 bytes of the same 100 pixel image, while RGB_565 takes up 200 bytes. RGB_565 has the advantage in memory, but the color value and clarity of the Bitmap are not as good as those in ARGB_8888 mode

It should be noted that before Glide4.0,Glide used RGB565 by default, which is relatively memory saving. However, after Glide4.0, the default format has been changed to ARGB_8888, so this advantage is lost. This in itself is a trade-off between quality and memory, and you can change the default format if your application requires a low quality image

// Change the default format to ARGB_8888
 public static final Option<DecodeFormat> DECODE_FORMAT =
      Option.memory(
          "com.bumptech.glide.load.resource.bitmap.Downsampler.DecodeFormat", DecodeFormat.DEFAULT);
Copy the code

3.3 Memory overcommitment Optimization

Bitmap occupies a large amount of memory. If we frequently create and reclaim Bitmap, it is easy to cause memory jitter. Therefore, we should try to reuse Bitmap memory Glide mainly uses inBitmap and BitmapPool to realize memory overuse

3.3.1 inBitmapintroduce

At the beginning of the Android 3.0 (API level 11), system introduced BitmapFactory. Options. InBitmap field. If this option is set, the decoding method using the Options object will attempt to reuse the inBitmap when generating the target Bitmap, which means that the memory of the inBitmap is reused, thus improving performance, while removing and unallocating memory. There are some limitations to how you can use inBitmap. Prior to Android 4.4 (API level 19), only bitmaps of the same size can be reused. After 4.4, inBitmap is only larger than the target Bitmap

3.3.2 rainfall distribution on 10-12BitmapPoolintroduce

We’ve seen that memory can be reused with inBitmap, but we still need a place to store reusable bitmaps. This is the ThreadPoolExecutor in the BitmapPool JDK, which most developers are familiar with, and is commonly referred to as a “thread pool.” Pooling is a very common concept. The purpose of pooling is to reuse objects. For example, ThreadPoolExecutor implements the reuse mechanism of threads and BitmapPool implements the pooling of bitmaps

3.3.3 GlideThe application of

  private static void setInBitmap(
      BitmapFactory.Options options, BitmapPool bitmapPool, int width, int height) {
    @Nullable Bitmap.Config expectedConfig = null;
    if (expectedConfig == null) {
      expectedConfig = options.inPreferredConfig;
    }
    // BitmapFactory will clear out the Bitmap before writing to it, so getDirty is safe.
    options.inBitmap = bitmapPool.getDirty(width, height, expectedConfig);
  }
Copy the code

The above is Glide set inBitmap code, to the BitmapPool passed width and height and format, get a reusable object, so that the realization of Bitmap memory multiplexing due to space reasons, detailed source code here is not posted, For more information, refer to Coil and Glide’s Bitmap cache reuse mechanism

4.GlideHow is the lifecycle managed?

When we are doing a network request, we should stop asking for instructions when the page exits, otherwise it is easy to cause memory leak. The same is true for image loading, we should stop asking for instructions when the page exits and destroy resources. But we do not need to use Glide when the page exit what to do, Glide can be done when the page is closed automatically release resources, let’s look at Glide is how to achieve the main two steps: 1. When called, pass in the context via Glide. With and build a Fragment 2 using the context. Monitor the Fragment life cycle and release Glide resources when destroying

4.1 the incomingcontextbuildFragment

// Get the RequestManager from the Activity
public RequestManager get(@NonNull Activity activity) {
      // Get the FragmentManager for the current Activity
      android.app.FragmentManager fm = activity.getFragmentManager();
      // Create a Fragment to bind a RequestManager
      return fragmentGet(
          activity, fm, /*parentHint=*/ null, isActivityVisible(activity));
  }

 private RequestManager fragmentGet(@NonNull Context context,
     @NonNull android.app.FragmentManager fm,
     @Nullable android.app.Fragment parentHint,
     boolean isParentVisible) {
   // add a Fragment to the current Activity to manage the request lifecycle
   RequestManagerFragment current = getRequestManagerFragment(fm, parentHint, isParentVisible);
   / / get RequestManager
   RequestManager requestManager = current.getRequestManager();
   // Create a RequestManager if no RequestManager exists
   if (requestManager == null) {
     Glide glide = Glide.get(context);
     / / (2) build RequestManager
     . / / the current getGlideLifecycle () is ActivityFragmentLifecycle, that is to build the ActivityFragmentLifecycle RequestManager will pass into fragments
     requestManager =
         factory.build(
             glide, current.getGlideLifecycle(), current.getRequestManagerTreeNode(), context);
     // Bind the built RequestManager to the fragment
     current.setRequestManager(requestManager);
   }
   // Return the administrator of the current request
   return requestManager;
 }  
Copy the code

Add a transparent Fragment to the current Activity to manage the request lifecycle 2. Build the RequestManager and pass in the Fragment lifecycle

4.2 RequestManagerListening life cycle

public class RequestManager implements LifecycleListener.ModelTypes<RequestBuilder<Drawable>> { 

	RequestManager(
      Glide glide,
      Lifecycle lifecycle,
      RequestManagerTreeNode treeNode,
      RequestTracker requestTracker,
      ConnectivityMonitorFactory factory,
      Context context) {
    ... 
    Registered to ActivityFragmentLifecycle / / the current object
    lifecycle.addListener(this);
  }
  / /...
    
  //RequestManager implements fragment lifecycle callback
  @Override
  public synchronized void onStart(a) {
    resumeRequests();
    targetTracker.onStart();
  }
      
  @Override
  public synchronized void onStop(a) {
    pauseRequests();
    targetTracker.onStop();
  }
      
  @Override
  public synchronized void onDestroy(a) { targetTracker.onDestroy(); }}public class RequestManagerFragment extends Fragment {
  The key in ActivityFragmentLifecycle / / life cycle
  private final ActivityFragmentLifecycle lifecycle;
  public RequestManagerFragment(a) {
    this(new ActivityFragmentLifecycle());
  }

  RequestManagerFragment(@NonNull ActivityFragmentLifecycle lifecycle) {
    this.lifecycle = lifecycle;
  }
  @Override
  public void onStart(a) {
    super.onStart();
    lifecycle.onStart();
  }

  @Override
  public void onStop(a) {
    super.onStop();
    lifecycle.onStop();
  }

  @Override
  public void onDestroy(a) {
    super.onDestroy();
    lifecycle.onDestroy();
    unregisterFragmentWithRoot();
  }
  / /...
}
Copy the code

The logic is simple: the Fragment lifecycle changes call back the RequestManager lifecycle and then do the related resource release

4.3 summary



Glide.with(this)The bindingActivityLife cycle of. inActivityA new none is created inUItheFragmenttheFragmentTo hold aLifecycleThrough theLifecycleinFragmentCritical lifecycle notificationsRequestManager intoRow related slave operation. In the life cycleonStartTo continue loading,onStopPause loading when,onDestoryStops loading tasks and clearing operations.

Because space is limited, not much code is posted here, more details can be referred to :Glide Lifecycle Management

5.GlideHow to do large image loading

There is also a case for image loading where a single image is very large and compression is not allowed. For example, display: First of all, the world map, Qingming River Map, weibo long map are not compressed and loaded according to the original size, then the screen is certainly not large enough, and considering the memory situation, it is impossible to load the whole picture into the memory at one time, so the optimization idea in this case is generally local loading. In this case usually Glide is only responsible for downloading the image down, the image loading is implemented by our custom ImageView

5.1 BitmapRegionDecoderintroduce

BitmapRegionDecoder is used to display a rectangular area of an image. If you need to display a specific area of an image, this class is perfect. To use this class, it is very simple. Since you are displaying an area of an image, you need at least one method to set the image; An example is a method passed in to the displayed area:

// Set the center area to display the image
BitmapRegionDecoder bitmapRegionDecoder = BitmapRegionDecoder.newInstance(inputStream, false);
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.RGB_565;
Bitmap bitmap = bitmapRegionDecoder.decodeRegion(new Rect(width / 2 - 100, height / 2 - 100, width / 2 + 100, height / 2 + 100), options);
mImageView.setImageBitmap(bitmap);
Copy the code

More detailed implementation is visible:Android’s hd Giant image loading scheme refuses to compress images

However, although this method can also load a large picture, but do not do enough, sliding memory jitter, lag phenomenon is more obvious, can not be used on the line

Here is a large image loading scheme that can be used online

5.2 Can be used for online large image loading scheme

Introduce an open source library: Subsampling – scale – image – view SubsamplingScaleImageView will larger image slices, and then determine whether visible, if visible in memory, or recycling, reduce the memory footprint and jitter At the same time, according to the different scale to choose the appropriate sampling rate, Further reduce the memory footprint at the same time in the child thread decodeRegion operation, decoded after successful callback to the main thread, reduce UI lag.

I also do before BitmapRegionDecoder and SubsamplingScaleImageView memory analysis Interested students can know: the Android UI caton optimization example analysis of performance optimization

conclusion

This article mainly to Glide to do what optimization as a starting point, to answer the following questions 1. Glide image loading process 2.Glide cache mechanism to do what optimizations? 3. What memory optimizations did Glide make? How does Glide manage the life cycle? 5. How to do Glide large image loading?

If it helps you, please click like, thank you ~

The resources

Interviewer: it is best not to write Glide on the resume, not to ask source code so simple talk about Glide in the interview those things from the perspective of source code analysis Glide cache strategy [optimization] do not use third-party libraries, Bitmap optimization strategy