use

A simple usage example is as follows:

// It is important to note that Placeholder not loaded asynchronously RequestOptions options = new RequestOptions().placeholder(r.rawable.ic_launcher_background) .error(R.drawable.error) .diskCacheStrategy(DiskCacheStrategy.NONE); Glide.with(this) .load(url) .apply(options) .into(imageView);Copy the code

If you want to get only the resource without displaying it, you can use CustomTarget:

Glide.with(context
    .load(url)
    .into(new CustomTarget<Drawable>() {
        @Override
        public void onResourceReady(Drawable resource, Transition<Drawable> transition) {
            // Do something with the Drawable here.
        }

        @Override
        public void onLoadCleared(@Nullable Drawable placeholder) {
            // Remove the Drawable provided in onResourceReady from any Views and ensure
            // no references to it remain.
        }
    });
Copy the code

If you want to load in a background thread, you can use Submit to get a FutureTarget:

FutureTarget<Bitmap> futureTarget = Glide.with(context)
    .asBitmap()
    .load(url)
    .submit(width, height);

Bitmap bitmap = futureTarget.get();

// Do something with the Bitmap and then when you're done with it:
Glide.with(context).clear(futureTarget);
Copy the code

The code architecture

The key interface

Glide source code of the most critical 5 interfaces are as follows:

  1. Request, used to represent an image loading Request, The related classes are RequestOptions (to specify rounded corners, placeholders, etc.), RequestListener (to listen to execution results), RequestCoordinator (to coordinate loading of master diagrams, thumbnails, and error diagrams), and RequestManager (to call back during the life cycle To control the execution of image loading requests

  2. DataFecher, which is used to fetch data, which may be local files, Asset files, server files, and so on

  3. Resource, which represents a specific image Resource, such as Bitmap and Drawable

  4. ResourceDecoder, used to read data and convert it to the appropriate resource type, such as converting a file to a Bitmap

  5. Target, the Target to load the image, such as ImageViewTarget

Cache interface

Cache-related interfaces are as follows:

  1. MemoryCache, MemoryCache, using LruCache implementation
  2. DiskCache, local file cache, using DiskLruCache implementation
  3. BitmapPool, used to cache Bitmap objects
  4. ArrayPool, used to cache data objects
  5. Encoder, used to persist data, such as writing a Bitmap to a local file

Secondary interface

The auxiliary interfaces are as follows:

  1. LifeCycleListener, which listens for the Activity/Fragment lifecycle so that Target can control the animation and clear resources
  2. ConnectivityListener, which listens to the network status of the device to perform corresponding operations, such as restarting image loading
  3. Transformation is used to perform image Transformation operations such as rounded corners and rotations. Note that these transformations are not applied to placeholders
  4. Transition to perform Transition animation effects
  5. ResourceTranscoder, used to convert resource types, such as a Bitmap to a Drawable

other

Some other important classes are as follows:

  1. Glide, used to provide a unified external interface
  2. Engine, used to co-ordinate all image loading. Related classes include EngineJob, EngineKey, etc
  3. MemorySizeCalculator, used to calculate the size of memory cache, BitmapPool, and ArrayPool based on device resolution

Thus, a complete image loading process looks like this:

Image loading process

Add Fragment to listen for life cycle

The Glide. With method is used to return a RequestManager and add a Fragment to the corresponding Activity/Fragment to listen for the lifecycle:

public class RequestManagerRetriever implements Handler.Callback { static final String FRAGMENT_TAG = "com.bumptech.glide.manager"; @NonNull private RequestManagerFragment getRequestManagerFragment(...) { RequestManagerFragment current = (RequestManagerFragment) fm.findFragmentByTag(FRAGMENT_TAG); If (current == null) {// Create a Fragment and add it to the current Activity/Fragment. fm.beginTransaction().add(current, FRAGMENT_TAG).commitAllowingStateLoss(); } return current; }}Copy the code

In this way, the RequestManager can perform actions during Activity/Fragment lifecycle callbacks, such as pausing/resuming network load requests, cleaning up resources, and so on:

public class RequestManager implements LifecycleListener { @Override public synchronized void onStart() { resumeRequests(); } @override public synchronized void onStop() {pauseRequests(); Override public synchronized void onDestroy() {for (Target<? > target : targetTracker.getAll()) { clear(target); } requestTracker.clearRequests(); lifecycle.removeListener(this); }}Copy the code

Coordination layer

The load method of the RequestManager is used to return a RequestBuilder, and the request is formally built and executed when the into method is executed.

Because an image load request may attach a thumbnail or an image that appears when an error occurs, to coordinate multiple image requests, Glide will load the request into SingleRequest, ErrorRequestCoordinator, ThumbnailRequestCoordinator 3 kinds.

The method of coordination is simple:

  1. The thumbnail and the main image are loaded at the same time. If the main image is loaded first, cancel the thumbnail loading request; otherwise, the thumbnail is displayed first and cleared after the main image is loaded
  2. In the case of error diagrams, loading and display begins after the main diagram has failed to load

The thumbnails and errors are independent SingleRequest, with a complete loading process and resources sent to Target after loading.

Why not coordinate placeholders? Because the placeholder map is loaded directly from the Android Resource in the main thread and is loaded and displayed the first time, no coordination is required.

Gets the target size and sets the placeholder map

The image loading process starts with the Request’s BEGIN method, and Glide needs to get the Size of Target before loading the data so that it can scale the image after loading. Also, the placeholder map is set at this time:

public final class SingleRequest<R> implements Request, SizeReadyCallback, ResourceCallback { @Override public void begin() { status = Status.WAITING_FOR_SIZE; SizeReadyCallback target.getSize(this); OnLoadStarted (getPlaceholderDrawable()); }}Copy the code

Take the ViewTarget example, which gets its width and height by listening on the ViewTreeObserver:

public abstract class ViewTarget<T extends View, Z> extends BaseTarget<Z> { void getSize(@NonNull SizeReadyCallback cb) { cbs.add(cb); // Call ViewTreeObserver observer = view.getViewTreeObserver(); observer.addOnPreDrawListener(layoutListener); } // Get the size and call void onPreDraw() {int currentWidth = getTargetWidth(); int currentHeight = getTargetHeight(); notifyCbs(currentWidth, currentHeight); }}Copy the code

Once we get the size, we can start loading:

@Override public void onSizeReady(int width, int height) { status = Status.RUNNING; / / load the engine load (...). ; }Copy the code

Memory cache

The first step in loading is to try to fetch from the memory cache:

public class Engine { public <R> LoadStatus load(...) { EngineKey key = keyFactory.buildKey(...) ; // Check the memory cache EngineResource<? > memoryResource = loadFromMemory(key, isMemoryCacheable, startTime); if (memoryResource ! = null) {cb.onResourceready (...); ; Return null} / / or create a new load job return waitForExistingOrStartNewJob (...). ; }}Copy the code

Glide’s memory cache consists of two parts:

  1. Active resources, which are called back to Target after loading and are not released, can be simply interpreted as images being displayed
  2. Memory cache, that is, resources that are no longer displayed but are still in memory and have not been removed by the LRU algorithm
@Nullable private EngineResource<? > loadFromMemory(...) {// Active resource EngineResource<? > active = loadFromActiveResources(key); if (active ! = null) { return active; } // Memory cache EngineResource<? > cached = loadFromCache(key); if (cached ! = null) { return cached; } return null; }Copy the code

Check if the same work is being performed

Glide uses a Job to represent an image load Job. Before creating a new Job, check whether the same Job is being executed. If so, add a callback and return it.

private <R> LoadStatus waitForExistingOrStartNewJob(...) {// Check if there is the same EngineJob< running? > current = jobs.get(key, onlyRetrieveFromCache); if (current ! = null) {// If so, add a callback and return current-.addCallback (cb, callbackExecutor); return new LoadStatus(cb, current); } // Otherwise create a new load job and execute EngineJob<R> EngineJob = engineJobFactory.build(...) ; / / execution engineJob. Start (decodeJob); return new LoadStatus(cb, engineJob); }Copy the code

Disk cache

The second step in image loading is to try to fetch the data from the disk cache, which Glide’s disk cache comes in two categories:

  1. ResourceCache: indicates the image after scaling and transformation
  2. DataCache, for raw image cache

Glide first looks in the scaled, transformed file cache, and if it can’t find it, in the original image cache.

Using ResourceCache as an example, it first obtains files from DiskCache:

class ResourceCacheGenerator implements DataFetcherGenerator, DataFetcher.DataCallback<Object> { @Override public boolean startNext() { currentKey = new ResourceCacheKey(...) ; CacheFile = helper.getDiskCache().get(currentKey); / / get data loadData. Fetcher. LoadData (helper, getPriority (), this); return started; }}Copy the code

Then we get the data from DataFetcher:

private static final class FileFetcher<Data> implements DataFetcher<Data> { @Override public void loadData(...) { try { data = new InputStream(file); callback.onDataReady(data); } catch (FileNotFoundException e) { callback.onLoadFailed(e); }}}Copy the code

Get server data

If the image cannot be found in the disk cache, obtain data from the server. Take OkHttp as an example:

public class OkHttpStreamFetcher implements DataFetcher<InputStream>, okhttp3.Callback { @Override public void loadData(...) Request Request = new request.builder ().url(url.tostringurl ()).build; call = client.newCall(request); call.enqueue(this); } @Override public void onResponse(@NonNull Call call, ResponseBody = response.body(); response.body(); if (response.isSuccessful()) { long contentLength = responseBody.contentLength(); stream = ContentLengthInputStream.obtain(responseBody.byteStream(), contentLength); callback.onDataReady(stream); } else { callback.onLoadFailed(new HttpException(response.message(), response.code())); }}}Copy the code

Caching raw data

After successfully retrieving the data, DataFetcher’s Callback calls back the raw data to the SourceGenerator and caches it to a disk file:

class SourceGenerator implements DataFetcherGenerator, DataFetcherGenerator.FetcherReadyCallback { void onDataReadyInternal(LoadData<? > loadData, Object data) {/ / according to caching strategies can determine whether the cache DiskCacheStrategy DiskCacheStrategy = helper. GetDiskCacheStrategy (); if (data ! = null && diskCacheStrategy.isDataCacheable(loadData.fetcher.getDataSource())) { dataToCache = data; cb.reschedule(); } } @Override public boolean startNext() { if (dataToCache ! = null) { Object data = dataToCache; dataToCache = null; cacheData(data); }... Private void cacheData(Object dataToCache) {helper.getDiskCache().put(originalKey, writer); }}Copy the code

decoding

What you get from the disk cache/remote server is a stream of data that needs to be decoded to Bitmap, Gif, etc. This is done by ResourceDecoder:

public class StreamBitmapDecoder implements ResourceDecoder<InputStream, Bitmap> { @Override public Resource<Bitmap> decode(...) { return downsampler.decode(...) ; // scale}}Copy the code
public final class Downsampler {

    private Resource<Bitmap> decode(...) {
        Bitmap result = decodeFromWrappedStreams(...);
        return BitmapResource.obtain(result, bitmapPool);
    }
}
Copy the code

During decoding, if device support is detected, use GPU to store Bitmap data:

boolean setHardwareConfigIfAllowed(...) { boolean result = isHardwareConfigAllowed(...) ; if (result) { optionsWithScaling.inPreferredConfig = Bitmap.Config.HARDWARE; optionsWithScaling.inMutable = false; } return result; }Copy the code

Otherwise use ARGB_8888 to store:

public enum DecodeFormat {
    PREFER_ARGB_8888,

    PREFER_RGB_565;

    public static final DecodeFormat DEFAULT = PREFER_ARGB_8888;
}
Copy the code

It is worth noting that Glide will determine the source of the data, and if it is not obtained from the transformed file cache, it needs to apply rounded corners, rotations, and so on to the Bitmap:

class DecodeJob<R> {

    <Z> Resource<Z> onResourceDecoded(DataSource dataSource, @NonNull Resource<Z> decoded) {
        Resource<Z> transformed = decoded;
        if (dataSource != DataSource.RESOURCE_DISK_CACHE) {
            appliedTransformation = decodeHelper.getTransformation(resourceSubClass);
            transformed = appliedTransformation.transform(glideContext, decoded, width, height);
        }
        return result;
    }
}
Copy the code

Close, display

After decoding, the result is first called back to Engine, Target, etc., then the transformed image data is written to the disk cache, and finally the resource is cleaned up:

class DecodeJob<R> { private void decodeFromRetrievedData() { Resource<R> resource = decodeFromData(currentFetcher, currentData, currentDataSource); notifyEncodeAndRelease(resource, currentDataSource, isLoadingFromAlternateCacheKey); } private void notifyEncodeAndRelease(...) {/ / the result object callback to the Engine, SingleRequest notifyComplete (result, dataSource, isLoadedFromAlternateCacheKey); / / the data written to the disk cache deferredEncodeManager. Encode (diskCacheProvider, options); OnEncodeComplete (); } private static class DeferredEncodeManager<Z> { void encode(DiskCacheProvider diskCacheProvider, DiskCacheProvider. GetDiskCache (). Put (key, new DataCacheWriter<>(encoder, DataCacheWriter) toEncode, options)); }}}Copy the code

Engine receives a callback and logs the resource with ActiveResources and removes the current load:

public class Engine { @Override public synchronized void onEngineJobComplete(...) { activeResources.activate(key, resource); jobs.removeIfCurrent(key, engineJob); }}Copy the code

In ImageView, for example, after receiving the callback, the Target first determines whether to perform the transition animation, then sets the image Resource to the View, and finally determines whether the Resource is a GIF animation, if so, then executes the animation:

public abstract class ImageViewTarget<Z> extends ViewTarget<ImageView, Z> { @Override public void onResourceReady(@NonNull Z resource, @Nullable Transition<? super Z> transition) { if (transition == null || ! Transition. Transition (resource, this)) {// If there is no transition animation, setResourceInternal(resource); } else {// otherwise execute the animation maybeUpdateAnimatable(resource); } } private void setResourceInternal(@Nullable Z resource) { setResource(resource); maybeUpdateAnimatable(resource); } private void maybeUpdateAnimatable(@nullable Z resource) {if (resource instanceof Animatable) {// GIF Animatable = (Animatable) resource; animatable.start(); } else { animatable = null; } } protected abstract void setResource(@Nullable Z resource); }Copy the code

Caching & resource reuse mechanisms

Allocate memory

Glide first allocates memory based on device conditions before using memory cache or object pool:

public final class GlideBuilder { Glide build(@NonNull Context context) { bitmapPool = new LruBitmapPool(memorySizeCalculator.getBitmapPoolSize()); arrayPool = new LruArrayPool(memorySizeCalculator.getArrayPoolSizeInBytes()); memoryCache = new LruResourceCache(memorySizeCalculator.getMemoryCacheSize()); }}Copy the code

As you can see, the exact memory size is calculated by the Memorysize Aculator.

For ArrayPool, 2MB is allocated by default if it is a low memory device, otherwise 4MB is allocated:

public final class MemorySizeCalculator { private static final int LOW_MEMORY_BYTE_ARRAY_POOL_DIVISOR = 2; MemorySizeCalculator(MemorySizeCalculator.Builder builder) { arrayPoolSize = isLowMemoryDevice(builder.activityManager) ? builder.arrayPoolSizeBytes / LOW_MEMORY_BYTE_ARRAY_POOL_DIVISOR : builder.arrayPoolSizeBytes; } static boolean isLowMemoryDevice(ActivityManager activityManager) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { return activityManager.isLowRamDevice(); } else { return true; } } public static final class Builder { // 4MB. static final int ARRAY_POOL_SIZE_BYTES = 4 * 1024 * 1024; int arrayPoolSizeBytes = ARRAY_POOL_SIZE_BYTES; }}Copy the code

For the Bitmap object pool, since Glide uses hardware (GPU) to store bitmaps above API 26, the memory requirement is much smaller, requiring only one screen resolution (ARGB), which is about 8MB for 1920×1080 phones. Prior to API 26, you needed 4 screen resolutions, or 32 MB.

static final int BITMAP_POOL_TARGET_SCREENS = Build.VERSION.SDK_INT < Build.VERSION_CODES.O ? 4:1; int widthPixels = builder.screenDimensions.getWidthPixels(); int heightPixels = builder.screenDimensions.getHeightPixels(); int screenSize = widthPixels * heightPixels * BYTES_PER_ARGB_8888_PIXEL; int targetBitmapPoolSize = Math.round(screenSize * BITMAP_POOL_TARGET_SCREENS);Copy the code

The memory cache defaults to 2 screen resolutions, about 16MB for 1920×1080 phones:

static final int MEMORY_CACHE_TARGET_SCREENS = 2;
int targetMemoryCacheSize = Math.round(screenSize * builder.memoryCacheScreens);
Copy the code

In addition, the Bitmap object pool and memory cache are limited by the maximum available cache for the application:

public class ActivityManager { public int getMemoryClass() { return staticGetMemoryClass(); }}Copy the code

But now the mobile phone performance has been very high, this value is generally relatively large, even Glide will be based on this multiplied by a coefficient (0.4 or 0.33), also basic enough, so it is not considered.

Reuse the Bitmap

BitmapPool is implemented using LRU algorithm, and the main interfaces are as follows:

public interface BitmapPool { void put(Bitmap bitmap); Get (int width, int height, bitmap.config config); // Get (int width, int height, bitmap.config config); GetDirty (int width, int height, bitmap.config config); getDirty(int width, int height, bitmap.config); // Clear all objects, void clearMemory() is called when device memory is low (onLowMemory); Void trimMemory(int level); void trimMemory(int level); }Copy the code

When a Bitmap object is reclaimed, it is moved to the object pool:

public class BitmapResource implements Resource<Bitmap>, Initializable { @Override public void recycle() { bitmapPool.put(bitmap); }}Copy the code

Reuse opportunities are mainly when transformation effects are performed:

public final class TransformationUtils { public static Bitmap centerCrop(...) {... Bitmap result = pool.get(width, height, getNonNullConfig(inBitmap)); applyMatrix(inBitmap, result, m); return result; }}Copy the code

Reuse active resources

An active resource is one that has been loaded and has not been released:

public class Engine {

    private final ActiveResources activeResources;

    @Override
    public synchronized void onEngineJobComplete(...) {
        activeResources.activate(key, resource);
    }

    @Override
    public void onResourceReleased(Key cacheKey, EngineResource<?> resource) {
        activeResources.deactivate(cacheKey);
    }
}
Copy the code

There are three main opportunities for release:

  1. The lifecycle method onStop callback
  2. The lifecycle method onDestroy callback
  3. OnTrimMemory callback at MODERATE level

In addition, thumbnails are released after the main image is loaded. Therefore, an active resource can simply be understood as a resource being displayed.

These resources are managed by reference counting and released when the count reaches zero:

class EngineResource<Z> implements Resource<Z> { private int acquired; synchronized void acquire() { ++acquired; } void release() { boolean release = false; synchronized (this) { if (--acquired == 0) { release = true; } } if (release) { listener.onResourceReleased(key, this); }}}Copy the code

Memory cache

MemoryCache has the same interface as BitmapPool and ArrayPool:

public interface MemoryCache { Resource<? > remove(@NonNull Key key); Resource<? > put(@NonNull Key key, @Nullable Resource<? > resource); // Remove all objects and call void clearMemory() when device memory is low (onLowMemory); Void trimMemory(int level); void trimMemory(int level); }Copy the code

On resource release, cache if memory cache is available (available via RequestOptions, by default), otherwise reclaim:

public class Engine { @Override public void onResourceReleased(Key cacheKey, EngineResource<? > resource) { activeResources.deactivate(cacheKey); if (resource.isMemoryCacheable()) { cache.put(cacheKey, resource); } else { resourceRecycler.recycle(resource, /*forceNextFrame=*/ false); }}}Copy the code

Reclamation performs different operations depending on the type of resource. If it is a Bitmap, it is moved to the Bitmap object pool, and if it is not, the resource is either cleared or nothing is done.

The keys used by the memory cache are enginekeys, the same as those used by active resources. They are distinguished by image width and height, transformation information, and resource type information.

class EngineKey implements Key { private final int width; private final int height; private final Class<? > resourceClass; private final Class<? > transcodeClass; private final Map<Class<? >, Transformation<? >> transformations; }Copy the code

Disk cache

DiskCache provides three interfaces:

Public interface DiskCache {interface Factory {/** * Default cache size is 250 MB */ int DEFAULT_DISK_CACHE_SIZE = 250 * 1024 * 1024; /** * Default cache folder */ String DEFAULT_DISK_CACHE_DIR = "image_manager_disk_cache"; DiskCache build(); } File get(Key key); void put(Key key, Writer writer); void clear(); }Copy the code

There are two types of cached files:

  1. The original image
  2. The transformed image

The specific file writing operation is completed by Encoder. Take Bitmap as an example. When the Bitmap is cached, it can be written to the file using compress method:

public class BitmapEncoder implements ResourceEncoder<Bitmap> { @Override public boolean encode(...) { final Bitmap bitmap = resource.get(); boolean success = false; OutputStream os = new FileOutputStream(file); bitmap.compress(format, quality, os); os.close(); }}Copy the code

If you need to clear the file cache, you can call Glide’s tearDown method:

public class Glide { public static void tearDown() { glide.engine.shutdown(); }}Copy the code
public class Engine { public void shutdown() { diskCacheProvider.clearDiskCacheIfCreated(); } private static class LazyDiskCacheProvider implements DecodeJob.DiskCacheProvider { synchronized void clearDiskCacheIfCreated() { diskCache.clear(); }}}Copy the code

The keys used for disk caching can be divided into three categories:

  1. GlideUrl, used to fetch remote image data, is distinguished by URL and HEADERS information
  2. ObjectKey is used to obtain local image data. It is distinguished by Uri and File
  3. ResourceCacheKey is used to obtain transformed image cache. It is distinguished by image width and height, transformation information, and resource type information

The thread pool

Glide consists of three main thread pools:

  1. The number of threads used to fetch data is the number of cpus, but not more than 4
  2. The number of threads used to perform read and write caching is 1

) is used to execute GIF animations. The number of threads is 2 if the number of cpus is greater than or equal to 4, otherwise it is 1

public final class GlideBuilder { private GlideExecutor sourceExecutor; private GlideExecutor diskCacheExecutor; private GlideExecutor animationExecutor; Glide build(@NonNull Context context) { sourceExecutor = GlideExecutor.newSourceExecutor(); diskCacheExecutor = GlideExecutor.newDiskCacheExecutor(); animationExecutor = GlideExecutor.newAnimationExecutor(); }}Copy the code

conclusion

Image loading process

In general, the image loading process can be roughly divided into four steps:

  1. The preparatory work
  2. Memory cache
  3. The data load
  4. The decoding display

Preparations include:

  1. Add a Fragment to the corresponding Activity/Fragment and listen for its life cycle. To control the suspension of execution of requests, resume execution, or clean up resources
  2. Build the Request recursively. To coordinate load requests for master, thumbnail, and error images. The thumbnail and the main image are loaded at the same time. If the main image is loaded first, cancel the thumbnail loading request; otherwise, the thumbnail is displayed first and cleared after the main image is loaded. The error diagram is loaded and displayed after the main diagram fails to load
  3. Gets the target size. In the case of ImageView, the size is obtained after the onPreDraw method callback in the ViewTreeObserver.
  4. Set up the placeholder map. The placeholder map is set directly by calling the setImageDrawable method, so there is no need to coordinate with the main map

The memory cache consists of two parts:

  1. Active resources, that is, resources that have been called back to Target after loading and have not been released, can simply be interpreted as images being displayed, with reference counting to determine the specific release time
  2. Memory cache, that is, resources that are no longer displayed but are still in memory and have not been removed by the LRU algorithm

Data loading consists of two parts:

  1. Load from disk cache
  2. Load from the destination address

Disk caching is divided into two parts:

  1. Lookup from the scaled, transformed file cache
  2. If not, look in the original image cache

There are many types of target addresses, such as local file paths, file handles, assets, server links, and so on.

After successful data acquisition:

  1. First, you need to cache the raw image data to your local disk
  2. The data is then decoded to resource types such as Bitmap/Drawable, while applying transformation effects such as rounded corners (if not retrieved from the transformed file cache)
  3. It can then be called back to Target and displayed
  4. Finally, you need to write the scaled and transformed image data to the disk cache and clean up the resources

Target also determines if a transition animation is required when displaying the image, and if so, displays the resource after the transition animation. When displaying a resource, the system checks whether the resource type is a GIF. If so, the animation starts. Otherwise, the system displays the resource directly.

Caching and resource reuse mechanisms

Glide’s caching mechanism is divided into three main parts:

  1. Resource reuse (array objects, Bitmap objects, active resources)
  2. Memory cache
  3. Disk cache (original image data, image data after application transformation)

Among them, active resources use reference counting to determine the timing of release, and other caching mechanisms manage data through the LRU algorithm.

An active resource is a resource that has been loaded and has not been released, and can simply be understood as a resource that is being displayed.

There are three main opportunities for resource release:

  1. The lifecycle method onStop callback
  2. The lifecycle method onDestroy callback
  3. OnTrimMemory callback at MODERATE level

When a resource is released, the system checks whether the memory cache can be used (RequestOptions can be configured, and the default is yes). If so, the memory cache is used; otherwise, the resource is reclaimed. Reclamation performs different operations depending on the type of resource. If it is a Bitmap, it is moved to the Bitmap object pool, and if it is not, the resource is either cleared or nothing is done.

Memory/disk allocation:

  1. Array object pool, if the device is low memory, the default allocation is 2MB, otherwise 4MB
  2. Bitmap object pool. Before API 26, four screen resolutions are allocated (about 32MB for 1920×1080 phones). After API 26, only one screen resolution is required (about 8MB for 1920×1080 phones).
  3. Memory cache, using 2 screen resolutions by default (about 16MB for 1920×1080 phones)
  4. Disk cache by default, a maximum of 250MB data is cached

In addition, the Bitmap object pool and memory cache are limited by the maximum available cache for the application (multiplied by a factor of 0.4 or 0.33) and proportionally allocated if not.

There are three main thread pools:

  1. The number of threads used to fetch data is the number of cpus, but not more than 4
  2. The number of threads used to perform read and write caching is 1
  3. The number of threads is 2 when the number of cpus is greater than or equal to 4, and 1 otherwise