如何絲滑般地加載超大gif圖?
作者: forJrking?
https://juejin.cn/user/2612095355987191
Why
為何要優(yōu)化glide的gif support呢?要回到2年前,我們需要在頁面支持很多png或者gif的圖作為活動(dòng)氛圍的背景,而運(yùn)營商給的gif圖都很大(>5mb),就會出現(xiàn)內(nèi)存抖動(dòng)致APP卡頓,還有g(shù)if會掉幀,雖然通過gif壓縮可以減小體積,但是顯示效果會大打折扣。調(diào)研加載支持gif的圖片加載庫,也只有g(shù)lide還有Fresco了。而項(xiàng)目已經(jīng)有g(shù)lide了,那么我們需要的就是去做優(yōu)化了。(額外說下Fresco還支持webp動(dòng)圖)
那么放在現(xiàn)在,glide本身對gif的支持優(yōu)化已經(jīng)很多了,之前多個(gè)gif同時(shí)渲染的內(nèi)存抖動(dòng)問題已經(jīng)沒了,掉幀問題也有優(yōu)化但是還是存在。但是內(nèi)存占用還有cpu占用率卻還是比優(yōu)化的版本差,今天就來分享下如何優(yōu)化。
優(yōu)化前后
一加1手機(jī)android6.0,加載6張2-5mb的gif圖。

How
要優(yōu)化首先要了解gif的特性,glide如何渲染gif的。由于源碼的剖析過程非常長,都可以單獨(dú)出個(gè)文章了。這里只說下要點(diǎn):
gif特性
gif文件的文件頭前3個(gè)字節(jié)必然為'G''I''F' gif中的每一幀圖片尺寸相同 gif中每幀會有間隔時(shí)間
ImageHeaderParserUtils.getType(..)檢測資源是否為gif com.bumptech.glide.load.resource.gif.GifDrawable為最終渲染gif的drawable StreamGifDecoder和ByteBufferGifDecoder把流轉(zhuǎn)換為GifDrawable GifDrawableEncoder把GifDrawable轉(zhuǎn)換為File 以上組件模塊在com.bumptech.glide.Glide的構(gòu)造方法內(nèi)進(jìn)行注冊組裝,而且支持注冊自己的組件
優(yōu)化的技術(shù)選型

融合glide
GifLibDecoder????????????解析io?InputStream?實(shí)際是獲取byte[]交給下面的解析器?
GifLibByteBufferDecoder??解析?byte[]生成?GifDrawable的?包裝?GifLibDrawableResource
GifLibDrawableResource???封裝GifDrawable提供銷毀和內(nèi)存占用大小計(jì)算(用于lrucache)
DrawableBytesTranscoder和GifLibBytesTranscoder??用于轉(zhuǎn)換??
GifLibEncoder????????????用于序列化成文件

class?GifLibByteBufferDecoder?...
????@Throws(IOException::class)
????override?fun?handles(source:?ByteBuffer,?options:?Options):?Boolean?{
????????//必須要?開啟anim
????????val?isAnim?=?!options.get(GifOptions.DISABLE_ANIMATION)!!
????????//根據(jù)文件頭判斷是否是gif
????????val?isGif?=?ImageHeaderParserUtils.getType(parsers,?source)?==?ImageType.GIF
????????//?DES:?此日志主要關(guān)注?gif圖并且?設(shè)置了不允許動(dòng)畫的地方
????????if?(isGif)?Log.e(TAG,?"gif?options?anim?->$isAnim")
????????return?isAnim?&&?isGif
????}
????/**解析方法*/
????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
????????????}
????????????//進(jìn)行采樣設(shè)置
????????????val?sampleSize?=?getSampleSize(header,?width,?height)
????????????//創(chuàng)建解析器構(gòu)建模式
????????????val?builder?=?GifDrawableBuilder()
????????????builder.from(byteBuffer)
????????????builder.sampleSize(sampleSize)
????????????builder.isRenderingTriggeredOnDraw?=?true
//????????????pl.droidsonroids.gif.GifOptions?gifOptions?=?new?pl.droidsonroids.gif.GifOptions();
//????????????DES:?不含透明層可以加速渲染?但是透明的gif會渲染黑色背景
//????????????gifOptions.setInIsOpaque();
????????????val?gifDrawable?=?builder.build()
????????????val?loopCount?=?gifDrawable.loopCount
????????????if?(loopCount?<=?1)?{
????????????????//循環(huán)一次的則矯正為無限循環(huán)
????????????????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))
????????}
????}
}
class?GifLibEncoder?:?ResourceEncoder<GifDrawable?>?{
????override?fun?getEncodeStrategy(options:?Options):?EncodeStrategy?{
????????return?EncodeStrategy.SOURCE
????}
????override?fun?encode(data:?Resource,?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:?將?resource?編碼成文件
????????????Log.d(TAG,?"GifLibEncoder?->?$success?->?${file.absolutePath}")
????????}
????????return?success
????}
}
通過Registry注冊組件
append(..)追加到最后,當(dāng)內(nèi)部的組件在 handles()返回false或失敗時(shí)候使用追加組件 prepend(..)追加到前面,當(dāng)你的組件在失敗時(shí)候使用原生提供組件 replace(..)替換組件 register(..)注冊組件
@JvmStatic
fun?registerGifLib(glide:?Glide,?registry:?Registry)?{
????//優(yōu)先使用gifLib-Gif
????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
????)
}
驗(yàn)證組件是否注冊成功
IGlide.with(view).load(url)
????.placeholder(R.color.colorAccent)
????.listener(object?:?RequestListener?{
????????override?fun?onResourceReady(
????????????resource:?Drawable?,?model:?Any?,
????????????target:?Target?,?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?,?isFirstResource:?Boolean) :?Boolean?=?false
????}).into(view)
log:?com.example.mydemo?D/TAG:?giflib的?Gifdrawable
transform缺陷
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);
????//對gifdrawble的?Transformation?支持緣由
????transform(GifDrawable.class,?new?GifDrawableTransformation(transformation),?isRequired);
????return?selfOrThrowIfLocked();
??}
}
class?GifLibDrawableTransformation(wrapped:?Transformation<Bitmap>)?:?Transformation<GifDrawable>?{
????private?val?wrapped:?Transformation?=?Preconditions.checkNotNull(wrapped)
????override?fun?transform(
????????context:?Context,?resource:?Resource,?outWidth:?Int,?outHeight:?Int :?Resource
????)? {
????????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?=?BitmapResource(bitmap,?bitmapPool)
????????????????val?transformed?=?wrapped.transform(context,?bitmapResource,?outWidth,?outHeight)
????????????????val?transformedFrame?=?transformed.get()
????????????????canvas.drawBitmap(transformedFrame,?null,?mDstRectF,?paint)
????????????}
????????}
????????return?resource
????}
????...
}
//每次調(diào)用?transform?時(shí)候注入下
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)
總結(jié)
https://github.com/forJrking/GlideGifLib

? 耗時(shí)2年,Android進(jìn)階三部曲第三部《Android進(jìn)階指北》出版!
? 『BATcoder』做了多年安卓還沒編譯過源碼?一個(gè)視頻帶你玩轉(zhuǎn)!
? 重生!進(jìn)階三部曲第一部《Android進(jìn)階之光》第2版 出版!
?BATcoder技術(shù)群,讓一部分人先進(jìn)大廠
大家好,我是劉望舒,騰訊TVP,著有三本業(yè)內(nèi)知名暢銷書,連續(xù)四年蟬聯(lián)電子工業(yè)出版社年度優(yōu)秀作者,百度百科收錄的資深技術(shù)專家。
想要加入?BATcoder技術(shù)群,公號回復(fù)
BAT?即可。
為了防止失聯(lián),歡迎關(guān)注我的小號
??微信改了推送機(jī)制,真愛請星標(biāo)本公號??
