Why

Why optimize glide’s GIF support? To go back to 2 years ago, we need to support a lot of PNG or GIF images in the page as the background of the active atmosphere, and the operators give GIF images are very large (> 5MB), there will be memory jitter caused APP card, and GIF will drop frames, although the GIF compression can reduce the volume, but the display effect will be greatly reduced. Research loading GIF image loading libraries, also only glide and Fresco. The project already has glide, so what we need is to do optimization. (Plus, Fresco also supports WebP GIFs)

Glide itself has been optimized to support GIFs a lot. The memory jitter problem of rendering multiple GIFs at the same time has been eliminated, and the frame drop problem has been optimized but still exists. However, the memory usage and CPU usage are still worse than the optimized version, today to share how to optimize.

Before and after optimization

1 plus 1 android6.0, load 6 2-5mb GIF images

How

To optimize, first understand the features of GIF and how Glide renders GIFs. Because the source code analysis process is very long, can be a separate article. Here are the main points:

  • GIF features:

    1. The first three bytes of a GIF file must be ‘G’, ‘I’, ‘F’
    2. Each frame in the GIF is the same size
    3. Each frame in GIF has an interval
  • Glide support:

    1. ImageHeaderParserUtils.getType(..)Detects whether the resource is a GIF
    2. com.bumptech.glide.load.resource.gif.GifDrawableDrawable for final rendering GIF
    3. StreamGifDecoder and ByteBufferGifDecoderConvert the stream toGifDrawable
    4. GifDrawableEncoderGifDrawableconvertFile
    5. The above module is incom.bumptech.glide.Glide, and support for registering your own components

Optimized technology selection

  1. Optimize parsing speed to improve efficiency, use Giflib to replace Glide’s Java parsing code to improve efficiency

    For example, giflib, Android-giF-drawable, and fresco

  2. For buffered rendering, the two bitmaps take turns to be parsed and filled in the child thread, and then rendered in the main thread

Computer based on the actual performance of the android – GIF – drawable, memory usage and CPU usage is best, and provide the pl. Droidsonroids. GIF. GifDrawable with parsing and serialization API, and the author in the ongoing maintenance, This third party library was selected as the core for GIF parsing and rendering because both bug fixing and other requirements of the project could be supported.

Fusion glide

Glide GIF has been analyzed before, we only need to copy the corresponding interface and class can be implemented, copy modification start, create these classes as follows

GifLibByteBufferDecoder parsed byte[] to generate a GifDrawable wrapper GifLibDrawableResource GifLibDrawableResource encapsulates GifDrawable for destruction and memory footprint size calculation (for Lrucache) DrawableBytesTranscoder and GifLibBytesTranscoder are used to convert GifLibEncoder for serialization into filesCopy the code

Important parsing class all methods and core methods:

class GifLibByteBufferDecoder.
    @Throws(IOException::class)
    override fun handles(source: ByteBuffer, options: Options): Boolean {
        // Anim must be enabled
        valisAnim = ! options.get(GifOptions.DISABLE_ANIMATION)!!
        // Determine whether it is a GIF based on the file header
        val isGif = ImageHeaderParserUtils.getType(parsers, source) == ImageType.GIF
        // DES: This log is focused on GIF images and has Settings that do not allow animation
        if (isGif) Log.e(TAG, "gif options anim ->$isAnim")
        return isAnim && isGif
    }
    /** Parse method */
    private fun decode(byteBuffer: ByteBuffer, width: Int, height: Int, parser: GifHeaderParser, options: Options): GifLibDrawableResource? {
        val startTime = LogTime.getLogTime()
        return try {
            val header = parser.parseHeader()
            if (header.numFrames <= 0|| header.status ! = GifDecoder.STATUS_OK) {// If we couldn't decode the GIF, we will end up with a frame count of 0.
                return null
            }
            // Set the sample
            val sampleSize = getSampleSize(header, width, height)
            // Create the parser build pattern
            val builder = GifDrawableBuilder()
            builder.from(byteBuffer)
            builder.sampleSize(sampleSize)
            builder.isRenderingTriggeredOnDraw = true
// pl.droidsonroids.gif.GifOptions gifOptions = new pl.droidsonroids.gif.GifOptions();
// DES: transparent giFs with no transparent layer will render with black background
// gifOptions.setInIsOpaque();
            val gifDrawable = builder.build()
            val loopCount = gifDrawable.loopCount
            if (loopCount <= 1) {
                // If the loop is repeated once, it is corrected to an infinite loop
                Log.v(TAG, "Decoded GIF LOOP COUNT WARN $loopCount")
                gifDrawable.loopCount = 0
            }
            GifLibDrawableResource(gifDrawable, byteBuffer)
        } catch (e: IOException) {
            Log.v(TAG, "Decoded GIF Error" + e.message)
            null
        } finally {
            Log.v(TAG, "Decoded GIF from stream in " + LogTime.getElapsedMillis(startTime))
        }
    }
}
Copy the code

Serialization classes:

class GifLibEncoder : ResourceEncoder<GifDrawable?> {
    override fun getEncodeStrategy(options: Options): EncodeStrategy {
        return EncodeStrategy.SOURCE
    }
    override fun encode(data: Resource<GifDrawable? >, file:File, options: Options): Boolean {
        var success = false
        if (data is GifLibDrawableResource) {
            val byteBuffer = data.buffer
            try {
                ByteBufferUtil.toFile(byteBuffer, file)
                success = true
            } catch (e: IOException) {
                e.printStackTrace()
            }
            // DES: encodes the resource as a file
            Log.d(TAG, "GifLibEncoder -> $success -> ${file.absolutePath}")}return success
    }
}
Copy the code

Register components through Registry

  • append(..)Append to the end, when the internal component is inhandles()Returns false or uses the append component on failure
  • prepend(..)Append to the previous, when your component fails to use the native supplied component
  • replace(..)Replace the components
  • register(..)Certified components

To register the component, use glide annotation class to inherit AppGlideModule and registerComponents(..) The following fun is called in the

@JvmStatic
fun registerGifLib(glide: Glide, registry: Registry) {
    // Giflib-gif is preferred
    val bufferDecoder = GifLibByteBufferDecoder(registry.imageHeaderParsers)
    val gifLibTranscoder = GifLibBytesTranscoder()
    val bitmapBytesTranscoder = BitmapBytesTranscoder()
    val gifTranscoder = GifDrawableBytesTranscoder()
    registry.prepend(
        Registry.BUCKET_GIF, java.io.InputStream::class.java, GifDrawable::class.java,
        GifLibDecoder(registry.imageHeaderParsers, bufferDecoder, glide.arrayPool)
    ).prepend(
        Registry.BUCKET_GIF,
        java.nio.ByteBuffer::class.java,
        GifDrawable::class.java, bufferDecoder
    ).prepend(
        GifDrawable::class.java, GifLibEncoder()
    ).register(
        Drawable::class.java, ByteArray::class.java,
        DrawableBytesTranscoder(
            glide.bitmapPool,
            bitmapBytesTranscoder,
            gifTranscoder,
            gifLibTranscoder
        )
    ).register(
        GifDrawable::class.java, ByteArray::class.java, gifLibTranscoder
    )
}
Copy the code

Verify that the component has been registered successfully

IGlide.with(view).load(url)
    .placeholder(R.color.colorAccent)
    .listener(object : RequestListener<Drawable> {
        override fun onResourceReady(
            resource: Drawable? , model:Any? , target:Target<Drawable>? , dataSource:DataSource? , isFirstResource:Boolean): 
Boolean {
            if (resource is pl.droidsonroids.gif.GifDrawable) {
                Log.d("TAG"."Giflib Gifdrawable")}else if (resource is com.bumptech.glide.load.resource.gif.GifDrawable) {
                Log.d("TAG"."Glide Gifdrawable")}return false
        }
        
        override fun onLoadFailed(e: GlideException? , model:Any? ,target:Target<Drawable>? , isFirstResource:Boolean): Boolean = false}).into(view) log: com.example.myDemo D/TAG: Giflib GifdrawableCopy the code

The transform defects

This seems to be a very low invasive replacement for Glide’s GIF support, and is also compatible with using native components after giflib fails, so what are the drawbacks? Disadvantages are also very headache, usually we will do some image loading needs to do some rounded corners or circle and so on. Glide’s GifDrawable is very well supported, almost all BitmapTransformation supports it, but our transform is not valid, the reason is that all transform Settings in the source code are finally called as follows:

class BaseRequestOptions. @NonNull
  T transform(@NonNull Transformation<Bitmap> transformation.boolean isRequired) {... DrawableTransformation DrawableTransformation =new DrawableTransformation(transformation, isRequired);
    transform(Bitmap.class, transformation, isRequired);
    transform(Drawable.class, drawableTransformation, isRequired);
    transform(BitmapDrawable.class, drawableTransformation.asBitmapDrawable(), isRequired);
    // Transformation of gifdrawble
    transform(GifDrawable.class, new GifDrawableTransformation(transformation), isRequired);
    returnselfOrThrowIfLocked(); }}Copy the code

Because the source code has fixed the sub-transformation injection port, unless we modify the source code compilation or ASM means. How to solve it? The first difficulty still GifLibDrawableTransformation then implement

class GifLibDrawableTransformation(wrapped: Transformation<Bitmap>) : Transformation<GifDrawable> {

    private val wrapped: Transformation<Bitmap> = Preconditions.checkNotNull(wrapped)

    override fun transform(
        context: Context, resource: Resource<GifDrawable? >, outWidth:Int, outHeight: Int
    ): Resource<GifDrawable? > {val drawable = resource.get()
        drawable.transform = object : Transform {
            private val mDstRectF = RectF()
            override fun onBoundsChange(rct: Rect) = mDstRectF.set(rct)
            override fun onDraw(canvas: Canvas, paint: Paint, bitmap: Bitmap) {
                val bitmapPool = Glide.get(context).bitmapPool
                val bitmapResource: Resource<Bitmap> = BitmapResource(bitmap, bitmapPool)
                val transformed = wrapped.transform(context, bitmapResource, outWidth, outHeight)
                val transformedFrame = transformed.get()
                canvas.drawBitmap(transformedFrame, null, mDstRectF, paint)
            }
        }
        return resource
    }
    ...
}

// Insert each time transform is called
val circleCrop = CircleCrop()
IGlideModule.with(this)
    .load("http://tva2.sinaimg.cn/large/005CjUdnly1g6lwmq0fijg30rs0zu4qp.gif")
    .transform(GifDrawable::class.java, GifLibDrawableTransformation(circleCrop))
    .transform(circleCrop)
    .into(iv_2)
Copy the code

Defect conquered? In fact, there is no perfect solution, first of all, this writing method is not very convenient, second of all, there is still a problem with support for ScaleType.CENTER_CROP, etc., if you have better suggestions or can fix it, please submit pr.

conclusion

Glide is already very good, if only a small amount of GIF can be fully competent, and with the Android version and hardware upgrade, these performance problems are less and less, but if you find that the use of GIF in the project caused more oom problems, you can try to optimize, in addition to reduce the phone heat consumption issues. In addition, such as Glide does not support webP GIFs, using the above principle, as long as you can find the parse and serialization of webP logic can be supported, life is not over and over.

For all the above code, see github.com/forJrking/G… , made a simple jitpack warehouse, there may be other bugs please submit the issue and PR. Read the source code documentation, anal code is not easy to give a like support, let me have the power of continuous output.