<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>

          通過使用協(xié)程改善APngDrawable

          共 6994字,需瀏覽 14分鐘

           ·

          2020-09-23 22:18

          作者:mjlong123
          來源:SegmentFault 思否社區(qū)



          背景


          APngDrawable在播放apng文件的過程中,解碼線程會經(jīng)常的發(fā)生掛起。為了充分的利用線程,避免掛起線程,并且簡化幀播放邏輯。所以我們考慮使用協(xié)程來解決這些問題。




          協(xié)程


          協(xié)程可以掛起執(zhí)行,這里的掛起執(zhí)行與線程的掛起不同。它沒有阻塞線程,而是記錄當(dāng)前執(zhí)行的位置。當(dāng)異步執(zhí)行結(jié)束后從記錄的執(zhí)行位置繼續(xù)執(zhí)行,掛起前后的執(zhí)行線程有可能不同。利用協(xié)程的非阻塞特性可以有效優(yōu)化apng文件的解碼過程。




          協(xié)程在解碼過程中的使用


          啟動播放apng的過程就是啟動協(xié)程任務(wù)的過程。協(xié)程的協(xié)程體中進(jìn)行循環(huán)播放控制,幀解碼控制,幀渲染控制。下面看下具體的代碼:


          playJob?=?launch(Dispatchers.IO)?{

          ????????????/**
          ?????????????*?for?decode?the?apng?file.
          ?????????????*/
          ????????????var?aPngDecoder:?APngDecoder??=?null
          ????????????frameBuffer?=?FrameBuffer(columns,?rows)
          ????????????try?{
          ????????????????//?send?start?event.
          ????????????????sendEvent(PlayEvent.START)
          ????????????????//Loop?playback.
          ????????????????repeat(plays)?{?playCounts?->
          ????????????????????log?{?"play?start?play?count?:?$playCounts"?}
          ????????????????????if?(playCounts?>?0)?{
          ????????????????????????//send?repeat?event.
          ????????????????????????sendEvent(PlayEvent.REPEAT)
          ????????????????????}
          ????????????????????//init?apng?decoder?and?frame?buffer.
          ????????????????????if?(aPngDecoder?==?null)?{
          ????????????????????????aPngDecoder?=?APngDecoder(streamCreator.invoke())
          ????????????????????????frameBuffer!!.reset()
          ????????????????????}
          ????????????????????aPngDecoder?.let?{?decoder?->
          ????????????????????????log?{?"decode?start?decoder?${decoder.hashCode()}?skipFrameCount?$skipFrameCount"?}
          ????????????????????????//seek?to?the?last?played?frame.
          ????????????????????????repeat(skipFrameCount)?{
          ????????????????????????????decoder.advance(frameBuffer!!.bgFrameData)
          ????????????????????????}
          ????????????????????????//decode?the?left?frames
          ????????????????????????repeat(frames?-?skipFrameCount)?{
          ????????????????????????????var?time?=?System.currentTimeMillis()
          ????????????????????????????decoder.advance(frameBuffer!!.bgFrameData)
          ????????????????????????????time?=?System.currentTimeMillis()?-?time
          ????????????????????????????//compute?the?delay?time.?We?need?to?minus?the?decode?time.
          ????????????????????????????val?delay?=?frameBuffer!!.fgFrameData.delay?-?time
          ????????????????????????????skipFrameCount?=?frameBuffer!!.bgFrameData.index?+?1
          ????????????????????????????logFrame?{?"decode?frame?index?${frameBuffer!!.bgFrameData.index}?skipFrameCount?$skipFrameCount?time?$time?delay?$delay"?}
          ????????????????????????????delay(delay)
          ????????????????????????????//swap?the?frame?between?fg?frame?and?bg?frame.
          ????????????????????????????frameBuffer?.swap()
          ????????????????????????????//send?frame?event.
          ????????????????????????????sendEvent(PlayEvent.FRAME)
          ????????????????????????}
          ????????????????????????//close?the?apng?decoder.
          ????????????????????????decoder.close()
          ????????????????????????skipFrameCount?=?0
          ????????????????????????aPngDecoder?=?null
          ????????????????????????log?{?"decode?end?release?decoder?${decoder.hashCode()}"?}
          ????????????????????}
          ????????????????????log?{?"play?end?play?count?:?$playCounts"?}
          ????????????????}
          ????????????????//play?end,?reset?the?start?state?for?next?time?to?restart?again.
          ????????????????isStarted?=?false
          ????????????????sendEvent(PlayEvent.END)
          ????????????}?catch?(e:?Exception)?{
          ????????????????log?{?"launch??Exception?${e.message}"?}
          ????????????????//send?cancel?event.
          ????????????????sendEvent(PlayEvent.CANCELED)
          ????????????}?finally?{
          ????????????????log?{?"release?decoder?and?frameBuffer?in?finally"?}
          ????????????????aPngDecoder?.close()
          ????????????????lastFrameData?.release()
          ????????????????lastFrameData?=?frameBuffer?.cloneFgBuffer()
          ????????????????frameBuffer?.release()
          ????????????}
          ????????}


          這里應(yīng)用到了協(xié)程的repeat方法控制循環(huán)播放和循環(huán)解碼frame,同時配合協(xié)程的delay方法控制幀的渲染時間。通過協(xié)程改造后的邏輯簡單清晰,更加容易理解。


          渲染的delay時間需要考慮到解碼frame的時間,這里的delay時間是將解碼時間排除掉后的時間。通過下面的圖可以方便理解:


          圖片反映的是一幀的解碼和渲染過程,由于draw frame的速度很快,所以它的執(zhí)行時間忽略不計。所以draw frame的開始點也是下一幀解碼的開始點。每一幀都是按照這樣的邏輯反復(fù)執(zhí)行。


          由于解碼的協(xié)程執(zhí)行在IO Dispatcher中,而渲染幀是在UI 線程,所以這里需要考慮多線程協(xié)同的問題。也就是說draw frame執(zhí)行在main ui線程。描畫時使用的幀數(shù)據(jù)和解碼的幀數(shù)據(jù)需要保證不是同一個數(shù)據(jù)。為了解決這個問題,我們定義了一個FrameBuffer用于控制解碼與渲染,讓他們可以協(xié)調(diào)工作。




          FrameBuffer的使用


          下面是FrameBuffer的完整代碼,代碼還是比較簡單的。它通過定義前臺frame和后臺frame來達(dá)到解碼與渲染的協(xié)同工作。前臺frame只用于渲染圖像,后臺frame只用于解碼使用。這樣他們兩個就各自工作而相互不影響。當(dāng)后臺frame解碼完成并且delay時間已經(jīng)到時,程序會通過調(diào)用swap方法切換前后臺frame。


          internal?class?FrameBuffer(w:?Int,?h:?Int)?{
          ????var?prFrameData:?FrameData?=?FrameData(Bitmap.createBitmap(w,?h,?Bitmap.Config.ARGB_8888))
          ????var?fgFrameData:?FrameData?=?FrameData(Bitmap.createBitmap(w,?h,?Bitmap.Config.ARGB_8888))
          ????var?bgFrameData:?FrameData?=?FrameData(Bitmap.createBitmap(w,?h,?Bitmap.Config.ARGB_8888))

          ????fun?swap()?{
          ????????val?temp?=?prFrameData
          ????????prFrameData?=?fgFrameData
          ????????fgFrameData?=?bgFrameData
          ????????bgFrameData?=?temp
          ????}

          ????fun?reset()?{
          ????????fgFrameData.reset()
          ????????prFrameData.reset()
          ????????bgFrameData.reset()
          ????}

          ????fun?release()?{
          ????????fgFrameData.release()
          ????????prFrameData.release()
          ????????bgFrameData.release()
          ????}

          ????fun?cloneFgBuffer()?=?FrameData(Bitmap.createBitmap(fgFrameData.bitmap))
          }





          如何共享APng播放


          有的時候我們需要在同一個畫面下播放多個同一個APng 文件,如果為每個播放都創(chuàng)建一個解碼用的APngHolder,那么內(nèi)存使用就會增加。我們可以通過共享APngHolder的方式來解決這個問題。在庫中我們定義了一個APngHolderPool用于管理共享的APngHolder。下面是這個類的代碼:


          class?APngHolderPool(private?val?lifecycle:?Lifecycle)?:?LifecycleObserver?{
          ????private?val?holders?=?mutableMapOf()

          ????init?{
          ????????lifecycle.addObserver(this)
          ????}

          ????@OnLifecycleEvent(Lifecycle.Event.ON_START)
          ????fun?onStart()?{
          ????????holders.forEach?{
          ????????????it.value.resume(true)
          ????????}
          ????}

          ????@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
          ????fun?onStop()?{
          ????????holders.forEach?{
          ????????????it.value.pause(true)
          ????????}
          ????}

          ????@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
          ????fun?onDestroy()?{
          ????????holders.clear()
          ????????lifecycle.removeObserver(this)
          ????}

          ????internal?fun?require(scope:?CoroutineScope,?file:?String,?streamCreator:?()?->?InputStream)?=
          ????????holders[file]??:?APngHolder(file,?true,?scope,?streamCreator)
          ????????????.apply?{
          ????????????????holders[file]?=?this
          ????????????????if?(lifecycle.currentState?>=?Lifecycle.State.STARTED)?{
          ????????????????????resume(true)
          ????????????????}
          ????????????}
          }


          通過代碼我們也能發(fā)現(xiàn)通過APngHolderPool管理的APngHolder的播放停止等動作只與lifecycle綁定,共享的APngHolder不會因為APngDrawable的隱藏和銷毀而停止播放并釋放。所以大家在使用共享的APngHolder的時候要考慮是否真正需要它。下面的代碼展示了如何使用APngHolderPool。


          val?sharedAPngHolderPool?=?APngHolderPool()
          ????fun?onClickView(view:?View)?{
          ????????when?(view.id)?{
          ????????????R.id.image1?->?{
          ????????????????imageView.playAPngAsset(this,?"google.png",?sharedHolders?=?sharedAPngHolderPool)
          ????????????}
          ????????????R.id.image2?->?{
          ????????????????imageView.playAPngAsset(this,?"blued.png")
          ????????????}
          ????????????R.id.imageView?->?(imageView.drawable?as??APngDrawable)?.let?{
          ????????????????if?(it.isRunning)?{
          ????????????????????it.stop()
          ????????????????}?else?{
          ????????????????????it.start()
          ????????????????}
          ????????????}
          ????????}
          ????}




          總結(jié)


          經(jīng)過協(xié)程改造過的解碼過程和渲染過程更加簡潔清晰了,也達(dá)到了最初的改造目的。并且通過kotlin的擴(kuò)展支持,使得播放APng的調(diào)用也更加簡單。下面我分享了整個的代碼,其中也包括了改造前的代碼。大家可以對照下,相信協(xié)程實現(xiàn)的優(yōu)點顯而易見。




          Git


          大家可以通過下面的git地址下載到完整的代碼。
          https://github.com/mjlong123123/PlayAPng/releases/tag/1.0.1





          點擊左下角閱讀原文,到?SegmentFault 思否社區(qū)?和文章作者展開更多互動和交流。

          -?END -

          瀏覽 30
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <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>
                  色青草无码视屏 | 九哥操屄网 | 欧美三级午夜理伦三级18禁 | 青娱乐成年人视频 | 大鸡巴插逼视频 |