1 APK瘦身的價值
應用的頻繁疊代和功能的不斷完善使得應用體積迅速增長,同時也促使開發者采用多種手段減小應用的包體積,其可帶來兩方面價值。
經濟價值:通常使用者在下載下傳應用時會特别關注APK的大小,特别是當使用者處在非WiFi網絡環境下載下傳應用,必然會消耗更多的流量和增加更多的費用。
使用者體驗價值:APK的大小還會直接影響安裝速度和加載速度,其對記憶體的使用以及手機資源的消耗在配置不那麼高的裝置上體驗極為明顯。
2 APK組成結構
在使用一些很酷的方法來減少應用程式的大小之前,必須先了解實際的APK檔案格式。簡單地說,APK是一個包含檔案/檔案夾的壓縮檔案。作為一個開發者,我們可以很容易的通過打開壓縮檔案的方式檢視到APK裡面的内容。
a) 7zip打開APK後的視圖:
b) 各個檔案或者檔案夾的功能:
3 APK瘦身方案
通過上面的分析,已經了解了APK的基本構成。下面我們就采用多種手段進行APK瘦身
3.1 針對整體優化
3.1.1 插件化
從應用功能擴張的角度看,APK包體積的增大是必然的,然而插件技術的出現很好的解決了這個問題。通過分離應用中比較獨立的子產品,然後以插件的形式進行加載,從伺服器下載下傳,然後以插件的方式加載到我們的主工程。
3.1.2 7ZIP壓縮
一般情況下面,AS直接編譯生成的APK裡面,.arsc檔案是沒有進行任何壓縮的,前文中APK組成部分的第一張圖就可以看出。下面,我們來解壓APK,重新用7zip進行壓縮,就會發現幾乎所有檔案都變小了,特别是.arsc檔案,減小的比較多。
對比7zip壓縮前和壓縮後APK裡面檔案的變化,可以看出通過7zip壓縮,.arsc檔案大概減小了2M多,其它檔案/檔案夾體積也減小了5%左右。
3.1.3 簽名方式
Google在Android7.0系統提供了新的apksigner簽名工具,相比使用java提供的jarsigner簽名工具,APK體積可以減小約5%(依賴檔案數量)。
上圖中間是未簽名的APK,左邊是jarsigner簽名的,右邊是apksigner簽名的。
對比未簽名的APK,用jarsigner簽名工具簽名,APK裡面所有壓縮後的檔案和檔案夾體積都增大了;而apksigner簽名工具簽名,除了META_INF檔案夾增大了以外,其它檔案和檔案夾的大小都沒有改變。
新的apksigner工具已經內建到Android 7.0 SDK中了,使用方法可以參考官方文檔:https://developer.android.com/studio/command-line/apksigner.html。
3.2 針對資源優化
上面描述了對整體檔案的優化,下面我們來針對資源檔案進行優化。
3.2.1 移除重複的資源
a) 一套資源
Android在适配圖檔資源的時候,如果隻有一套資源,低密度手機會縮放圖檔,高密度手機會拉伸圖檔。我們利用這個特性,存放一套資源圖就可以供所有密度的手機使用。綜合考慮圖檔清晰度,靜态大小和記憶體占用情況,一般采用xhdpi下的資源圖檔。
b) 重複資源
很多時候,随着工程的增大,以及開發人員的變動,有些資源檔案名字不同,但是内容卻完全相同。我們可以通過掃描檔案的MD5值,找出名字不同,内容相同的圖檔并删除,做到圖檔不重複。
3.2.2 移除無用的資源
由于項目的疊代以及UI改版等各種因素,會導緻工程項目裡面有許多無用的資源的存在,定期掃描處理無用資源。
a) 通過Lint工具掃描工程資源
當Lint工具掃描發現無用資源的時候,會輸出如下的資訊,就可以删除這種資源。
需要特别注意的是,需要確定不存在反射,資源拼接等通路這些資源,才可以安全的删除掉這些資源,進而減小資源個數。
b) 通過Gradle參數配置
如果工程比較大,由主工程和多個子工程組成的話,子工程裡面也可能包含很多的無用資源。可以通過設定shrinkResources=true讓Gradle移走無用的資源,否則預設情況下,Gradle編譯隻會移除無用代碼,而不會關心無用資源。
需要特别注意的是shrinkResources依賴于minifyEnabled,必須和minifyEnabled一起用,即打開shrinkResources也必須打開minifyEnabled。
c) 通過開源掃描工具
大家可能會發現Lint不是非常好用,當工程裡面存在反射,過濾結果非常麻煩。是以我們實作了一個資源掃描的工具(https://github.com/zhuzhumouse/ScanUnusedResouce ),可以過濾掉通過反射調用的資源。原理就是把所有java和xml檔案以字元串掃描到記憶體,然後拿到資源檔案(xml,png,jpg等)名稱做比對查找,如果沒有比對到,該資源就是無用資源,可以直接删除。
該掃描工具可以解決反射調用的問題,但是不能解決資源拼接的問題,還有就是不能處理存在很多資源字首相同的情況。
3.2.3 png圖檔壓縮
可以通過使用圖檔壓縮工具對png圖檔進行壓縮,壓縮效果比較好的工具有pngcrush,pngquant,zopflipng等,可以在保持圖檔品質的前提下,縮減圖檔的大小。還可以通過網站對圖檔進行壓縮,如比較有名的www.tinypng.com,該網站對上傳的圖檔自動選擇合适的壓縮算法,壓縮比比較高,但是隻支援500張免費圖檔,更多圖檔處理是要收費的。
3.2.4 采用WebP格式
WebP是Google推出的影像技術,Google大力提倡WebP在網頁上進行應用,近兩年在Android應用中也開始推廣使用。
目前4.2及以上的手機系統已經支援WebP的無損和有損壓縮,但是4.0,4.1的手機系統支援隻支援不含透明度的有損壓縮。如果應用支援的最低版本是4.0,那麼就隻能針對不含透明度的圖檔進行WebP轉換了。
在Android Studio 2.3版本及以上,我們可以選中 drawable 和 mipmap 檔案夾,右鍵後選擇 convert to webp,将圖檔轉為 WebP 格式。如果Android Stuido版本比較低的話,可以直接通過官方提供的cwebp工具,将png轉化為WebP。
取兩張比較大的圖檔轉化為webp格式進行測試,壓縮效果如下:
然而不是所有的圖檔都有這麼高的壓縮比,WebP主要針對色差比較小的圖檔,壓縮比比較高,有些圖檔可能壓縮後反而會增大,是以任何一種壓縮算法隻是針對某種類型的圖檔壓縮比比較高,沒用萬能鑰匙的。
3.2.5 優化庫中資源
通常在大型的項目中,都會引入很多系統庫和第三方的庫。比如低版本相容庫V4、V7、網絡請求庫、圖檔處理庫等,如果庫中包含一些大圖,而我們并不會用到,就可以采用1x1的透明圖檔替代,達到既能編譯通過,又可以縮小庫體積的目的。
3.2.6 大背景圖處理
對清晰度要求高的大圖檔,采用單純的壓縮方法就不能滿足UE的要求了,需要找到一種非壓縮方式來解決這個問題。純色圖+背景下載下傳的方式很好的解決了這個問題,用戶端先使用純色圖檔,然後大圖從後端下載下傳,這樣隻是啟動的前幾次使用純色圖,以後都會使用大圖。
3.2.7 Lottie動畫庫的使用
動畫,尤其是幀動畫,一直都是相當占用資源的。現在可以通過Lottie動畫庫,直接用json檔案來描述動畫,然後直接加載繪制出來。
3.2.8 其它資源政策
A)優先使用9圖
B)考慮采用矢量圖svg和VectorDrawable
C) 盡量避免使用圖檔,比如可以使用shape代碼實作
D) 盡量使用一張圖,如果同一張圖,隻是旋轉角度或者顔色不同,可以通過代碼實作
E)lottie
3.3 針對代碼優化
上面已經詳細的介紹了資源檔案的優化方法,通過這些優化,包體積得到明顯的縮減,下面我們再來讨論一下代碼的優化。
3.3.1 代碼混淆
在gradle使用minifyEnabled進行Proguard混淆的配置,可大大減小APP大小:
尤其需要注意的是:在proguard中,是否保留符号表對APP的大小是有顯著的影響的,可酌情不保留,但是建議盡量保留用于調試。
3.3.2 無用代碼掃描
同無用資源掃描方式一樣,可以針對無用的代碼進行掃描,這裡需要關注的一點就是在插件裡面通過反射的方法調用的主應用的一些類和方法是不能删除的。
也可以使用SonarQube掃描無用類,以及不同類裡面的重複代碼。詳情請參考:https://github.com/SonarSource/sonarqube 。
3.3.3 剔除R檔案
随着項目中資源的增加,會發現生成的dex檔案裡面R.class檔案越來越大。我們知道真正使用資源的地方都是以R.xxx.xxx這種方式通路的,而R.xxx.xx是對應于.arsc檔案裡面的一個常量值。
arsc裡面的内容具體如下:
通過這兩張截圖我們可以看出,直接用ID替換資源通路代碼R.XXX.XXX,這樣R.class檔案就沒有任何作用了,可以删除它,并且代碼裡面的資源通路字元串也變成了常量,兩個方面都減小了dex的大小。剔除R檔案可以參考開源工具:https://github.com/meili/ThinRPlugin
3.3.4 注解替代枚舉
谷歌官方一直強烈推薦用注解替代枚舉,一方面可以縮減包體積,另一友善可以節省記憶體開銷。我們來對比一下,在使用注解和使用枚舉兩種情況下,生成的class檔案内容。
通過對比可以看到生成的class檔案裡面,每個變量都是一個對象,并且還有一個value對象數組。
注解生成的class檔案隻是一些常量。
通過上面的代碼對比可以看出,常量+注解的形式,一方面可以減小生成的class檔案的位元組數,另一方面可以減小記憶體開銷。
3.4 .arsc檔案優化
在剔除R檔案小節中,大家已經看到了.arsc檔案内容格式。在整體優化小節中,已經對.arsc進行了比較大的優化,接下來分析一下其它優化方式。
可以采用混淆來縮減資源檔案的名稱,以及移除未使用的備用資源等方式來優化.arsc檔案。如何移除未使用的備用資源,gradle裡面增加如下配置:
3.5 lib目錄優化
隻提供對主流架構的支援,比如arm,對于mips和x86架構可以考慮不提供支援,系統會自動提供相應的相容
4 瘦身過程中遇到的問題
a) WebP支援問題
WebP圖檔的轉化過程中,一定要注意資源拼接的情況。比如如果存在vip_1,vip_2,vip_3,vip_4,vip_5等五個資源,要麼都轉化成WebP,要麼都不轉,不能處理其中的一部分。
替換一些引導圖的時候,一定要打包工具和用戶端同時替換。如果用戶端把引導圖替換成了WebP格式,而打包的時候,由于不同步,該圖檔又被替換成png格式,就會導緻資源加載不成功,進而程式崩潰。
b) 簽名方式
使用apksigner簽名工具前,必須先執行zipalign操作;而使用jarsigner簽名工具則是先簽名,然後再用zipalign優化。
5 參考資料
1、 Reduce APK Size
2、 Shrink Your Code and Resources
3、 Create WebP Images
4、 WebP檔案壓縮
5、 png檔案格式
6、 Facebook工程師是如何改進他們Android用戶端的
7、 剔除R檔案
8、 圖檔壓縮神器tinypng
9、 pngquat 圖檔壓縮