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.Glide
This section describes the overall process of image loading
At the beginning of understandingGlide
And before we do that, let’s look atGlide
Picture loading overall process to do a simple introduction, let you first have an overall concept.
At the same time face backGlide
Do 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 out
Bitmap
After 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.Glide
What 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 ideaGlide
Caching 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 formedGlide
Excellent 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, weLru
Memory cachesize
Set up to hold 99 pictures on the slideRecycleView
If 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 oneRequest
Request, 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 recycledLruCache
The 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.ALL
Use:DATA
andRESOURCE
Cache remote data using onlyRESOURCE
To cache local data.DiskCacheStrategy.AUTOMATIC
: It tries to use the best strategy for both local and remote images. When you load remote data,AUTOMATIC
The 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,AUTOMATIC
The 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.Glide
What 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 storesRGB
Channel, 2 bytes per pixel, rightBitmap
Color is not high demand, can use this mode;ARGB_4444
Deprecated, in useARGB_8888
Instead 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 upBitmap
In 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 inBitmap
introduce
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-12BitmapPool
introduce
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 Glide
The 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.Glide
How 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 incomingcontext
buildFragment
// 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 RequestManager
Listening 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 bindingActivity
Life cycle of. inActivity
A new none is created inUI
theFragment
theFragment
To hold aLifecycle
Through theLifecycle
inFragment
Critical lifecycle notificationsRequestManager into
Row related slave operation. In the life cycleonStart
To continue loading,onStop
Pause loading when,onDestory
Stops 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.Glide
How 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 BitmapRegionDecoder
introduce
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