用戶常常避免下載太大的APP,尤其是使用移動(dòng)流量的情況下吁断,而且太大的APP也會(huì)占用更多的內(nèi)存并消耗更多的資源仙逻,導(dǎo)致安裝速度和加載速度變慢揭措,特別是在低配手機(jī)上,這些情況尤為嚴(yán)重脖捻。以下是我在對(duì)自己的APK瘦身之路上的一些經(jīng)驗(yàn)分享。
APK的組成結(jié)構(gòu)
在使用一些很酷的方法來(lái)減少APK的大小之前,必須先了解實(shí)際的APK文件格式淹辞。簡(jiǎn)單的說(shuō),APK是一個(gè)包含文件俘侠、文件夾的壓縮文件象缀。在Android Studio工具欄里蔬将,打開(kāi)build–>Analyze APK, 選擇要分析的APK包(Raw File Size表示原文件大小,Download Size表示經(jīng)過(guò)Google play處理壓縮后的apk大泄ダ洹)娃胆。
APK的瘦身方案
1. 整體優(yōu)化
1.1 插件化
從應(yīng)用功能擴(kuò)張的角度看,APK包體積的增大是必然的等曼,然而插件化技術(shù)的出現(xiàn)很好的解決了這個(gè)問(wèn)題里烦。通過(guò)分離應(yīng)用中比較獨(dú)立的模塊,然后以插件的形式進(jìn)行加載禁谦。比如愛(ài)奇藝Android客戶端有很多相對(duì)獨(dú)立的功能胁黑,游戲、漫畫州泊、文學(xué)丧蘸、電影票、應(yīng)用商店等遥皂,都是通過(guò)插件的方式力喷,從服務(wù)器下載,然后以插件的額方式加載到我們的主工程演训。
1.2 重新壓縮
一般情況下面弟孟,AS直接編譯生成的APK里面,.arsc文件是沒(méi)有進(jìn)行任何壓縮的样悟,我們可以解壓APK拂募,重新用壓縮軟件(WinRAR/7zip)進(jìn)行壓縮,就會(huì)發(fā)現(xiàn)幾乎所有的文件都變小了窟她,特別是.arsc文件陈症,減少的比較多。
1.3 簽名方式
Google在Android7.0系統(tǒng)提供了新的apksigner簽名工具震糖,相比使用java提供的jarsigner簽名工具录肯,APK體積可以減少約5%(依賴文件數(shù)量)。產(chǎn)生上述變化的原因是jarsigner是針對(duì)每個(gè)文件進(jìn)行了簽名试伙,然后針對(duì)簽名后的文件計(jì)算摘要嘁信,并寫入到META-INF文件夾下的MANIFEST.MF文件里面;而apksigner直接計(jì)算所有文件的摘要疏叨,寫入MANIFEST.MF文件潘靖。
新的apksigner工具,已經(jīng)集成到Android7.0 SDK中了蚤蔓,使用方法可以參考官方文檔:
https://developer.android.com/studio/command-line/apksigner.html
2. 資源優(yōu)化
2.1 移除重復(fù)的資源
- 一套資源
Android在適配圖片資源的時(shí)候卦溢,如果只有一套資源,低密度的手機(jī)會(huì)縮放圖片,高密度的手機(jī)會(huì)拉伸圖片单寂。我們利用這個(gè)特性贬芥,存放一套資源圖就可以供所有密度的手機(jī)使用。綜合考慮圖片清晰度宣决,靜態(tài)大小和內(nèi)存占用情況蘸劈,建議取720p的資源,放到xhdpi目錄尊沸。 - 重復(fù)資源
很多時(shí)候威沫,隨著工程的增大,以及開(kāi)發(fā)人員的變動(dòng)洼专,有些資源文件名字不同棒掠,但是內(nèi)容卻完全不同。我們可以同過(guò)掃描文件的MD5值屁商,找出名字不同烟很,內(nèi)容相同的圖片并刪除,做到圖片不重復(fù)蜡镶。
2.2 移除無(wú)用的資源
- 通過(guò)Lint工具掃描工程資源
當(dāng)Lint工具掃描發(fā)現(xiàn)無(wú)用資源的時(shí)候雾袱,會(huì)輸出如下信息,就可以刪除這種資源官还。
res/layout/preferences.xml: Warning: The resource R.layout.preferences appears to be unused [UnusedResources]
需要特別注意的是谜酒,需要確保不存在反射,資源拼接等訪問(wèn)這些資源妻枕,才可以安全的刪除掉這些資源,從而減少資源個(gè)數(shù)粘驰。
- 通過(guò)Gradle參數(shù)配置
如果工程比較大屡谐,由主工程和多個(gè)子工程組成的話,子工程里面也可能包含很多的無(wú)用資源蝌数°堤停可以通過(guò)設(shè)置shrinkResources=true讓Gradle移走無(wú)用的資源,否則默認(rèn)情況下顶伞,Gradle編譯只會(huì)移除無(wú)用代碼饵撑,而不會(huì)關(guān)心無(wú)用資源。
需要特別注意的是shrinkResources依賴于minifyEnabled唆貌,必須和minifyEnabled一起用滑潘,即打開(kāi)shrinkResources也必須打開(kāi)minifyEnabled
android {
// Other settings
buildTypes {
release {
minifyEnabled true
shrinkResources true
proguardFiles
getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
- 通過(guò)開(kāi)源掃描工具
大家可能會(huì)發(fā)現(xiàn)Lint不是非常好用,當(dāng)工程里面存在反射锨咙,過(guò)濾結(jié)果非常麻煩语卤。所以我們實(shí)現(xiàn)了一個(gè)資源掃描的工具(https://github.com/zhuzhumouse/ScanUnusedResouce ),可以過(guò)濾掉通過(guò)反射調(diào)用的資源。原理就是把所有java和xml文件以字符串掃描到內(nèi)存粹舵,然后拿到資源文件(xml,png,jpg等)名稱做匹配查找钮孵,如果沒(méi)有匹配到,該資源就是無(wú)用資源眼滤,可以直接刪除巴席。
該掃描工具可以解決反射調(diào)用的問(wèn)題,但是不能解決資源拼接的問(wèn)題诅需,還有就是不能處理存在很多資源前綴相同的情況漾唉。
2.3 png圖片壓縮
可以通過(guò)使用圖片壓縮工具對(duì)png圖片進(jìn)行壓縮,壓縮效果比較好的工具有:pngcrush,pngquant,zopflipng等,可以在保持圖片質(zhì)量的前提下诱担,縮減圖片的大小毡证。
還可以通過(guò)網(wǎng)站對(duì)圖片進(jìn)行壓縮,如比較有名的www.tinypng.com蔫仙,該網(wǎng)站對(duì)上傳的圖片自動(dòng)選擇合適的壓縮算法料睛,壓縮比比較高,但是只支持500張免費(fèi)圖片摇邦,更多圖片處理是要收費(fèi)的恤煞。
2.4 采用WebP格式
WebP分為有損壓縮,無(wú)損壓縮以及包含透明度的有損壓縮施籍。
有損WebP是基于VP8視頻編碼中的預(yù)測(cè)編碼方法來(lái)壓縮圖像數(shù)據(jù)居扒;無(wú)損WebP基于使用不同的技術(shù)對(duì)圖像數(shù)據(jù)進(jìn)行轉(zhuǎn)換;有損WebP(支持透明度)區(qū)別于有損WebP和無(wú)損WebP丑慎,這種編碼允許對(duì)RGB頻道的有損編碼同時(shí)可對(duì)透明度頻道進(jìn)行無(wú)損編碼喜喂。
目前4.2及以上的手機(jī)系統(tǒng)已經(jīng)支持WebP的無(wú)損和有損壓縮,但是4.0,4.1的手機(jī)系統(tǒng)只支持不含透明度的有損壓縮竿裂。如果應(yīng)用支持的最低版本(minSdkVersion)是4.0玉吁,那么就只能針對(duì)不含透明度的圖片進(jìn)行WebP轉(zhuǎn)換了。
在Android Studio 2.3版本及以上腻异,我們可以選中 drawable 和 mipmap 文件夾进副,右鍵后選擇 convert to webp,將圖片轉(zhuǎn)為 WebP 格式悔常。如果Android Stuido版本比較低的話影斑,可以直接通過(guò)官方提供的cwebp工具,將png轉(zhuǎn)換為WebP机打。
從以上兩張樣圖的轉(zhuǎn)換結(jié)果看矫户,不是所有的圖片都有高壓縮比,有些圖片壓縮后反而會(huì)增大姐帚,比如第二張樣圖吏垮。WebP對(duì)色差比較小的圖片障涯,壓縮比會(huì)比較高,任何一種壓縮算法只能針對(duì)具有某種特點(diǎn)的圖片進(jìn)行壓縮膳汪,沒(méi)有萬(wàn)能壓縮方法唯蝶。
2.5 優(yōu)化庫(kù)中資源
通常在大型的項(xiàng)目中,都會(huì)引入很多系統(tǒng)庫(kù)和第三方的庫(kù)遗嗽。
比如低版本兼容庫(kù)V4粘我、V7、網(wǎng)絡(luò)請(qǐng)求庫(kù)痹换、圖片處理庫(kù)等征字,如果庫(kù)中包含一些大圖,而我們并不會(huì)用到娇豫,就可以采用1x1的透明圖片替代匙姜,達(dá)到既能編譯通過(guò),又可以縮小庫(kù)體積的目的冯痢。
2.6 大背景圖處理
對(duì)清晰度要求高的大圖片氮昧,采用單純的壓縮方法就不能滿足UE的要求了,需要找到一種非壓縮方式來(lái)解決這個(gè)問(wèn)題浦楣。
純色圖+后臺(tái)下載的方式很好的解決了這個(gè)問(wèn)題袖肥,客戶端先使用純色圖片,然后大圖從后端下載振劳,這樣只是啟動(dòng)的前幾次使用純色圖椎组,以后都會(huì)使用大圖
2.7 Lottie動(dòng)畫庫(kù)的使用
動(dòng)畫,尤其是幀動(dòng)畫历恐,一直都是相當(dāng)占用資源的〈绨現(xiàn)在可以通過(guò)Airbnb公司開(kāi)源的Lottie動(dòng)畫庫(kù),直接用json文件來(lái)描述動(dòng)畫弱贼,然后直接加載繪制出來(lái)灵份。
具體使用參考:https://github.com/airbnb/lottie-android
2.8 其他資源策略
- 首先考慮能否不用圖片,比如使用shape代碼實(shí)現(xiàn)哮洽。
- 其次如果用圖片的話,能否優(yōu)先使用.9圖來(lái)簡(jiǎn)化圖片弦聂。
- 采用svg矢量圖和VectorDrawable類來(lái)替換傳統(tǒng)的圖片鸟辅。
- 如果圖片只是旋轉(zhuǎn)角度或者顏色不同,可以用代碼實(shí)現(xiàn)變換莺葫。
3. 代碼優(yōu)化
3.1 代碼混淆
在gradle使用minifyEnabled進(jìn)行Proguard混淆的配置匪凉,可大大減小APP大小:
android {
buildTypes {
release {
//是否進(jìn)行混淆
minifyEnabled true
//混淆文件的位置
proguardFile('proguard.cfg')
}
}
}
3.2 無(wú)用代碼掃描
同無(wú)用資源掃描方式一樣捺檬,可以針對(duì)無(wú)用的代碼進(jìn)行掃描再层,這里需要關(guān)注的一點(diǎn)就是在插件里面通過(guò)反射的方法調(diào)用的主應(yīng)用的一些類和方法是不能刪除的。
也可以使用SonarQube掃描無(wú)用類,以及不同類里面的重復(fù)代碼聂受。
詳情請(qǐng)參考:https://github.com/SonarSource/sonarqube
3.3 剔除R文件
隨著項(xiàng)目中資源的增加蒿秦,會(huì)發(fā)現(xiàn)生成的dex文件里面R.class文件越來(lái)越大。我們知道真正使用資源的地方都是以R.xxx.xxx這種方式訪問(wèn)的蛋济,而R.xxx.xx是對(duì)應(yīng)于.arsc文件里面的一個(gè)常量值棍鳖。arsc里面的內(nèi)容具體如下:
通過(guò)這兩張截圖我們可以看出,直接用ID替換資源訪問(wèn)代碼R.XXX.XXX碗旅,這樣R.class文件就沒(méi)有任何作用了渡处,可以刪除它,并且代碼里面的資源訪問(wèn)字符串也變成了常量祟辟,兩個(gè)方面都減小了dex的大小医瘫。
剔除R文件可以參考開(kāi)源工具:https://github.com/meili/ThinRPlugin
3.4 注解替代枚舉
谷歌官方一直強(qiáng)烈推薦用注解替代枚舉,一方面可以縮減包體積旧困,另一方便可以節(jié)省內(nèi)存開(kāi)銷醇份。我們來(lái)對(duì)比一下,在使用注解和使用枚舉兩種情況下叮喳,生成的class文件內(nèi)容被芳。
枚舉類型源碼
public enum MarkViewType3{
SIMPLE_TEXT_MARK,
DO_LIKE_MARK,
BOTTOM_BANNER1,
BOTTOM_BANNER2,
TL_GREY_BACKGROUND_RANK,
/**
*服務(wù)導(dǎo)航mark
*/
SERVICENAVIRIGHTMARK,
/**
*搜索頁(yè)熱點(diǎn)事件,標(biāo)題馍悟、評(píng)論畔濒、事件
*/
BOTTOM_COMPOUND_TEXT_BANNER
}
編譯生成dex后的class文件
public enum MarkViewType3
{
static
{
DO_LIKE_MARK = new MarkViewType3("DO_LIKE_MARK", 1);
BOTTOM_BANNER1 = new MarkViewType3("BOTTOM_BANNER1", 2);
BOTTOM_BANNER2 = new MarkViewType3("BOTTOM_BANNER2", 3);
TL_GREY_BACKGROUND_RANK = new MarkViewType3("TL_GREY_BACKGROUND_RANK", 4);
SERVICENAVIRIGHTMARK = new MarkViewType3("SERVICENAVIRIGHTMARK", 5);
BOTTOM_COMPOUND_TEXT_BANNER = new MarkViewType3("BOTTOM_COMPOUND_TEXT_BANNER", 6);
$VALUES = new MarkViewType3[] { SIMPLE_TEXT_MARK, DO_LIKE_MARK, BOTTOM_BANNER1, BOTTOM_BANNER2, TL_GREY_BACKGROUND_RANK, SERVICENAVIRIGHTMARK, BOTTOM_COMPOUND_TEXT_BANNER };
}
}
通過(guò)對(duì)比可以看到生成的class文件里面,每個(gè)變量都是一個(gè)對(duì)象锣咒,并且還有一個(gè)value對(duì)象數(shù)組侵状。
注解的實(shí)現(xiàn)源碼
public class MarkViewType1{
public static final int SIMPLE_TEXT_MARK = 0;
public static final int DO_LIKE_MARK = 1;
public static final int BOTTOM_BANNER1 = 2;
public static final int BOTTOM_BANNER2 = 3;
public static final int TL_GREY_BACKGROUND_RANK = 4;
/**
*服務(wù)導(dǎo)航mark
*/
public static final int SERVICENAVIRIGHTMARK = 5;
/**
*搜索頁(yè)熱點(diǎn)事件,標(biāo)題毅整、評(píng)論趣兄、事件
*/
public static final int BOTTOM_COMPOUND_TEXT_BANNER = 6;
@IntDef ({SIMPLE_TEXT_MARK, DO_LIKE_MARK, BOTTOM_BANNER1, BOTTOM_BANNER2, TL_GREY_BACKGROUND_RANK
, SERVICENAVIRIGHTMARK, BOTTOM_COMPOUND_TEXT_BANNER})
@Retention(RetentionPolicy.SOURCE)
public @interface MarkViewType1Anno{
}
}
生成的class文件
{
public static final int BOTTOM_BANNER1 = 2;
public static final int BOTTOM_BANNER2 = 3;
public static final int BOTTOM_COMPOUND_TEXT_BANNER = 6;
public static final int DO_LIKE_MARK = 1;
public static final int SERVICENAVIRIGHTMARK = 5;
public static final int SIMPLE_TEXT_MARK = 0;
public static final int TL_GREY_BACKGROUND_RANK = 4;
@Retention(RetentionPolicy.SOURCE)
public static @interface MarkViewType1Anno
{
}
}
注解生成的class文件只是一些常量。
通過(guò)上面的代碼對(duì)比可以看出悼嫉,常量+注解的形式艇潭,一方面可以減小生成的class文件的字節(jié)數(shù),另一方面可以減小內(nèi)存開(kāi)銷戏蔑。
4. arsc文件優(yōu)化
在剔除R文件小節(jié)中蹋凝,大家已經(jīng)看到了.arsc文件內(nèi)容格式。在整體優(yōu)化小節(jié)中总棵,已經(jīng)對(duì).arsc進(jìn)行了比較大的優(yōu)化鳍寂,接下來(lái)分析一下其它優(yōu)化方式。
可以采用混淆來(lái)縮減資源文件的名稱情龄,以及移除未使用的備用資源等方式來(lái)優(yōu)化.arsc文件迄汛。如何移除未使用的備用資源捍壤,gradle里面
增加如下配置:
android {
defaultConfig {
...
resConfigs "zh", "zh_CN", "zh_HK", "zh_MO", "zh_TW", "en"
}
}
5. lib目錄優(yōu)化
只提供對(duì)主流架構(gòu)的支持,比如arm鞍爱,對(duì)于mips和x86架構(gòu)可以考慮不提供支持鹃觉,系統(tǒng)會(huì)自動(dòng)提供相應(yīng)的兼容。
除了插件化硬霍,客戶端還是用了RN的方案帜慢,從而引入了RN的so庫(kù)。由于RN的so庫(kù)資源比較大唯卖,有2M多粱玲,進(jìn)而引入了RN的so庫(kù)的插件化。通過(guò)so庫(kù)的插件化拜轨,來(lái)縮減包體積抽减。RN庫(kù)的插件化,包體積就縮減了1M多橄碾。
APK瘦身中要注意的問(wèn)題
WebP圖片的轉(zhuǎn)化過(guò)程中卵沉,一定要注意資源拼接的情況。比如如果存在vip_1,vip_2,vip_3,vip_4,vip_5等五個(gè)資源法牲,要么都轉(zhuǎn)化成WebP,要么都不轉(zhuǎn)史汗,不能處理其中的一部分。
替換一些引導(dǎo)圖的時(shí)候拒垃,一定要打包工具和客戶端同時(shí)替換停撞。如果客戶端把引導(dǎo)圖替換成了WebP格式,而打包的時(shí)候悼瓮,由于不同步戈毒,該圖片又被替換成png格式,就會(huì)導(dǎo)致資源加載不成功横堡,進(jìn)而程序崩潰埋市。
使用apksigner簽名工具前,必須先執(zhí)行zipalign操作命贴;而使用jarsigner簽名工具則是先簽名道宅,然后再用zipalign優(yōu)化。