<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          如何絲滑般地加載超大gif圖?

          共 2294字,需瀏覽 5分鐘

           ·

          2021-11-18 07:55

          作者: 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特性


          1. gif文件的文件頭前3個(gè)字節(jié)必然為'G''I''F'
          2. gif中的每一幀圖片尺寸相同
          3. gif中每幀會有間隔時(shí)間



          glide支持


          1. ImageHeaderParserUtils.getType(..)檢測資源是否為gif
          2. com.bumptech.glide.load.resource.gif.GifDrawable為最終渲染gif的drawable
          3. StreamGifDecoder和ByteBufferGifDecoder把流轉(zhuǎn)換為GifDrawable
          4. GifDrawableEncoder把GifDrawable轉(zhuǎn)換為File
          5. 以上組件模塊在com.bumptech.glide.Glide的構(gòu)造方法內(nèi)進(jìn)行注冊組裝,而且支持注冊自己的組件

          優(yōu)化的技術(shù)選型

          優(yōu)化解析速度提升效率,使用giflib替換glide的java解析代碼提升效率。例如:giflib、android-gif-drawable、fresco。

          緩沖渲染,2個(gè)Bitmap容器輪流進(jìn)入子線程解析填充,之后在主線程渲染。


          根據(jù)上機(jī)實(shí)際表現(xiàn)android-gif-drawable,內(nèi)存占用和cpu占用率最好,而且提供了pl.droidsonroids.gif.GifDrawable并且擁有解析和序列化的api,而且作者在持續(xù)維護(hù),后期bug修復(fù)和項(xiàng)目其他需求支持均可以兼顧,選擇此第三方庫為gif解析和渲染核心。

          融合glide

          glide的gif之前前面已經(jīng)分析出來,我們只需要照貓畫虎實(shí)現(xiàn)對應(yīng)接口和類即可,copy修改開始,創(chuàng)建如下這些類。

          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(..)注冊組件



          注冊組件,用glide注解類繼承AppGlideModule并在registerComponents(..)中調(diào)用如下fun:

          @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缺陷

          這樣做看起來侵入性很低的替換了Glide的gif支持,并且還可以兼容giflib出錯(cuò)后使用原生組件,那么缺點(diǎn)呢?缺點(diǎn)也是非常頭疼,通常我們會對一些圖片加載需求做一些圓角或者圓形等等處理。glide自己的GifDrawable支持的很好,幾乎所有的BitmapTransformation都支持,而我們的缺失效了,究其原因是源碼中所有transform設(shè)置最終調(diào)用到如下:

          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();
          ??}
          }

          由于源碼已經(jīng)固定了次轉(zhuǎn)換注入口,除非我們自己修改源碼編譯或者asm手段。如何解決呢?先依舊照貓畫虎GifLibDrawableTransformation然后實(shí)現(xiàn)。

          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)

          缺陷攻克了?其實(shí)還沒有完美解決,一來這樣的書寫方式不是很方便,二來目前對ScaleType.CENTER_CROP等支持還是有問題,如果你有更好的建議或者可以修復(fù)請?zhí)峤籶r。

          總結(jié)

          glide已經(jīng)非常優(yōu)秀了,如果是僅僅少量使用gif完全可以勝任了,而且隨著android版和硬件的升級,這些性能問題越來越少,但是如果你發(fā)現(xiàn)項(xiàng)目中因?yàn)間if的使用導(dǎo)致oom的問題較多可以嘗試次優(yōu)化,另外也可以降低手機(jī)發(fā)熱耗電問題。另外比如glide還不支持webp動(dòng)圖,利用上面的原理,只要找到可以解析和序列化的webp邏輯就可以支持了,生命不息折騰不止啊。

          以上所有代碼請參見如下地址:


          https://github.com/forJrking/GlideGifLib



          做了簡單的jitpack倉庫,可能還有其他bug請?zhí)峤籭ssue和pr。閱讀源碼整理文檔資料,肛代碼不易請給個(gè)贊表示支持,讓我有持續(xù)輸出的動(dòng)力。




          ? 耗時(shí)2年,Android進(jìn)階三部曲第三部《Android進(jìn)階指北》出版!

          ? 『BATcoder』做了多年安卓還沒編譯過源碼?一個(gè)視頻帶你玩轉(zhuǎn)!

          ? 『BATcoder』我去!安裝Ubuntu還有坑?

          ? 重生!進(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)本公號??
          瀏覽 64
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  操操网继续操用力豆花 | 特级毛片www | 十八女人高潮A片免费 | 男女猛干直接看 | 啪一啪操一操 |