Coil and Glide's Bitmap cache reuse mechanism

Coil and Glide's Bitmap cache reuse mechanism

Official account : byte array , I hope to help you

The most familiar image loading framework for Android developers should be Glide and Picasso. In the past two years, there has also been a rising star: Coil

Coil is an emerging Android image loading library. The name of Coil comes from: Co routine, I mage and L oader get Coil. Its characteristics are:

  • Faster : Coil has made many optimizations in performance, including memory caching and disk caching, sampling images in memory, reusing Bitmap, and supporting automatic pause and cancellation of image requests according to life cycle changes, etc.
  • Lighter weight : Coil will add about two thousand methods to your App (provided that your App has integrated OkHttp and Coroutines). Coil has the same number of methods as Picasso, which is much lighter than Glide and Fresco
  • Easier to use : Coil's API takes full advantage of the new features of the Kotlin language, simplifies and reduces a lot of duplication of code
  • More popular : Coil prefers Kotlin language development, and uses more modern open source libraries including Coroutines, OkHttp, Okio and AndroidX Lifecycles

Coil has some unique advantages. For example, in order to monitor the life cycle changes of the UI layer, Glide implements indirect monitoring by injecting a Fragment with no UI interface into the Activity or Fragment, while Coil only needs to directly monitor the Lifecycle. Coil will be simpler in implementation. Efficient. In addition, when requesting images online, Glide needs to use thread pools and multiple callbacks to complete the display of the final image, and Coil can complete asynchronous loading and thread switching very concisely because of the Kotlin coroutine. In the process, Coil will Much clearer. But in fact, Coil also borrows some excellent open source library implementation ideas, so when I look at the source code of Coil, I always find some shadows of Glide and OkHttp

If you have used Jetpack, Kotlin Coroutines, OkHttp in a large area in your project, then Coil will be more suitable for your project

Here is a brief comparison between the various features of Coil and Glide, and let the readers have a general impression.

  1. Implementation language
    • Glide is implemented entirely in the Java language, and is about the same as Java and Kotlin language friendly
    • Coil is fully implemented in Kotlin language, and multiple extension functions for loading images are declared for ImageView, which will be much more friendly to Kotlin language
  2. Network request
    • Glide uses HttpURLConnection by default, but it also provides an entrance to change the way to implement network requests.
    • Coil uses OkHttp by default, but it also provides an entrance to change the way to implement network requests.
  3. Life cycle monitoring
    • Glide implements monitoring by injecting a Fragment without a UI interface into Activity or Fragment
    • Coil implements monitoring directly through Lifecycle
  4. Memory cache
    • Glide's memory cache is divided into ActiveResources and MemoryCache two levels
    • Coil's memory cache is divided into two levels, WeakMemoryCache and StrongMemoryCache, which are essentially the same as Glide
  5. Disk cache
    • Glide uses DiskLruCache to perform disk caching after loading the image, and provides multiple options such as whether to cache, whether to cache the original image, whether to cache the converted image, etc.
    • Coil implements disk caching through OkHttp's network request caching mechanism, and disk caching is only effective for original pictures loaded through network requests, and pictures from other sources and converted pictures are not cached.
  6. Network cache
    • Glide does not have this concept
    • Compared with Glide, Coil has an extra layer of network cache, which can be used to implement local cache instead of network loading (of course, if the local cache does not exist, an error will be reported)
  7. Thread frame
    • Glide uses native ThreadPoolExecutor to complete background tasks, and implements thread switching through Handler
    • Coil uses Coroutines to complete background tasks and thread switching

When I wrote an article about Glide and Coil's source code analysis, I did not specifically introduce Bitmap's cache reuse logic. This article will add this knowledge point, I hope it will be helpful to you

This article is based on the current latest version of Glide and Coil for analysis

Implementation "com.github.bumptech.glide: Glide: 4.12.0" Implementation "KT-io.coil: Coil: 1.2.0" Copy Code

1. BitmapPool

The ThreadPoolExecutor in the JDK is believed to be familiar to most developers, and we generally call it a "thread pool". Pooling is a very common concept, and its purpose is to achieve object reuse. For example, ThreadPoolExecutor implements the thread reuse mechanism

There is also the realization of the concept of "pooling" in the Android system. Because there are many events in the system itself that need to be delivered to Looper for processing through Messages, the creation of Messages is very frequent. In order to reduce the frequent repeated creation of Messages, Message provides MessagePool to realize the cache reuse of Messages to optimize memory usage. Called when Looper consumes Message

recycleUnchecked()
Method to recycle Message, after clearing all resources, it will be cached
sPool
In terms of variables, at the same time, the previously cached Message is set as the next node next, and up to 50 Messages can be cached through this linked list structure.
obtain()
Method will determine whether there is currently available cache, if any, it will
sPool
It returns after being removed from the linked list, otherwise it returns a new Message instance. So when we send a message, we should try to call
Message.obtain()
or
Handler.obtainMessage()
Method to get the Message instance

public final class Message implements Parcelable { /** @hide */ public static final Object sPoolSync = new Object(); private static Message sPool; private static int sPoolSize = 0 ; private static final int MAX_POOL_SIZE = 50 ; public static Message obtain () { synchronized (sPoolSync) { if (sPool != null ) { Message m = sPool; sPool = m.next; m.next = null ; m.flags = 0 ; //clear in-use flag sPoolSize--; return m; } } return new Message(); } @UnsupportedAppUsage void recycleUnchecked () { //Mark the message as in use while it remains in the recycled object pool. //Clear out all other details. flags = FLAG_IN_USE; what = 0 ; arg1 = 0 ; arg2 = 0 ; obj = null ; replyTo = null ; sendingUid = UID_NONE; workSourceUid = UID_NONE; when = 0 ; target = null ; callback = null ; data = null ; synchronized (sPoolSync) { if (sPoolSize <MAX_POOL_SIZE) { next = sPool; sPool = this ; sPoolSize++; } } } } Copy code

Correspondingly, BitmapPool is to realize the reuse of Bitmap. At present, all popular image loading frameworks need to use BitmapPool to reduce memory consumption. Bitmap should be the type of resource that occupies the most memory space in many applications, and it is also one of the common causes of application OOM. BitmapPool is a very important means for applications to improve performance.

2. Bitmap recycling and reuse

According to Google s official introduction: Managing Bitmap Memory We can take some measures to promote Bitmap garbage collection and reuse, but the specific strategy needs to depend on the system version:

  • On Android 2.3.3 (API level 10) and lower versions, it is recommended to pass
    bitmap.recycle()
    To recover Bitmap as soon as possible and reduce
    OutOfMemoryError
    The probability. But you should only use it when you are sure that Bitmap is no longer in use
    recycle()
    , Otherwise if called
    recycle()
    And later try to draw the Bitmap, you will receive an error:
    "Canvas: trying to use a recycled bitmap"
  • Starting from Android 3.0 (API level 11), the system introduced
    BitmapFactory.Options.inBitmap
    Field. If this option is set, then use
    Options
    The object's decoding method will try to reuse inBitmap when generating the target Bitmap, which means that the memory of inBitmap is reused, thereby improving performance, and removing memory allocation and deallocation. However, there are some restrictions on the use of inBitmap. Before Android 4.4 (API level 19), the system only supports reusing bitmaps of the same size. After 4.4, as long as the size of inBitmap is larger than the target Bitmap.

Both Glide and Coil use inBitmap on the basis of BitmapPool, which further improves the reuse efficiency of Bitmap

3. Coil's reuse of Bitmap

The BitmapPool interface of Coil defines all methods for caching Bitmap. The meaning of BitmapPool's existence is to realize the reuse of Bitmap, so naturally there needs to be a corresponding access method, corresponding

put
Method and multiple
get
method. The size of the cache cannot grow indefinitely, so there needs to be a way to clean the cache.
trimMemory
Methods and
clear
method. among,
trimMemory
The method is used to decide how to clean up the cache according to the current running situation of the application or the system. For example, when the application returns to the background, this method can be used to actively reduce the memory usage, thereby increasing the priority of the process and reducing the application being killed by the system The probability

In addition,

invoke
It is the operator overload method, maxSize is the maximum cache space allowed to be used, maxSize is equal to 0 means no caching, then use empty to realize EmptyBitmapPool, otherwise use RealBitmapPool

interface BitmapPool { companion object { @JvmStatic @JvmName( "create" ) operator fun invoke (maxSize: Int ) : BitmapPool { return if (maxSize == 0 ) EmptyBitmapPool() else RealBitmapPool(maxSize) } } //Store Bitmap fun put (bitmap: Bitmap ) //Acquire the cached Bitmap according to the requirements or construct a new Bitmap fun get ( @Px width: Int , @Px height: Int , config: Bitmap . Config ) : Bitmap fun getOrNull ( @Px width: Int , @Px height: Int , config: Bitmap . Config ) : Bitmap? fun getDirty ( @Px width: Int , @Px height: Int , config: Bitmap .Config) : Bitmap fun getDirtyOrNull ( @Px width: Int , @Px height: Int , config: Bitmap . Config ) : Bitmap? //Decide how to clean up the cache according to the current operating conditions of the application or system fun trimMemory (level: Int ) //Clear all the cache fun clear () } Copy code

RealBitmapPool is the only meaningful implementation class of the BitmapPool interface. It can be seen that although RealBitmapPool will increase or decrease the element value in bitmaps HashSet accordingly when accessing Bitmap, in fact, all the values obtained externally are taken from strategy. BitmapPoolStrategy really defines the cache reuse mechanism. The place

internal class RealBitmapPool ( private val maxSize: Int , private val allowedConfigs: Set<Bitmap.Config> = ALLOWED_CONFIGS, private val strategy: BitmapPoolStrategy = BitmapPoolStrategy(), private val logger: Logger? = null ): BitmapPool { private val bitmaps = hashSetOf<Bitmap>() @Synchronized override fun put (bitmap: Bitmap ) { //Delivered to BitmapPoolStrategy strategy.put(bitmap) bitmaps += bitmap currentSize += size puts++ logger?.log(TAG, Log.VERBOSE) { "Put bitmap = ${strategy.stringify(bitmap)}/n ${logStats()} " } trimToSize(maxSize) } @Synchronized override fun getDirtyOrNull ( @Px width: Int , @Px height: Int , config: Bitmap . Config ) : Bitmap? { require(!config.isHardware) { "Cannot create a mutable hardware bitmap." } //Fetch val from BitmapPoolStrategy as required result = strategy. get (width, height, config) if (result == null ) { logger?.log(TAG, Log.VERBOSE) { "Missing bitmap = ${strategy.stringify(width, height, config)} " } misses++ } else { bitmaps -= result currentSize -= result.allocationByteCountCompat hits++ normalize(result) } logger?.log(TAG, Log.VERBOSE) { "Get bitmap = ${strategy.stringify(width, height, config)}/n ${logStats()} " } return result } } Copy code

As mentioned above, the recycling and reuse mechanism of Bitmap has some differences in different system versions, and BitmapPoolStrategy completely shields the differences in Bitmap reuse rules in different Android system versions, and its internal decision will be based on the system version. Which multiplexing mechanism is adopted so that the external can be accessed through a unified method without having to care about the internal implementation. Strategy mode is used here

  • AttributeStrategy was used before Android 4.4. AttributeStrategy will
    bitmapWidth, bitmapHeight, bitmapConfig
    These three are the only identifiers of Bitmap, and only Bitmaps that are exactly equal to these three attributes can be reused
  • Starting from Android 4.4, SizeStrategy is adopted. SizeStrategy will
    bitmapSize
    As the unique identifier of Bitmap, only Bitmap that is not smaller than the target size and does not exceed four times the size can be reused. The reason for the maximum limit is to save memory. After all, if the bitmap used for reuse exceeds too much, it will be wasteful.
internal interface BitmapPoolStrategy { companion object { operator fun invoke () : BitmapPoolStrategy { return when { SDK_INT >= 19 -> SizeStrategy() else -> AttributeStrategy() } } } fun put (bitmap: Bitmap ) fun get ( @Px width: Int , @Px height: Int , config: Bitmap . Config ) : Bitmap? fun removeLast () : Bitmap? fun stringify (bitmap: Bitmap ) : String fun stringify ( @Px width: Int , @Px height: Int , config: Bitmap . Config ) : String } internal class AttributeStrategy : BitmapPoolStrategy { private val entries = LinkedMultimap<Key, Bitmap>() override fun put (bitmap: Bitmap ) { //Use width, height, and config as the unique identification key at the same time entries.put(Key(bitmap.width, bitmap.height, bitmap.config), bitmap) } override fun get ( @Px width: Int , @Px height: Int , config: Bitmap . Config ) : Bitmap? { return entries.removeLast(Key(width, height, config)) } private data class Key ( @Px val width: Int , @Px val height: Int , val config: Bitmap.Config ) } internal class SizeStrategy : BitmapPoolStrategy { companion object { private const val MAX_SIZE_MULTIPLE = 4 } private val entries = LinkedMultimap< Int , Bitmap>() private val sizes = TreeMap< Int , Int >() override fun put (bitmap: Bitmap ) { //Use the size of bitmap as its unique identifier key val size = bitmap.allocationByteCountCompat entries.put(size, bitmap) val count = sizes[size] sizes[size] = if (count == null ) 1 else count + 1 } override fun get ( @Px width: Int , @Px height: Int , config: Bitmap . Config ) : Bitmap? { //Calculate the size occupied by the target bitmap val size = Utils.calculateAllocationByteCount(width, height, config) //This step is used to find the most suitable bitmap for multiplexing in entries. //First find the key that is larger than size and closest to size in sizes. If the key exists and its size does not exceed four times the size, use the key , Otherwise use size directly //that is, find the bitmap that is closest to the size but does not exceed the size too much, otherwise if the bitmap used for reuse is too large, it will be wasteful val bestSize = sizes.ceilingKey(size)?.takeIf {it <= MAX_SIZE_MULTIPLE * size} ?: size //Always call removeLast so bestSize becomes the head of the linked list. val bitmap = entries.removeLast(bestSize) if (bitmap != null ) { decrementSize(bestSize) //Convert the reused bitmap into target size and target configuration bitmap.reconfigure(width, height, config) } return bitmap } } Copy code

Coil will take out Bitmap from RealBitmapPool for reuse when loading network pictures and transforming pictures

Coil uses OkHttp to request network pictures by default. After getting BufferedSource, it will pass BitmapFactoryDecoder.

decodeInterruptible
Method to convert BufferedSource to Bitmap. Coil also uses the system's BitmapFactory to generate Bitmap, so
decodeInterruptible
The method will try to set the inBitmap property to BitmapFactory.Options to achieve reuse. The data source of inBitmap is taken from BitmapPool

private fun decodeInterruptible ( pool: BitmapPool , source: Source , size: Size , options: Options ) : DecodeResult = BitmapFactory.Options().run { val safeSource = ExceptionCatchingSource(source) val safeBufferedSource = safeSource.buffer() //1. read the width and height of the bitmap corresponding to the source.//Get outWidth and outHeight inJustDecodeBounds = true BitmapFactory.decodeStream(safeBufferedSource.peek().inputStream(), null , this ) safeSource.exception?.let { throw it} inJustDecodeBounds = false when { outWidth <= 0 || outHeight <= 0 -> { inSampleSize = 1 inScaled = false //Failed to read the width and height properties. At this time, I don t know that an inBitmap of the appropriate size cannot be obtained, so set it to null inBitmap = null } size! is PixelSize -> { //This occurs if size is OriginalSize. inSampleSize = 1 inScaled = false if (inMutable) { //The original image size is required to load the image outside //Then also generate inBitmap according to the original image size inBitmap = pool.getDirty(outWidth, outHeight, inPreferredConfig) } } else -> { if (inMutable) { //Set inBitmap according to whether to sample inSampleSize and whether to scale inScaled inBitmap = when { //If we're not scaling the image, use the image's source dimensions. inSampleSize == 1 && !inScaled -> { pool.getDirty(outWidth, outHeight, inPreferredConfig) } //We can only re-use bitmaps that don't match the image's source dimensions on API 19 and above. SDK_INT >= 19 -> { //Request a slightly larger bitmap than necessary as the output bitmap's dimensions //may not match the requested dimensions exactly. This is due to intricacies in Android's //downsampling algorithm across different API levels. val sampledOutWidth = outWidth/inSampleSize.toDouble() val sampledOutHeight = outHeight/inSampleSize.toDouble() pool.getDirty( width = ceil(scale * sampledOutWidth + 0.5 ).toInt(), height = ceil(scale * sampledOutHeight + 0.5 ).toInt(), config = inPreferredConfig ) } //Else, let BitmapFactory allocate the bitmap internally. else -> null } } } } val inBitmap: Bitmap? = inBitmap var outBitmap: Bitmap? = null try { outBitmap = safeBufferedSource.use { //Here to really generate Bitmap if (SDK_INT < 19 && outMimeType == null ) { val bytes = it.readByteArray() BitmapFactory.decodeByteArray(bytes, 0 , bytes.size, this ) } else { BitmapFactory.decodeStream(it.inputStream(), null , this ) } } safeSource.exception?.let { throw it} } catch (throwable: Throwable) { //If an exception is thrown in the process of generating Bitmap, then try to recycle inBitmap and outBitmap inBitmap?.let(pool::put) if (outBitmap !== inBitmap) { outBitmap?.let(pool::put) } throw throwable } } Copy code

In addition, image transformation is a function that basically all image loading libraries will support. Coil's abstraction of this function is the Transformation interface. Effects such as rounded corners and blur processing need to be implemented through this interface. In the process of image transformation, a blank Bitmap is often used, so

transform
The method contains a BitmapPool parameter for subclasses to use

interface Transformation { fun key () : String suspend fun transform (pool: BitmapPool , input: Bitmap , size: Size ) : Bitmap } Copy code

For example, Coil provides CircleCropTransformation by default to achieve the rounded corner effect. CircleCropTransformation will first obtain a blank Bitmap through BitmapPool, and then draw the original Bitmap on top of the blank Bitmap, so as to reuse the existing Bitmap as much as possible.

class CircleCropTransformation : Transformation { override fun key () : String = CircleCropTransformation:: class .java.name override suspend fun transform (pool: BitmapPool , input: Bitmap , size: Size ) : Bitmap { val paint = Paint(Paint.ANTI_ALIAS_FLAG or Paint.FILTER_BITMAP_FLAG) val minSize = min(input.width, input.height) val radius = minSize/2f //Get a blank Bitmap val output = pool. get (minSize, minSize, input.safeConfig) output.applyCanvas { //Draw the circle first drawCircle(radius, radius, radius, paint) paint.xfermode = XFERMODE //Draw the original Bitmap on the output drawBitmap(input, radius-input.width/2f , radius-input.height/2f , paint) } return output } override fun equals (other: Any ?) = other is CircleCropTransformation override fun hashCode () = javaClass.hashCode() override fun toString () = "CircleCropTransformation()" private companion object { val XFERMODE = PorterDuffXfermode(PorterDuff.Mode.SRC_IN) } } Copy code

4. Glide's reuse of Bitmap

After understanding Coil's cache reuse logic for Bitmap, it will be much simpler to look at Glide. The implementation of the two is highly consistent in this respect, and even the interface name and class name are very similar. To be precise, it should be that Coil borrowed from Glide's implementation ideas. As a rising star, Coil borrowed many implementation ideas from the two open source libraries Glide and OkHttp. When you look at the Coil source code, you can always find the shadow of these two open source libraries.

Glide also contains a BitmapPool interface, which has two implementation classes, one is an empty implementation BitmapPoolAdapter, the other is a practical LruBitmapPool

public interface BitmapPool { long getMaxSize () ; void setSizeMultiplier ( float sizeMultiplier) ; void put (Bitmap bitmap) ; @NonNull Bitmap get ( int width, int height, Bitmap.Config config) ; @NonNull Bitmap getDirty ( int width, int height , Bitmap.Config config) ; void clearMemory () ; void trimMemory (int level) ; } public class LruBitmapPool implements BitmapPool { private static final String TAG = "LruBitmapPool" ; private static final Bitmap.Config DEFAULT_CONFIG = Bitmap.Config.ARGB_8888; private final LruPoolStrategy strategy; private final Set<Bitmap.Config> allowedConfigs; private final long initialMaxSize; private final BitmapTracker tracker; private long maxSize; private long currentSize; private int hits; private int misses; private int puts; private int evictions; //Exposed for testing only. LruBitmapPool( long maxSize, LruPoolStrategy strategy, Set<Bitmap.Config> allowedConfigs) { this .initialMaxSize = maxSize; this .maxSize = maxSize; this .strategy = strategy; this .allowedConfigs = allowedConfigs; this . tracker = new NullBitmapTracker(); } } Copy code

LruBitmapPool is associated with an LruPoolStrategy object, and LruPoolStrategy implements the specific Bitmap cache reuse logic. Bitmaps are actually accessed by LruPoolStrategy

interface LruPoolStrategy { void put (Bitmap bitmap) ; @Nullable Bitmap get ( int width, int height, Bitmap.Config config) ; @Nullable Bitmap removeLast () ; String logBitmap (Bitmap bitmap) ; String logBitmap ( int width, int height, Bitmap.Config config) ; int getSize (Bitmap bitmap) ; } Copy code

LruPoolStrategy contains two implementation classes, basically the same as Coil s design

  • AttributeStrategy. For systems prior to Android 4.4, will
    bitmapWidth, bitmapHeight, bitmapConfig
    These three are the only identifiers of Bitmap, and only Bitmaps that are exactly equal to these three attributes can be reused
  • SizeConfigStrategy. For Android 4.4 and later systems, it will
    bitmapSize, bitmapConfig
    These two are used as the only identifiers of Bitmap, and only the Bitmap that is not less than the target size and the size does not exceed eight times can be reused (Coil requires no more than four times)

Glide will also take out Bitmap from BitmapPool for reuse when loading network pictures and transforming pictures.

Downsampler

decodeFromWrappedStreams
The method realizes the specific decoding logic. For example, when loading a network picture, it will be packaged as an ImageReader object according to the InputStream you get. If you can get the width and height of the Bitmap corresponding to the InputStream, it will call
setInBitmap
Method to set inBitmap property for BitmapFactory.Options, inBitmap is taken from bitmapPool

private Bitmap decodeFromWrappedStreams ( ImageReader imageReader, BitmapFactory.Options options, DownsampleStrategy downsampleStrategy, DecodeFormat decodeFormat, PreferredColorSpace preferredColorSpace, boolean isHardwareConfigAllowed, int requestedWidth, int requestedHeight, boolean fixBitmapToRequestedDimensions, DecodeCallbacks callbacks) throws IOException { if ((options.inSampleSize == 1 || isKitKatOrGreater) && shouldUsePool(imageType)) { //If this isn't an image, or BitmapFactory was unable to parse the size, width and height //will be -1 here. if (expectedWidth> 0 && expectedHeight> 0 ) { //got the Bitmap width corresponding to InputStream After the high size, set inBitmap for Options setInBitmap(options, bitmapPool, expectedWidth, expectedHeight); } } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { boolean isP3Eligible = preferredColorSpace == PreferredColorSpace.DISPLAY_P3 && options.outColorSpace != null && options.outColorSpace.isWideGamut(); options.inPreferredColorSpace = ColorSpace.get(isP3Eligible? ColorSpace.Named.DISPLAY_P3: ColorSpace.Named.SRGB); } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { options.inPreferredColorSpace = ColorSpace.get(ColorSpace.Named.SRGB); } //Go to actually decode the Bitmap Bitmap downsampled = decodeStream(imageReader, options, callbacks, bitmapPool); Bitmap rotated = null ; if (downsampled != null ) { //If we scaled, the Bitmap density will be our inTargetDensity. Here we correct it back to //the expected density dpi. downsampled.setDensity(displayMetrics.densityDpi); rotated = TransformationUtils.rotateImageExif(bitmapPool, downsampled, orientation); if (!downsampled.equals(rotated)) { bitmapPool.put(downsampled); } } return rotated; } @TargetApi(Build.VERSION_CODES.O) private static void setInBitmap (BitmapFactory.Options options, BitmapPool bitmapPool, int width, int height) { @Nullable Bitmap.Config expectedConfig = null ; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES .O) { if (options.inPreferredConfig == Config.HARDWARE) { return ; } expectedConfig = options.outConfig; } if (expectedConfig == null ) { expectedConfig = options.inPreferredConfig; } options.inBitmap = bitmapPool.getDirty(width, height, expectedConfig); } Copy code

In addition, from the comment on inBitmap in the system source code, you can see that if the decoding fails after setting this field, an IllegalArgumentException will be thrown.

/** * If set, decode methods that take the Options object will attempt to * reuse this bitmap when loading content. If the decode operation * cannot use this bitmap, the decode method will throw an * { @link java.lang.IllegalArgumentException}. */ public Bitmap inBitmap; copy code

Glide

decodeStream
The method caught this exception. If it is executing
imageReader.decodeBitmap
If an IllegalArgumentException is thrown in the process and the current inBitmap is not null, then the exception will be caught, and then inBitmap will be set to null and decode again. If an exception occurs even when inBitmap is null,
decodeStream
The method throws the exception directly, that is, the method performs decoding twice at most. And Coil will only decode once, there is no downgrade rule like Glide

private static Bitmap decodeStream( ImageReader imageReader, BitmapFactory.Options options, DecodeCallbacks callbacks, BitmapPool bitmapPool) throws IOException { if (!options.inJustDecodeBounds) { callbacks.onObtainBounds(); imageReader.stopGrowingBuffers(); } int sourceWidth = options.outWidth; int sourceHeight = options.outHeight; String outMimeType = options.outMimeType; final Bitmap result; TransformationUtils.getBitmapDrawableLock().lock(); try { //Go to call BitmapFactory.decodeStream method to generate Bitmap result = imageReader.decodeBitmap(options); } catch (IllegalArgumentException e) { IOException bitmapAssertionException = newIoExceptionForInBitmapAssertion(e, sourceWidth, sourceHeight, outMimeType, options); if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d( TAG, "Failed to decode with inBitmap, trying again without Bitmap re-use" , bitmapAssertionException); } if (options.inBitmap != null ) { try { //Recycle inBitmap and save it in bitmapPool again //Then set inBitmap to null and decode it again bitmapPool.put(options.inBitmap); options.inBitmap = null ; return decodeStream(imageReader, options, callbacks, bitmapPool); } catch (IOException resetException) { throw bitmapAssertionException; } } throw bitmapAssertionException; } finally { TransformationUtils.getBitmapDrawableLock().unlock(); } return result; } Copy code

The abstraction of Glide's image transformation function is also called the Transformation interface. We generally use its subclass BitmapTransformation to operate on Bitmap and return the result of the operation. This piece of logic is basically the same as Coil.

public abstract class BitmapTransformation implements Transformation < Bitmap > { @NonNull @Override public final Resource<Bitmap> transform ( @NonNull Context context, @NonNull Resource<Bitmap> resource, int outWidth, int outHeight) { if (!Util.isValidDimensions(outWidth, outHeight)) { throw new IllegalArgumentException( " Cannot apply transformation on width: " + outWidth + "or height:" + outHeight + "less than or equal to zero and not Target.SIZE_ORIGINAL" ); } //Get BitmapPool BitmapPool bitmapPool = Glide.get(context).getBitmapPool(); //Get the original Bitmap Bitmap toTransform = resource.get(); //Get the target width and height int targetWidth = outWidth == Target.SIZE_ORIGINAL? ToTransform.getWidth(): outWidth; int targetHeight = outHeight == Target.SIZE_ORIGINAL? ToTransform.getHeight(): outHeight; //Go to perform image transformation and Get the converted result Bitmap transformed = transform(bitmapPool, toTransform, targetWidth, targetHeight); final Resource<Bitmap> result; if (toTransform.equals(transformed)) { result = resource; } else { result = BitmapResource.obtain(transformed, bitmapPool); } return result; } protected abstract Bitmap transform ( @NonNull BitmapPool pool, @NonNull Bitmap toTransform, int outWidth, int outHeight) ; } Copy code

5. related articles

  • Three-party library source notes (9)-super detailed Glide source code
  • Three-party library source notes (10)-Glide knowledge points you may not know
  • Three-party library source code notes (13)-may be the first Coil source code analysis article on the entire network
  • Talk about some knowledge points of Bitmap