APK瘦身記,如何?高達53%的壓縮效果,apk53%
作者:非戈@阿里Mobile Security
1
、我是怎麼思考這件事情的
APK是Android系統安裝包的檔案格式,關於這個話題其實是一個老生常談的題目,不論是公司內部,還是外部網路,前人前輩已經總結出很多方法和規律。不過隨著移動端技術近兩年的飛速發展,一些新的思維方式和最佳化方法也逐漸湧現和成熟起來。筆者在實踐過程中踩過一些坑,收穫了一些經驗,在這裡做個思考和總結,所以隨筆給大家,希望對大家從事相關工作的時候有所協助和參考,同時也是拋磚引玉,希望大家共同探討這個開放性的話題。
關於為什麼APK要瘦身,這個不多說,只從三個方面嘮叨一下,對於使用者(或者客戶)來說,APK越大,在下載安裝過程中,他們耗費的流量會越多,安裝等待時間也會越長;對於產品本身,意味著下載轉化率會越低(因為競品中,使用者有更多機會選擇那個體驗最好,功能最多,效能最好,包最小的);對於研發來說,是一種最佳化改進技術的機會。
欲瘦身,我們先找找胖的原因和問題。按目標-路徑-資源的思維模式,找原因和問題有如下幾條路徑,一是拍腦袋,按自己的經驗和判斷,甚至是主觀想象;二是去搜尋引擎找關鍵字,逛各種技術論壇聽技術大牛們怎麼說,看各類技術文章抽取提煉;三是用一種可測量的工具或者方法發現問題。
前兩種不贅述,我這裡說說第三種方法。用一種可測量的工具或者方法來分析,所謂工欲善其事,必先利其器。這個器可以可以自己鍛造,也可以用現成的。這裡推薦一個線上apk分析工具,因為是外部工具,所以大家請在使用過程中,不要上傳未發布出去的產品,為了資料安全,筆者這裡拿一個github上開源的Android項目作為瘦身樣本。
2、尋找問題
NimbleDroid 是美國哥倫比亞大學的博士創業團隊研發出來的分析Android app效能指標的系統,分析的方式有靜態和動態兩種方式,其中靜態分析可以分析出APK安裝包中大檔案熱門排行榜,各種知名SDK的大小以及占代碼整體的比例,各種類型檔案的大小以及占排行,各種知名SDK的方法數以及占所有dex中方法數的比例,廢話不多說,下面上高清無碼大圖看看顏值吧。
如果想流量分析功能分析自己的產品,請登入並上傳自己產品的apk包,所有功能目前均免費使用,如果是想分析Google Play上已經發布的產品,可以直接點擊"Play Apps"查看,還可以使用搜尋功能根據應用程式名稱和包名查看結果。再次強調下,請不要上傳任何未發行的作品。
登入
上傳apk檔案
分析結果摘要,可以看到一些概覽的資訊,apk檔案大小,總的方法數
檔案大小分析詳情頁,大檔案清單,這裡列出的是apk檔案中超過100k的檔案排行,這裡的檔案大小指的是apk檔案中的大小
各種知名SDK的大小以及占代碼整體的比例,這裡目前能識別出Android Support,Jackson JSON parser, Google Play Services, Paypal, Glide, OkHttp, Facebook SDK, Fabric, Gson等等,Application表示App中自己編寫的代碼部分
各種類型檔案的大小以及排行
各種知名SDK占所有dex中方法數的比例
各種知名SDK的方法數熱門排行榜
看完這個apk內剖圖是不是有一種神清氣爽的感覺!我把這個分析工具比做我們家買的智能體重秤,可以稱體重,脂肪含量,骨重,骨礦物質密度,肌肉含量等等,那麼,我們是不是發現了一些問題,進而把這些問題和我們之前靠經驗和一拍腦袋的原因可以用邏輯聯絡在一起。
那麼,我們接下來可以通過分析資料整理出我們的最佳化目標
3、梳理最佳化目標
所以我們的目標是沒有蛀牙,不對,是下面的目標:
1. png圖片最佳化;
2. resources.arsc檔案的最佳化;
3. 代碼最佳化
3.1圖片最佳化的嘗試
首先是第一個目標,圖片的最佳化,慢點,我們看看這些圖為什麼這麼大先,準確的說,為什麼這些圖在apk(其實就是zip檔案)裡這麼大,好了,上工具分析。
這次用了一些簡單的工具組合,系統內建的cmd就好。
命令執行的結果如下
恩,所有的png檔案居然是STORE的方式儲存到apk裡的,關於zip裡的STORE和DEFLATE,詳見 )
通俗的說,當檔案是STORED的方式儲存到zip,表示這個檔案並沒有經過壓縮,如果是Defl:N的方式,表示通過DEFLATED normal的方式壓縮儲存到zip。
這看起來有點不合理,png原封不動的放入zip,當然最後產出的apk會比較大。那麼,如何解決呢?筆者首先嘗使用android gradle plugin的方式,發現aaptOptions和packagingOptions都未能解決問題。在github上發現一個開源項目AndResGuard,試了整合到項目中,再看結果如下:
最佳化前:
10536027位元組
最佳化後:
普通zip壓縮: 8786265位元組 (壓縮了將近17%)
採用7zip壓縮:8567150位元組 (壓縮了將近19%)
再看看這個工具做了什麼,對比下開啟資源混淆前後
最佳化前
最佳化後
用apk反編譯神器jadx內窺apk尋找真相
原來apk中資源(png, xml,以及properties檔案)的相對路徑會存放到META-INF/*.SF 以及META-INF/*.MF中並為每個資源檔計算SHA1值並儲存在這兩個檔案中,至於為啥這麼做以及這兩個SHA1有啥區別和作用請參考網路上關於這方面知識的文章,已超出本文的主題所以這裡不再贅述。
對於resources.arsc檔案
很容易看出來它是資源檔索引表,所以,看到這裡大家應該明白這三個檔案為啥會變小了吧。
3.2一次意外的發現
順著resources.arsc往下看,發現一個有趣的東西,
這又將成為一個最佳化點,去除那些沒用的翻譯資源,引入一些第三方的SDK,往往這些SDK帶了很多翻譯資源在裡面,比如android support庫,去掉後我們來看看效果。
假設我們只保留英文,當然只是個實驗,現實中看具體情況了,
採用7zip壓縮:8220738位元組 (壓縮了將近22%,再增加3個點)
當然,真實的項目裡不可能這樣,但是蚊子肉也是肉啊!
其實,我想說的是這提供了一種最佳化思路,就是利用gradle的配置幹掉無用的資源,同樣的可以用在so本地庫上,解析度(gradle配置已deprecated)上。
gradle配置樣本如下:
記得包在android{}中間哦。那麼,有人要問了,abi裡腫麼沒有x86?據說intel提供了一個解決方案叫houdini,是一個運行在x86裝置上的中介軟體,可以將arm轉碼為x86的指令,不過效率很低,有些運算型的,比如計算MD5和SHA1,甚至不如java,筆者曾經做過測試對比,又是另外一個話題,此處不贅述,感興趣的讀者可以移步。
到此為止,我們已經在朝第一個目標邁進,不經意間發現了第一個目標和第二個目標之間的關係,所以利用資源混淆工具,達成了第二個目標。
利用7zip壓縮,我們對整個包進行了2個點的壓縮,這是一個超出預期的成果。
3.3圖片最佳化的方法
關於第一個目標,我們的路徑還沒有結束,拍腦袋想出來的路徑是壓縮png,非alpha圖轉成jpg,還有什嗎?所以去各種技術論壇逛了一圈,請教了各種技術大牛,梳理的路徑如下:
1. 手動lint檢查,手動刪除代碼中沒有引用到的資源,實際效果不等。
在Android Studio中開啟“Analyze” 然後選擇"Inspect Code...",範圍選取整個項目,然後點擊"OK"
配置如
2. gradle指令碼中開啟shrinkResources
指令碼參考如下
shrinkResources配合minifyEnabled使用效果更佳,詳見shrinkResources用法以及注意
採用7zip壓縮:8115283位元組 (壓縮了將近23%,再增加1個點)
3. 使用圖片壓縮公用程式,壓縮png圖的大小,將非alpha的圖轉換成jpg形式,關於這點同事以及網路上的大牛們已經整理的很詳細了,我這裡做簡單總結,欲知詳情,請見附錄的參考。
- 使用tinypng,我只想說咱們在公司做產品,此方案慎用,上傳任何未發布產品的內容到外部網路,都有可能引起資料泄漏,所以慎用此方案。下面說替代方案。
- WASTED
- pngquant
- ImageAlpha
- ImageOptim
- 以上工具太散,有沒有整合化的工具,答案是“有”,@心倫 童鞋開發的imagemin
- @姐夫童鞋開發的MSImageResourcesHelper
- png轉成jpg格式 具體效果不等。
4. 終極大殺器,png轉成webp,關於webp,更多詳情請參考Google官方文檔以及安卓開發人員線上參考
先上:
採用7zip壓縮:4926912位元組 (壓縮了將近53%,再增加30個點)
沒看錯吧,是30個點,目前apk的大小是原始apk大小的一半不到,而我做的,一行代碼木有改動,僅用了一些工具而已!
說人話,我木有吃減肥藥,木有絕食,體重卻輕了一半!!!
但是,目前卻沒能用到項目中,因為有兩個坑
- 在三星的部分機型上,部分有alpha背景部分的圖會有一條很明顯的黑線,這裡就不了,這個問題目前通過白名單的方式不去做成webp的圖來處理;
- 在小米2刷成4.xx的手機上,未能正確識別xml檔案中描述的webp圖片,導致介面起來後載入xml布局檔案,檔案載入webp失敗,報錯說resource file not found,導致app發生崩潰。跟蹤發現是小米機器代理了類Resource為MIUIResource,但是這個MIUIResource未能正確識別webp,所以導致載入資源檔失敗,初步判定,目前暫時沒有解決方案,所以只能忍痛放棄這個最佳化方案。
關於第一個目標,圖片資源的最佳化,就寫到這裡了。
3.4代碼最佳化
第二個目標已達成,剩下第三個目標,代碼的最佳化,梳理如下最佳化路徑:
1. 開啟proguard的代碼最佳化
將proguardFiles getDefaultProguardFile('proguard-android.txt'),'proguard-project.txt'
改為proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'),'proguard-project.txt'
開啟代碼最佳化後的注意點請參見附錄。
2. 去除無用的庫
如果apk支援的最低版本是API14,而代碼中沒有用到高於api14的api就可以考慮拿掉整個android support庫。
3. 用更小的庫替代方案
如果只用到了Google統計,那麼就不要把整個google play services都整合進來,只整合需要的部分。
4.定期清理廢棄的代碼
定期刪除無用的邏輯和到期的業務功能模組,以及廢棄的A/B test代碼。
5.業務模組採用外掛程式化架構,代碼動態從雲端拉取
外掛程式化,這是另外一個課題了,這裡不贅述。
apk瘦身記最終的成果
10536027位元組壓縮到4926912位元組, 壓縮了將近53%
總結
最後,繼續學習和嘗試新的最佳化方案
以此文獻給“唯瘦身與產品不可辜負”的技術們!!!
附錄
如何做到將apk大小減少6M
Android APP終極瘦身指南
APK瘦身實踐
作者:非戈@阿里Mobile Security,更多技術乾貨,請訪問阿里聚安全部落格