從零開始擼一個Fresco之gif和Webp動畫

weixin_34253539發表於2018-02-02

上一篇文章的連結:從零開始擼一個Fresco之硬碟快取 轉載請註明出處 Fresco原始碼文件翻譯專案請看這裡:Fresco原始碼翻譯專案 這個專案會不斷更新想學習Fresco原始碼的同學一定不要錯過。 Fresco中有個很重要的功能就是gif和Webp動畫的實現,今天我就來講解一下這個模組,順便擼了個模組demo出來。這是專案的github地址Fresco動畫模組,推薦看部落格的時候結合專案一起看,專案中絕大部分類都有細緻的註釋,看起來還是很清晰的。

一、專案包結構

  • 1.animated:
    • 1.gif:這個包中的兩個類都使用了jni程式碼,GifImage有兩個功能:1.用於將Gif動畫已解碼資料儲存在jni程式碼管理的本地記憶體中2.通過jni程式碼解析未解碼的Gif資料。GifFrame則是儲存Gif動畫單個幀的資料也是通過jni程式碼管裡的本地記憶體。
    • 2.webp:WebPImage類似前面的GifImage,只不過換成了Webp的資料。WebPFrame同理類似GifFrame
  • 2.bitmapFactory:無論是動畫的幀還是靜態的圖片,最後都需要建立為Bitmap顯示在View上。而不同Android版本建立Bitmap的方式是不同的,這裡的工廠不同的工廠就是用於不同Android版本建立Bitmap這裡的工廠用到了後面的platformDecoder包中的解碼器,platformDecoder包中的類才是在不同Android版本下建立Bitmap的具體程式碼邏輯。
  • 3.cacheKey:每一個Key對應一個圖片或動畫。
  • 4.common:
    • 1.nativeLoader:每一個Key對應一個圖片或動畫。
    • 2.s:不同類的工具方法,如Ints中有int的工具方法。
    • 3.stream:簡單的基於Java流的包裝類
    • 4.util:工具類
  • 5.excutor:
    • 1.executorSupplier:包的實現在DefaultExecutorSupplier中,用於提供不同的Executor
    • 2.handlerExcutor:用於提供轉移到Handler執行緒的Excutor,唯一實現是UiThreadImmediateExecutorService,轉到主執行緒。
    • 3.serailExcutor:用於提供序列執行任務的Excutor,DefaultSerialExecutorService是具體實現。
  • 6.image:包對外的實現是CloseableAnimatedImage和CloseableStaticBitmap,一個用於封裝動畫資料,一個用於封裝靜態圖片資訊。
    • 1.imageDecode:AnimatedImageDecoder的實現為animated包下的GifImage和WebPImage,用於解碼未解碼的動畫資料。ImageDecoder的實現是DefaultImageDecoder用於解碼所有影像資料,其用到了AnimatedImageFactoryImpl以提供CloseableAnimatedImage。
    • 2.imageFormat:這個包用於檢測EcodingImage類中的資料是什麼型別的影像資料。
    • 3.imageInfo:用於儲存圖片的簡單資訊和圖片目前的質量。
  • 7.imagepipeline:
    • 1.memory:NativeMemoryChunk是本地記憶體塊的java封裝,用於提供一塊本地記憶體,這裡的本地記憶體使用jni程式碼管理。NativePooledByteBuffer則是基於NativeMemoryChunk提供了一個位元組池,用來提供可回收使用的位元組陣列。
    • 2.nativecode:各種使用到了jni程式碼的java封裝類
  • 8.platformDecoder:bitmapFactory包中各個工廠生成Bitmap的具體實現包。
  • 9.pool:這個包裡是各種資源可回收使用的物件池子,如Bitmap和Byte陣列等等。這樣的好處是減少記憶體頻繁GC帶來的卡頓。
    • 1.poolFactory:這個包的對外實現是PoolFactory,用於提供各種Pool。
    • 2.poolParams:這個包中提供各種Pool的引數。
    • 3.poolUtilpool的工具包,BitmapCounter用於計數Bitmap,保持跟蹤總的byte的數量,和Bitmap數量。PoolStatsTracker用來跟蹤各種Pool的操作。
  • 10.refrence:包對外提供的實現是CloseableReference,以引用計數的方式將一些可關閉的大資料塊關閉。類似JVM的記憶體回收,當引用計數為0時,記憶體會自動釋放。
  • 11.webpsupport:在Android2.3以下是不支援Webp的,這個包中的類就是用來讓2.3一下的Android機可以使用Webp。
  • 12:factoryAndProvider:這個包就是動畫的主要實現邏輯, 最終提供的是AnimatedDrawable類,這個類只要直接設定在Veiw上就能使View顯示Gif或者Webp的動畫。這個包我使用樹狀層次來描述各個類之間的使用關係,所以比較複雜,大家可以結合後面的圖片一起觀看。AnimatedFactoryProvider用於提供一個AnimatedFactory
    • 1.animatedFactory:AnimatedFactory用於返回建立一個Gif和Webp動畫的兩個重要工廠:AnimatedDrawableFactory和AnimatedImageFactory。AnimatedFactoryImpl是具體實現。
      • 1.animatedImageFactory:AnimatedImageFactory用於將EncodedImage這個未解碼的Gif或者Webp的資料類,解碼成CloseableImage,如果解碼成功這裡的CloseableImage的實現是CloseableAnimatedImage。AnimatedImageFactoryImpl是AnimatedImageFactory的具體實現。
        • 1.animatedImage:這裡個包的主要實現是AnimatedImageResult,上一級目錄中說的CloseableAnimatedImage中的Gif和Webp解碼後的資料就是用這個AnimatedImageResult包裝。其內部儲存了animated包中的類:整個動畫、每幀畫面和預覽幀。
      • 2.animatedDrawableFactory:這裡的AnimatedDrawableFactory用於將AnimatedImageFactory提供的CloseableImage解析成AnimatedDrawable。具體實現是AnimatedDrawableFactoryImpl。
        • 1.animatedDrawable:包對外提供的實現是AnimatedDrawable,該類實現了Drawable,用於將AnimatedDrawableBackend提供的動畫資料繪製在該Drawable設定測View上。
        • 2.animatedBackend用於封裝CloseableAnimatedImage提供的AnimatedImageResult。包對外提供的實現是AnimatedDrawableBackendImpl和AnimatedDrawableCachingBackendImpl,AnimatedDrawableCachingBackendImpl用於在AnimatedDrawableCachingBackend之上封裝一個快取功能。
        • 3.other:一些輔助AnimatedDrawable的類

二、物件池Pool

先來講講pool包中的物件池,物件池有什麼用?當我們使用一個頻繁建立和銷燬的物件的時候,為了減少建立和銷燬物件所帶來的消耗,我們可以維持一個該物件的集合,當不使用的時候將物件放回集合中,使用的時候直接獲取引用賦予值。一個典型的物件池就是執行緒池。在Fresco中由於要頻繁地對Bitmap進行操作,所以對Bitmap我們可以使用物件池,此外還有byte陣列等。

  • 1.先來介紹Pool所用到的資料結構:
    • 1.以Bitmap為例要重新使用一個Bitmap,就需要預期的Bitmap與重用的Bitmap使用的記憶體位元組數相同或者重用的大於預期的,只有這樣預期的Bitmap才能完整地放入被重用的Bitmap中。所以這裡我們用到了SparseArray(稀疏陣列)和Bucket(桶,自定義的類,內部使用了LinkedList)。首先SparseArray的下標表示記憶體的位元組數,由於位元組數一般跨度比較大所以使用了SparseArray。SparseArray中儲存著Bucket,Bucket表示當兩個可以被重用的Bitmap位元組數相同時,使用LinkedList進行排列儲存。下面的圖簡單的描述了一下這個資料結構。
    • 2.上面的這一個Pool是基於Java記憶體分配,但是我們都知道一個app能使用的記憶體是有限制的,因為使用new和建立Bitmap的時候使用的記憶體都是通過dalvik虛擬機器在java堆上分配記憶體的。Android系統設定了一個Java堆的閾值(48M、24M、16M等)當超出之後就會報OOM。而使用jni程式碼在native heap上面可以申請的記憶體卻是不受限制的(只受整個手機的記憶體限制)。所以Fresco當然使用了這個方式以提供Byte陣列池。具體封裝了jni管理的本地記憶體的類是imagePipline.memory包下的NativeMemoryChunk類。這裡的NativeMemoryChunk只替代了1中申請記憶體的方式,其他方面不變。
  • 2.總結:在Fresco中一般的靜態圖片的資料使用的是BitmapPool,這裡使用的是java堆上的記憶體。而動態圖片類似Gif和Webp,則是使用Native記憶體

三、AnimatedDrawable

上面的圖是factoryAndProvider包中類的結構示意圖,一定要結合專案一起觀看。AnimatedDrawable顧名思義就是一個可以顯示動畫的Drawable。Android的View在設計的時候為了讓Drawable能夠實現動畫,特意實現了Drawable.CallBack介面。這個介面可以讓Drwable對View顯示的影像進行排程。AnimatedDrawable就是通過這個機制實現動畫的。

  • 1.如何建立一個AnimatedDarwable,由上面的圖可以看出有以下幾個步驟:
    • 1.AnimatedFactoryProvider提供一個AnimatedFactoryImpl。
    • 2.AnimatedFactoryImpl提供一個AnimatedDrawableFactoryImpl和AnimatedImageFactoryImpl。
    • 3.AnimatedImageFactoryImpl將EncodedImage通過GifImage和WebpImage轉換成一個AnimatedImageResult並用CloseableAnimatedImage包裝。
    • 4.AnimatedDrawableFactoryImpl通過傳入的CloseableAnimatedImage獲取AnimatedImageResult,然後包裝成一個AnimatedDrawableCachingBackendImpl。
    • 5.AbstractAnimatedDrawable繼承了Drawable然後內部整合了AnimatedDrawableCachingBackendImpl。最後通過Drawable.CallBack介面在View上繪製AnimatedDrawableCachingBackendImpl中提供的每一幀的資料。
    • 6.以上就是簡述AnimatedDrawable建立的全過程,專案中有詳細的註釋,大家可以跟著上面這幾個步驟觀看專案原始碼
  • 2.AnimatedDrawable顯示動畫的流程:我在專案中的AbstractAnimatedDrawable類的開頭,詳細地描述了AnimatedDrawable的兩種啟動動畫的方式,大家可以順著專案中描述的路線觀看

四、專案使用

在administrator.myanimated包下有個MainActivity,用來演示png、jpg、靜態webp、動態webp、gif這五種影像的展示。大家在使用的時候記得將自己準備的這個幾種檔案按命名,放入app的快取資料夾裡。

相關文章