Android APK瘦身實踐
因為推廣的需要,公司需要把APK的大小再“減小”一下,4M以內!
當達到4M以內之后,公司建議說,能否再壓壓?2M如何?
瘦身前
因為平時就考慮到大小的限制,所以很多工作已經做過了,如下列舉現在的狀態(tài):
- 7.3M(Debug版本)和6.5M(Release版本)
- 開啟minifyEnabled
- 開啟shrinkResources
- 已經去除不相關的大型庫
- 圖片和代碼已經經歷過粗略的一輪清理
開始魔鬼瘦身
1. tinypng有損壓縮
android打包本身會對png進行無損壓縮,不信大家可以看看apk中的圖片的大小實際上比你代碼工程里的圖片要小(針對沒進行過無損壓縮的那些png圖)。
所以,純粹的進行無損壓縮并不會對apk的減小有任何效果,這是我特別想在這里強調的一個經驗。
現在大家主流的比較喜歡用的tinypng其實是有損壓縮:
https://tinypng.com/
[原文] TinyPNG uses smart lossy compression techniques to reduce the file size of your PNG files…
[翻譯] TinyPNG使用智能有損壓縮技術,來減少PNG文件的大小…
通過tinypng確實能在盡量少的損失下再減小apk,如果圖片資源多或者大的話,效果還是很明顯的。
具體減少多少,因為這個處理過程我們是間隔做的,無法準確給出結果,就按200k~500k算吧。
2. png換成jpg
經驗發(fā)現,一些背景,啟動頁,宣傳頁的PNG圖片比較大,這些圖片圖形比較復雜,如果轉用有損JPG可能只有不到一半(當然是有損,不過通過設置壓縮參數可以這種損失比較小到忽略)。
因為都是大圖,所以這種方式能有效減小apk的大小。
這種情況下的apk的減小是不可估量的。
3. jpg換成webp
如果png大圖轉成jpg還是很大,或者想壓的更小,而盡量不降低畫質,那么可以考慮一下webp。
android 4.0+才原生支持webp, 但是我們的app是兼容2.3+,所以4.0以下的設備將無法看到圖片。
考慮到我們4.0以下的所有設備比例之和大約在0.44%,非常少
4.0以下的設備不會崩潰
我們選擇不對4.0以下做webp兼容處理,不顯示就不顯示。否則,要引入webp相關so文件增大apk大小。
通過把下面四張大圖換成webp,webp的quality參數按50配置(據說官方評測75是最佳值),清晰度勉強可以接受,這個值大家具體按產品要求來定。
其中安裝jpg轉webp工具:
- brew install webp
轉換命令如下
- cwebp -q input.jpg output.webp// Example:cwebp -q 50 a.jpg a.webp
更多下載:https://developers.google.com/speed/webp/docs/precompiled
最終,apk減小了188k。
4. 大圖縮小
如果經過上面的步驟,依然存在大圖的話,說明確實圖有點大了,可能真的有點大了!
所以,要考慮的問題是,是否有必要保證如此的大小?能否縮小?
如果這方面能減小的話,apk瘦身的效果必然又會上一個檔次。
這種情況下的apk的減小是不可估量的。
5. 覆蓋aar里的一些默認的大圖
一些aar庫里面包含根本就沒有用的圖。最典型的是support-v4兼容庫中包含一些“可能”用到的圖片,實際上在你的app中不會用到。
我沒有把所有圖都替換掉,只是把幾張大一點點的圖(選中的那些圖)用1×1的圖片替換,如果9patch圖的話,要做成3×3的9patch圖替換。
support庫可能還算好的,就怕有些庫引用了一些大圖而不自知,可以在/build/intermediates/exploded-aar/下的各個aar庫的res目錄查找檢驗。
apk減小了18k。
6. 刪除armable-v7包的so
感謝@楊輝__ ,@kymjs張濤的提醒,armable-v7和armable文件夾可以只保留armable。
當然,armable-v7a的庫會對圖形渲染方面有很大的改進,因為我們主要是一些業(yè)務上動態(tài)庫,所以刪掉無大礙。
apk減小了191k。
7. 微信資源壓縮打包
這個方案網上一直在說,之前一直沒有需求或者動力實踐,在這里感謝一下@裸奔的凱子哥的推薦和交流,他那邊的apk可以壓小1M,效果還是比較驚人的。
這個步驟我是在后面很多步壓縮之后測試的,每個階段的壓縮結果都會有些許出入,所以數據僅供參考。
- 通過正常壓縮,apk包減小了464k。
- 如果開啟7zip,apk包減小了594k。
- apk減小了594k。
PS: 關于這個壓縮,我集成到了gradle腳本中了,新建了一個Task,大概代碼如下:
- task compressReleaseApp {
- // 在現有release的版本上生成到compressed目錄下
- def appid = "appid"
- def channel = "abcdefghijkl"
- def guardJarFile = file('../AndResGuard/andresguard-1.1.jar')
- def guardConfigFile = file('../AndResGuard/config.xml')
- def originApkFile = file("../app.${appid}/build/outputs/apk/release/${appid}-release-${rootProject.ext.versionName}-${rootProject.ext.versionCode}-${channel}.apk")
- def outputDir = file("../app.${appid}/build/outputs/apk/compressed/")
- def keystoreFile = file(RELEASE_STORE_FILE)
- // 開始執(zhí)行壓縮命令
- def proc = "java -jar ${guardJarFile} ${originApkFile} -config ${guardConfigFile} -out ${outputDir} -signature ${keystoreFile} ${RELEASE_STORE_PASSWORD} ${RELEASE_KEY_PASSWORD} ${RELEASE_KEY_ALIAS}".execute();
- proc.waitFor();
- println "return code: ${ proc.exitValue()}" + ", stderr: ${proc.err.text}" + " stdout: ${proc.in.text}"
- }
config開啟了7zip, 部分配置如下:
- <?xml version="1.0" encoding="UTF-8"?>
- <resproguard>
- <!--defaut property to set -->
- <issue id="property" >
- <seventzip value= "true" />
- <!-- ... -->
- </issue>
- <issue id="whitelist" isactive="true">
- <path value ="com.xxx.yyy.R.drawable.emoji_*" />
- <path value ="com.xxx.yyy.... />
- </issue>
- <issue id ="compress" isactive="true">
- <!-- ... -->
- </issue>
- </resproguard>
詳情參考:https://github.com/shwenzhang/AndResGuard
原理介紹:
http://mp.weixin.qq.com/s?__biz=MzAwNDY1ODY2OQ==&mid=208135658&idx=1&sn=ac9bd6b4927e9e82f9fa14e396183a8f#rd
8. proguard深度混淆代碼
之前為了簡單起見,很多包都直接忽略了,現在啟動嚴格模式,把能混淆的都混淆了:

采用微信壓縮方案最終效果比較:
apk減小了215k。
PS:混淆后,一定要經過嚴格測試,有時候甚至很難發(fā)現錯誤,比如我開啟嚴格混淆,用了一段時間之后慢慢發(fā)現了兩個bug,排除了兩個包程序才正常。
9. 深度清理代碼和資源
有意思的是,無論何時何地去清理代碼和資源,總能有新的發(fā)現:
- 新發(fā)現或者新引入的無用圖片
- 這幾張圖怎么一樣
- 這個類好像沒有用
- 沒用的類相關的圖片也沒用
- 有些圖片可以用著色方案替換
- 有些圖片可以用shape來代替
- hdpi里的ic_luancher.png好像也可以刪掉
- …
apk減小了66k。
10. proguard去符號表
之前為了保留調試信息,我們是在Proguard保留了符號表的:
-keepattributes SourceFile,LineNumberTable
官方渠道我覺得還是盡量保留這個,現在針對推廣渠道,只能采用特殊手段,注釋這一行。
apk減小了230k。
ps:以后友盟上看推廣渠道的bug要辛苦一點,手動上傳mapping.txt了。
11. provided關鍵字
可以對僅在運行時需要的庫設置provided關鍵字,實際并不被打包:
- provided 'com.android.support:support-annotations:22.0.0'
我沒有發(fā)現這樣的場景,如果說有的話,就是support-annotations,但是經過后來的測試驗證,support-annotations本來就會在release版本中被minifyEnabled掉,所以對support-annotations設置provided是沒有意義的。
如果有實際場景,歡迎留言說明,不甚感激。
apk沒有減小。
12. 表情包在線化
雖然應用的表情不多,只有50來個,但是如果能把這部分表情放到網上,不僅能有效減小apk大小,還可以方便后期擴展支持:
打包成emoji_v1.zip, 大小是202k。
現在把emoji_v1.zip放到網上,按需下載后使用,最終對比結果如下:
apk減小了193k。
13. 全版本兼容的著色方案
考慮著色方案主要目的是更方便支持多主題,減輕UI工作量,減少工程里一大堆selector文件等,然后才是,順便的減小一下apk大小。
通過著色方案,我們去除了10多張純色的按下狀態(tài)圖片和對應的xml等等。
apk減小了15k。
PS: 具體實現可以參考 http://www.race604.com/tint-drawable/ ,而我也把它集成到了我的LessCode庫中了:DrawableLess.java:https://github.com/openproject/LessCode/blob/master/lesscode-core/src/main/java/com/jayfeng/lesscode/core/DrawableLess.java
14. 去除重復庫
發(fā)現兩個地方:
- 現在發(fā)現七牛的SDK引用了android-async-http-1.4.6.jar,雖然不大,只有95.4k,但是感覺完全可以寫一個輕量級的jar,控制在10~20k就足夠了,具體可以在現有的網絡庫上實現。
- 自己工程使用的是UIL,但是引入的第三方庫引用了picasso,兩個重復的圖片下載庫也是完全沒用必要的。
現在還沒有處理這塊,新任務介入,延期優(yōu)化,敬請期待。
15. 去除無用庫
這是一個很基本的點,但是確很容易被人忽視,當你仔細回顧的時候,有一些雞肋的功能或者庫,是幾無用處的。不如干脆去掉。
比如,在很早的時候,我就把我們app里的sharesdk刪除了,因為對于我們的產品定位和推廣來看,這毫無意義。
這種情況下的apk的減小是不可估量的。
16. 去除百度統計
這個視具體情況決定。
因為我們的APP里面包含友盟和百度兩套統計系統,早期老板要求,事實上后面已經很少看這方面的數據,百度統計的數據幾乎沒用人去看,可以暫時先去除。
原本的百度統計的jar有130多k,去除之后的apk的減小會遠遠沒有這么多。
apk減小了20k。
17. 使用更小的庫
使用更小的庫不應該成為你選擇方案的決定性因素,但是可以作為參考因素(freso確實太大了,這個大小也可以成為決定性因素)。
圖片下載,網絡請求,json解析等等的庫和它的競品都有多大,你心里有數嗎?
以工具庫為例,網上有很多工具庫,但是往往它們的大小很難控制。
- xutils-3.2.6.aar – 843.8k
- lite-common-1.1.3.jar – 148.1k
- lesscode-core-0.8.2.aar – 64k
- …
上面最后一個庫LessCode是我自己收集的工具類集合,非常?。篖essCode,混淆后只有不到50k大小。
不僅提高了開發(fā)效率,減少了冗余代碼,而且能避免引用一些其他大型的庫,有效避免包的增大。
比如,我們碰到過這樣的一個bug,快速點擊按鈕多次觸發(fā)跳轉,現在RxJava結合RxBind有這樣的一個場景解決方案,如果引入這些庫的話必然會增大apk大小,實際上就幾行代碼,我把這樣的解決方案集成到了LessCode,下次別的項目碰到這樣的問題不用再猶豫是否要引入一個這么大的庫了。
這些小的工具庫,建議根據自己的經驗人手總結一個,不求全,但求精!
這種情況下的apk的減小是不可估量的。
18. 插件化
尷尬的是,我們所呈現的功能大部分都是重要的不可分割的功能,很難從業(yè)務上分離出來。
今年預計要實踐一個輕量級的插件化方案,用別人的也好,自己寫也好,希望能解決或者優(yōu)化一些安裝包加載多模塊,或者主題切換,或者熱修復的問題。
這里作為候選方案備用。
這種情況下的apk的減小是不可估量的。
19. 功能業(yè)務取舍
一開始考慮瘦身,領導是允許適當的砍掉一些功能,因為4M的目標我們已經實現了,所以現在還沒有到砍功能的地步。
這里作為候選方案備用。
這種情況下的apk的減小是不可估量的。
補充
文章發(fā)出后,收到了一些朋友的建議,補充幾點。
1. 去除無用的語言資源
感謝@牧志軒的建議,通過配置resConfigs可以選擇只打包哪幾種語言,進而去掉各種aar包中全世界的語言,尤其是support包中的。
選擇保留什么語言要根據產品的用戶和市場來定,如果只選擇默認英語和中文語言,配置如下
- android {
- defaultConfig {
- resConfigs "zh"
- }
- }
看看效果:
如果不采用微信壓縮方案結果對比,apk減小了197k。
如果采用微信壓縮(開啟7zip)對比結果,apk只減小了16k,因為微信對resources.arsc進行了強力壓縮,厲害!
apk減小了16k。
2. 刪除x86包的so
再次感謝@楊輝__的建議,x86的包刪除了之后,測試反應好像有些機器容易崩潰,未能經過嚴格測試,所以主版本又復原了,只在個別渠道執(zhí)行這條措施。
一般情況下不會有問題,測試了一下效果,apk減小了78k。
這里作為候選方案備用。
小結
最終,我們成功的把apk壓到了2.9M,如果把上面遺漏的步驟繼續(xù)再做,應該還能再減小一點。
客戶反應壓的好小,領導簡直不敢相信~
瘦身不難,難的是魔鬼瘦身!