本文會不定期更新税课,推薦watch下項目邮屁。如果喜歡請star姆涩,如果覺得有紕漏請?zhí)峤籭ssue,如果你有更好的點子可以提交pull request卫旱。本文意在分享作者在實踐中對于debug包和release包的打包提速的方案人灼。
本文固定連接:https://github.com/tianzhijiexian/Android-Best-Practices
需求
讓打包變得更快一點,再快一點顾翼!
實現(xiàn)
刪除不必要的module
AS的代碼結構和eclipse完全不同投放,它為開發(fā)者提供了單工程多module的形式。但多建立一個module就需要多維護一個module适贸。所以如果僅僅是為了方便寫代碼而建立一個module是不可取的灸芳,我強烈建議先做好項目結構的梳理再考慮是否需要建立module。
下面是一個多module的app結構圖:
在as中通過自帶的預覽工具拜姿,也可以幫助我們進行modules的梳理:
這個項目中的module有很多烙样,所以gradle在編譯的時候會去檢測module的依賴鏈,gradle會幫助我們層層梳理module之間的關系蕊肥,避免因為module之間相互引用而來帶的問題谒获。這些梳理工作和module的合并工作都會帶來build的時間,如果你的項目build十分緩慢壁却,我強烈建議你去梳理下module的關系批狱,合并部分module。將穩(wěn)定的底層module打包為aar展东,上傳到公司的maven倉庫赔硫,借此來加快build速度。
刪除module中的無用文件
as默認在建立module的同時會建立test目錄:
如果你根本沒有編寫測試代碼的打算盐肃,你完全可以刪除test目錄爪膊。
當然,如果你的module就是純代碼砸王,根本沒用到資源文件惊完,也請一并把res目錄刪除掉。
刪除主項目中無用的資源文件
項目開發(fā)中多少都會存留一些無用的代碼和資源处硬,資源越多打包合并資源的時間就越長。然而刪除無用的代碼對于提升打包速度的作用微乎其微拇派,我們可以利用混淆這一利器在打release包的時候將無用代碼一次性剔除掉荷辕。對于資源文件,as提供了自動檢測失效文件和刪除的功能件豌,這個絕對值得一試疮方。
在彈出的對話框中,我強烈建議不要勾選刪除無用的id茧彤,因為databinding會用到一些id骡显,但這在代碼中沒有體現(xiàn),所以as會認為這些id是無用的。如果你刪除了這些id惫谤,那么就等著編譯失敗吧壁顶。別問我是怎么知道的T_T。順便說一下溜歪,每次做這種操作前記得commit一下若专,方便做diff。
利用no-op加快debug的速度
如果項目中有很多公司自己的module依賴蝴猪,那么你完全可以采用類似于這篇的技巧调衰,給私有的module做no-op(什么是no-op可以看這篇的例子)。因為一般私有的module會比較穩(wěn)定自阱,并且對外暴露的方法不多嚎莉,甚至會是別的項目組開發(fā)的,所以如果這個module本身是用來做監(jiān)控統(tǒng)計這樣的不影響功能工作的話沛豌,建議和開發(fā)者商量提供no-op版本趋箩。文中的舉例:
debugCompile(project(':share-lib-no-op')) {}
releaseCompile(project(':share-lib')) {}
debugCompile(project(':zxing-no-op')) {}
releaseCompile(project(':zxing')) {}
用no-op版本的好處就是只使用接口不使用實現(xiàn),將實現(xiàn)的代碼全部剔除琼懊,如果做的好的話甚至可以再debug時不用multidex阁簸。但壞處就是需要進行協(xié)作交流,如果module對外的接口變動了哼丈,應該考慮到對no-op版本的影響启妹。
減少方法數(shù),不使用multidex
關于什么是multidex醉旦,和怎么使用它饶米,請參考這篇文章:
http://blog.csdn.net/t12x3456/article/details/40837287
它是一種不得已而為之的舉措吏垮,在使用的時候我經常會發(fā)現(xiàn)在一些特殊的機型上會出現(xiàn)一些奇奇怪怪的錯誤蝙叛,總之就是有很多坑焕数。
在build時間這一塊寻拂,multidex因為有分包和壓縮的過程摄杂,所以它對于編譯速度方面有有嚴重的影響厂捞。我通過dexcount這個插件分析了我們的項目后馍迄,發(fā)現(xiàn)項目中有一些庫已經不再用或者有更好的替代品兴泥,于是我精簡了第三方庫主卫,并且開啟了support包的混淆逃默,最終讓我們的項目的release包的方法數(shù)達到了一個合理的水平。
為了控制變量完域,我專門做了一個空項目,用來做support包混淆前后的對比瘩将,我們來看一下數(shù)據(jù):
當一個第三方sdk說不要混淆support包吟税,不要混淆我sdk的代碼的時候凹耙,我強烈建議你考慮下方法數(shù)的問題。混淆的作用之一是將代碼進行優(yōu)化和縮短方法名肠仪、字段名肖抱;作用之二就是刪除沒有被用到的變量和方法。第三方sdk的方法數(shù)眾多藤韵,如果沒辦法混淆虐沥,那么會帶來大量的方法數(shù),這點需要十分的小心泽艘∮眨混淆雖然是一個十分有用的工具,但也是很多錯誤的來源匹涮,所以我建議你小心謹慎的多多使用它天试!
對第三方庫進行優(yōu)化
上面講到了優(yōu)化第三方庫會減少方法數(shù),這里簡單講一下一般的優(yōu)化策略:
1.利用debugCompile來依賴debug時才用到的庫
debugCompile我在第三方庫開發(fā)實踐中已經講到了然低,這里就不多說了喜每。
2.利用更小的庫替代現(xiàn)有的庫
這個就要看開發(fā)人員的經驗和知識面了,雖然是廢話雳攘,如果能真正做到带兜,成果是極其明顯的。
3.利用exclude來排出某些不需要的依賴
以rn舉例吨灭,rn是一個龐大的庫刚照,引入rn后會依賴很多別的庫:
在我們的項目中,我利用了自己編寫的網絡請求模塊進行網絡請求喧兄,所以我就想要剔除掉rn引入的okhttp无畔,我又發(fā)現(xiàn)它還引入了support包,而我項目中也肯定有support包吠冤,所以我也想要排出掉它(不排除support包也沒事浑彰,gradle會僅包含最新的庫版本,我這里僅僅是舉個例子)
compile ('com.facebook.react:react-native:+'){
exclude group: 'com.squareup.okhttp3', module: 'okhttp'
exclude group: 'com.android.support', module: 'support-v4'
exclude group: 'com.android.support', module: 'support-v7'
}
重新build一次后拯辙,你會發(fā)現(xiàn)okhttp已經被剔除掉了:
對于本地的module也是可以這樣處理的:
compile(project(':react-native-custom-module')) {
exclude group: 'com.facebook.react', module: 'react-native'
}
用公司的倉庫做緩存
我推薦的做法是項目所有的依賴(私有或第三方)都通過公司的倉庫進行獲取郭变,公司的倉庫會自己查找jcenter等倉庫,下載好需要的依賴涯保,并進行緩存饵较。這樣的好處是,當一個同事引入了新庫或者更新庫版本后遭赂,別的同事在build時可以直接拿緩存,大大減少了下載依賴的時間横辆。這點雖然是小優(yōu)化撇他,但是對于新人和團隊協(xié)作來說是很重要的茄猫。
debug時跳過某些task
我們的項目中用到了很多gradle插件,有些插件會在build時運行自己的task:
tiny是用來壓縮圖片的困肩,buildtime是用來檢測build時間的划纽,dexcount是用來分析方法數(shù)的。這些插件對于我們的開發(fā)帶來了巨大的幫助锌畸,但也增加了build時間勇劣。
我分享下我的做法:
- 在每次發(fā)版本前開啟tiny,直接build一次潭枣,壓縮完圖片后將其關閉比默。
- 在需要檢測和診斷build時間的時候啟用buildtime,一般的debug時不開啟它盆犁。
- 在release包中開啟dexcount命咐,并且讓其于Jenkins進行結合。這樣既不會影響debug包谐岁,又可以進行方法數(shù)的持續(xù)監(jiān)控醋奠。
關于dexcount是如何和Jenkins結合的,并且是如何產生下面的圖表的伊佃,請參考:
http://www.th7.cn/Program/Android/201606/870070.shtml
放棄lambda表達式窜司,謹慎使用AspectJ
目前android不支持lambda,所以很多人都引入了 retrolambda航揉。一旦你引入了這個庫塞祈,你就必須面臨著字節(jié)碼轉換而帶來的build慢的問題。你用的越多迷捧,代碼看起來越簡單织咧,但build時間也會越來越長。所以漠秋,我不推薦在目前的階段使用它笙蒙,還是等等看看谷歌jack的表現(xiàn)吧。
AspectJ是aop的很好的工具庆锦,但因為需要在build時進行代碼的插入捅位,所以使用AspectJ后build時間會明顯的增加,具體看使用量而定搂抒。AspectJ的優(yōu)缺點十分明顯艇搀,我這里只是提出來,具體如何權衡求晶,就看大家自己了焰雕。我的話,因為用了UiBlock所以引入了AspectJ芳杏,讓我debug是build的速度慢了三秒鐘矩屁,但UiBlock的好處也十分明顯辟宗,所以我還是用了它。
dev包中設置minSdkVersion為21
因為在debug時吝秕,我們不會去開啟混淆泊脐,所以debug包是需要用mulitdex的
android5.0對于mulitdex做了優(yōu)化,具體可以參考官方的文章烁峭,我就直接說怎么做就好容客。先在gradle的配置中添加一個flavors,比如叫做dev约郁,在dev中配置最小支持的android版本為21.
然后在build時選中devDebug缩挑,這樣你debug的時候就是走最低支持api為21的編譯方式了。
特別注意:
你現(xiàn)在為了提速將最低版本寫為21棍现,假設你最終可能支持的是16调煎。這就有個風險點,因為as會在你寫代碼的時候認為你的應用就是支持21的己肮,所以對于一些1621的api不會有風險提示士袄。因此使用1621之間的api時需要人為的注意,這是最大的風險點;哑АBα!
開啟offline
這個是最簡單直接的加速方案了艘绍,效果極其明顯赤拒,誰用誰知道!
優(yōu)化gradle
gradle的各種優(yōu)化配置網上已經有很多了诱鞠,這里建議看這篇文章:
我自己的配置如下:
org.gradle.daemon=true
org.gradle.parallel=true
# ndk
android.useDeprecatedNdk=true
org.gradle.configureondemand=truex
org.gradle.jvmargs=-Xmx3072m -XX:MaxPermSize=1024m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
總的來說除了增加內存這一項感覺還有點用處外挎挖,其余配置都不痛不癢。我最后直接加了4g的內存條航夺,一次性解決了大多數(shù)的問題蕉朵。
優(yōu)化crashlytics的upload
上面講到的都是build過程中的提速,但打包不僅僅包含了build阳掐,還包含了混淆始衅,簽名等過程。如果你的項目用了crashlytics缭保,crashlytics會在混淆時自動上傳map文件到服務器汛闸,這樣可以幫助你在分析崩潰的時候看到的是混淆前的代碼和行數(shù),十分方便艺骂。
萬事有利有弊诸老,我們項目的map文件為6m左右,crashlytics的服務器又是在國外钳恕,所以每次都會需要很長的一段時間别伏。
優(yōu)化點主要是提升上行帶寬和網絡速度吮廉,前者需要硬件的支持,后者可以通過vpn進行優(yōu)化畸肆。在配置release包打包命令的時候,可以不用每次都把build目錄刪除宙址,這在一定程度上也可解決此問題轴脐。
利用MultiChannelPackageTool進行多渠道打包
我們的應用可能會被分發(fā)到多個渠道,而我們又想進行多個渠道的數(shù)據(jù)分析抡砂,這就產生了目前android要打多個渠道包的現(xiàn)狀大咱。這篇文章詳細的分析了國內最高效的打包方案,文章短小精干注益,值得一讀碴巾。
我選擇的是MultiChannelPackageTool來進行打包,它的速度是最快的丑搔,而且使用方式十分的簡單厦瓢。他的原理是在zip文件的comment中加入渠道號,這樣既可以寫入渠道號又不會破壞zip的簽名啤月,因為apk本身就是一個zip文件煮仇,所以這個規(guī)則是可靠并完全適用的。
具體的原理和實現(xiàn)方案也不難谎仲,這里可以參考趙林寫的這篇文章進行深入了解浙垫。
下面我給大家演示下實際的情況:
現(xiàn)在我們可以通過
MCPTool.getChannelId(context, "password", "")
得到渠道名稱,如果你用的是友盟來做監(jiān)控和統(tǒng)計郑诺,那么你肯定需要在代碼中設置友盟的key和channel名夹姥。通過友盟的文檔和論壇我發(fā)現(xiàn)友盟最新的sdk提供了這樣的機制,于是就有了如下代碼:
// 設置key和渠道號辙诞,在application中就需要進行設置
UMAnalyticsConfig config = new UMAnalyticsConfig(context, appKey, channelId);
MobclickAgent.startWithConfigure(config);
// 得到key和渠道號
String appKey = AnalyticsConfig.getAppkey(activity);
String channel = AnalyticsConfig.getChannel(activity);
采用增量編譯
as目前已經支持了增量編譯辙售,但是效果真的很差,甚至經常會增加build時間倘要,所以這里我還是推薦一直在更新的Jrebel做增量編譯的工具圾亏。我之前寫《Android中UI實時預覽實踐》的時候就有推薦過它,只不過那時候真的太貴了》馀。現(xiàn)在as出了增量編譯志鹃,它也坐不住了,立刻降價泽西,價錢還算是可以接收曹铃。至于效果嘛,我可以說是目前android增量編譯做的最好的了捧杉,如果你寫的是小型應用的話陕见,效果會更好∶匮現(xiàn)在它已經不用我們單獨配置maven倉庫了,完全和項目解耦评甜,而且它竟然支持注解和aop灰粮,堪稱黑科技!所以忍坷,如果你有心想要加快打包的速度粘舟,我強烈推薦你去試用上21天,看看它是否值得你為之付費佩研。
升級jdk和gradle
最新的Gradle要比老版本要快柑肴,jdk1.8比jdk1.6要快,所以可以跟著官方升級新版本就好旬薯。