前言
老話常談,我們每次引入新的優化手段,都需要詳細調研,明確優缺點,以及引入這項技術或者功能,能給現有的項目帶來什麼收益以及帶來哪些不便。
首先我們要搞明白爲什麼要優化包體積?普遍認爲的減少包體積有以下幾個好處:
- 下載轉換率,體積越小下載率越高
- 如果需要和廠商合作進行預裝,由於預裝空間是有限的,體積越小,成本越低
- 推廣一般按照流量收費,同理,安裝包體積越小,成本越低
- 減少應用佔用手機的儲存空間,一般100M的安裝包,安裝到手機上後一般會佔用200M,相應的對於中低端手機就不友好
接下來就詳細介紹如何對圖片進行優化。
背景
隨着項目的不斷迭代和開發人員的增多,多業務線同時進行開發,就會造成一些問題:
- 由於同時進行業務的迭代,可能會造成相同的圖片不同的命名放入項目中,造成圖片重複增加了包體積。
- 由於導入圖片是手動行爲,很可能造成導入過大的圖片。
- 存在第三方的module,第三方的aar中可能存在一些圖片過於大。
以上的問題都是日積月累形成的,而且難以察覺,但是積少成多,這部分的文件在包體積中,佔了不小的比重,而且這個行爲是一直存在的,這個時候就需要有一個自動的過程,來檢查整個過程或者是優化圖片佔用的大小,避免包體積以一種少爲人知的方式慢慢增加。
目標
我們的總目標是減少包體積,以及在以後的開發中避免類似的問題造成包體積的增加,爲了達到這個總目標,我們把目標更加具體化:
- 進一步壓縮圖片(png,jpeg,webp)
- 找出項目中內容一樣,但是命名和位置不一樣的圖片
- 自動化整個過程,並且對所有的開發同學來說透明
- 除了減少包體積外,不會出現crash以及顯示效果
壓縮圖片
我們在app中的圖片一般包含 .jpg
、.jpeg
、png
、webp
、.9.png
。對於圖片的選擇:沒有透明度的需求則選擇jpg和jpeg,相對png的文件體積更小。
PNG
PNG是包含透明度的圖片,顧名思義就是可以設置圖片的透明度,每一個色素點包含ARGB
四個通道,由於png是無損壓縮的圖片,對png進行壓縮沒有太好的收益,一般使用的工具有TinyPng這個網站,他們的算法是比較好的,可以將PNG壓縮到很小(有損壓縮,但是不影響觀看),大部分設計師應該都知道這個網站,這個網站也有api來統一壓縮圖片,但是缺點是收費,每天只要超過500次就需要收費,對於商用的app來說不太友好。開源的庫有pngquant,它的壓縮率近似TinyPng,但是壓縮速度非常的緩慢,好處是不收費。
JPEG和JPG
JPEG是沒有透明度的,每個色素點包含RGB
,沒有alpha通道並且是採用了標準的有損壓縮算法,可以進一步的壓縮圖片,現在開源比較好的壓縮工具有Guetzli,基本上在高質量視覺的情況下可以減小20~30%的文件大小
WebP
WebP是谷歌提供的一種支持有損壓縮和無損壓縮的圖片文件格式,而且可以提供比JPEG或PNG更好的壓縮。在Android 4.0(API level 14)中支持有損的WebP圖片,在Android 4.3(API level 18)和更高的版本中支持無損和透明的WebP圖像(我們的app minSdkVersion=21,所以不用擔心兼容問題),更加詳細的介紹可以參考官網介紹
從上面的比對中不難看出,在我們平常人的視覺中,基本是沒有區別的,我們也不會放大去看,所以在滿足用戶視覺需求的情況下,我們可以依照自己的需求對圖片進行壓縮或者轉換。
獲取項目中所有的圖片
獲取所有的圖片資源一般有兩種方案:
- 遍歷工程目錄中的所有文件,找出後綴爲圖片格式的文件,然後進行處理,這種方案有以下幾個缺陷
- 如果修改本地文件,必定會產生diff,需要生成新的commit,不合理
- 如果是第三方的aar,則遍歷不出圖片資源,不合理
- 在打包過程中,hook一個階段,拿到所有的資源文件,這個階段不會產生commit,而且可以拿到第三方aar的圖片資源,更加合理。
這裏需要一些額外的知識,需要了解一下apk打包過程
我們先打印出所有的Task
║ :app:checkDebugClasspath [153]
║ :app:preBuild [0]
║ :app:preDebugBuild [14]
║ :app:compileDebugAidl [1]
║ :app:compileDebugRenderscript [11]
║ :app:checkDebugManifest [1]
║ :app:generateDebugBuildConfig [13]
║ :app:mainApkListPersistenceDebug [9]
║ :app:generateDebugResValues [2]
║ :app:generateDebugResources [0]
║ :app:mergeDebugResources [2099]
║ :app:createDebugCompatibleScreenManifests [4]
║ :app:processDebugManifest [94]
║ :app:splitsDiscoveryTaskDebug [5]
║ :app:processDebugResources [420]
║ :app:compileDebugKotlin [4510]
║ :app:prepareLintJar [0]
║ :app:generateDebugSources [0]
║ :app:javaPreCompileDebug [14]
║ :app:compileDebugJavaWithJavac [714]
║ :app:compileDebugNdk [1]
║ :app:compileDebugSources [0]
║ :app:mergeDebugShaders [7]
║ :app:compileDebugShaders [5]
║ :app:generateDebugAssets [0]
║ :app:mergeDebugAssets [6]
║ :app:transformClassesWithDexBuilderForDebug [754]
║ :app:transformDexArchiveWithExternalLibsDexMergerForDebug [681]
║ :app:transformDexArchiveWithDexMergerForDebug [423]
║ :app:mergeDebugJniLibFolders [4]
║ :app:transformNativeLibsWithMergeJniLibsForDebug [294]
║ :app:checkDebugLibraries [1]
║ :app:processDebugJavaRes [1]
║ :app:transformResourcesWithMergeJavaResForDebug [415]
║ :app:validateSigningDebug [2]
║ :app:packageDebug [416]
║ :app:revertDebug [185]
║ :app:assembleDebug [0]
從上面的task中可以看到有一個generateDebugResources
的task,這個task就是生成所有的資源文件,然後需要通過反射調用MergeResources.computeResourceSetList
方法,即可獲取所有資源文件的路徑。
尋找內容相同圖片和大圖
在打包過程中能獲取所有資源文件的前提下,其實這個過程想起來很簡單,一般找出兩個相同的資源文件有兩個方案:
- 最初的方案是需要將所有的圖片(png,jpeg,jpg,webp)彙集到一個集合中,每個圖片跟剩下所有的圖片進行比對,而且是對比兩張圖片的所有像素點,這樣的話整個算法是非常複雜而且運算量非常的大,會極大的增加打包流程,直接pass。
- 將所有圖片大小相同的文件放入一個集合中,然後使用文件摘要算法計算出一個字符串,然後直接對字符串直接對比即可找出相同內容的圖片文件,然後將這兩個文件的路徑打印到一個文件中,就可以查看並且刪除相同的資源文件了。這樣就大大縮減了整個task的過程。
從上面的demo中可以看到,找出了兩張大的圖片,和相同的圖片,這樣我們就可以在項目中,將這些問題進行優化。
自動化
關於自動化,最好的實踐方式就是自定義Plugin,這個過程在打包階段,對於開發階段來說是透明的,怎樣實現自定義的Plugin,可以參考Gradle-自定義plugin,這裏的難點在於獲取所有資源的文件,上面已經說了大致的流程。
進一步壓縮圖片
從現有的資料中,在大體不失真的情況對圖片壓縮最多也就是減少75%,但是如果想進一步較少圖片,那麼就會影響用戶的視覺體驗,這種情況是不允許的,所以我們可以將所有的圖片轉換爲webp
,在不影響圖片質量的情況下極大的減少圖片,可以看下網上的一個對比圖片
將所有的圖片轉換爲webp的一個重要原因是,在現有所有開源的壓縮庫中,如果對png進行壓縮至25%,一張30kb的圖片將耗時6-8s,如果在工程中批量壓縮圖片,那麼增加的時長將不可想象,而將png或者jpeg轉換爲webp則僅僅需要不到100ms。而且在不影響性能和用戶體驗的前提下我們選擇將圖片轉換爲webp。
最佳實踐
以上描述中需要優化的點是:圖片壓縮, 雖然每種類型的圖片都可以壓縮,但是壓縮率以及壓縮時間在png上表現都不是很好,一個30kb的png圖片,使用pngquant壓縮,需要2s左右,如果是批量壓縮,那麼會非常的慢,所以這裏建議是如果app的 minSdkVersion >= 18
那麼完全可以將所有類型的圖片都轉換爲webp
,這樣不近能極大的縮短每張圖片處理時間而且圖片的壓縮率也非常的可觀,總結下整個流程。
- 自定義plugin,hook住
generateDebugResources
Task。 - 創建自己的Task,通過反射拿到所有資源的文件。
- 找出所有的相同內容的圖片,打印出來
- 如果
minSdkVersion >= 18
,則將所有的圖片都轉化爲webp
- 生成apk
實驗對比
以上整個流程達到的效果是:
- 不會引起bug或者crash
- 對業務開發的人員來說完全透明,不會影響到任何的開發效率
- 對於整個打包過程不會有太大的影響,不會過分的增加整個打包時長
channel | process | optimize |
---|---|---|
Debug | 3M -> 2.3M | 0.7M |