Android打包提速實(shí)踐

本文會不定期更新,推薦watch下項(xiàng)目。如果喜歡請star,如果覺得有紕漏請?zhí)峤籭ssue,如果你有更好的點(diǎn)子可以提交pull request。本文意在分享作者在實(shí)踐中對于debug包和release包的打包提速的方案。

本文固定連接:https://github.com/tianzhijiexian/Android-Best-Practices

需求

讓打包變得更快一點(diǎn),再快一點(diǎn)!

實(shí)現(xiàn)

刪除不必要的module

AS的代碼結(jié)構(gòu)和eclipse完全不同,它為開發(fā)者提供了單工程多module的形式。但多建立一個module就需要多維護(hù)一個module。所以如果僅僅是為了方便寫代碼而建立一個module是不可取的,我強(qiáng)烈建議先做好項(xiàng)目結(jié)構(gòu)的梳理再考慮是否需要建立module。
下面是一個多module的app結(jié)構(gòu)圖:

framework

在as中通過自帶的預(yù)覽工具,也可以幫助我們進(jìn)行modules的梳理:

module

這個項(xiàng)目中的module有很多,所以gradle在編譯的時候會去檢測module的依賴鏈,gradle會幫助我們層層梳理module之間的關(guān)系,避免因?yàn)閙odule之間相互引用而來帶的問題。這些梳理工作和module的合并工作都會帶來build的時間,如果你的項(xiàng)目build十分緩慢,我強(qiáng)烈建議你去梳理下module的關(guān)系,合并部分module。將穩(wěn)定的底層module打包為aar,上傳到公司的maven倉庫,借此來加快build速度。

刪除module中的無用文件

as默認(rèn)在建立module的同時會建立test目錄:

test

如果你根本沒有編寫測試代碼的打算,你完全可以刪除test目錄。
當(dāng)然,如果你的module就是純代碼,根本沒用到資源文件,也請一并把res目錄刪除掉。

res

刪除主項(xiàng)目中無用的資源文件

項(xiàng)目開發(fā)中多少都會存留一些無用的代碼和資源,資源越多打包合并資源的時間就越長。然而刪除無用的代碼對于提升打包速度的作用微乎其微,我們可以利用混淆這一利器在打release包的時候?qū)o用代碼一次性剔除掉。對于資源文件,as提供了自動檢測失效文件和刪除的功能,這個絕對值得一試。

remove res

在彈出的對話框中,我強(qiáng)烈建議不要勾選刪除無用的id,因?yàn)閐atabinding會用到一些id,但這在代碼中沒有體現(xiàn),所以as會認(rèn)為這些id是無用的。如果你刪除了這些id,那么就等著編譯失敗吧。別問我是怎么知道的T_T。順便說一下,每次做這種操作前記得commit一下,方便做diff。

dialog

利用no-op加快debug的速度

如果項(xiàng)目中有很多公司自己的module依賴,那么你完全可以采用類似于這篇的技巧,給私有的module做no-op(什么是no-op可以看這篇的例子)。因?yàn)橐话闼接械膍odule會比較穩(wěn)定,并且對外暴露的方法不多,甚至?xí)莿e的項(xiàng)目組開發(fā)的,所以如果這個module本身是用來做監(jiān)控統(tǒng)計(jì)這樣的不影響功能工作的話,建議和開發(fā)者商量提供no-op版本。文中的舉例:

debugCompile(project(':share-lib-no-op')) {}
releaseCompile(project(':share-lib')) {}
debugCompile(project(':zxing-no-op')) {}
releaseCompile(project(':zxing')) {}

用no-op版本的好處就是只使用接口不使用實(shí)現(xiàn),將實(shí)現(xiàn)的代碼全部剔除,如果做的好的話甚至可以再debug時不用multidex。但壞處就是需要進(jìn)行協(xié)作交流,如果module對外的接口變動了,應(yīng)該考慮到對no-op版本的影響。

減少方法數(shù),不使用multidex

關(guān)于什么是multidex,和怎么使用它,請參考這篇文章:
http://blog.csdn.net/t12x3456/article/details/40837287

它是一種不得已而為之的舉措,在使用的時候我經(jīng)常會發(fā)現(xiàn)在一些特殊的機(jī)型上會出現(xiàn)一些奇奇怪怪的錯誤,總之就是有很多坑。
在build時間這一塊,multidex因?yàn)橛蟹职蛪嚎s的過程,所以它對于編譯速度方面有有嚴(yán)重的影響。我通過dexcount這個插件分析了我們的項(xiàng)目后,發(fā)現(xiàn)項(xiàng)目中有一些庫已經(jīng)不再用或者有更好的替代品,于是我精簡了第三方庫,并且開啟了support包的混淆,最終讓我們的項(xiàng)目的release包的方法數(shù)達(dá)到了一個合理的水平。

優(yōu)化前
精簡庫,開啟support包的混淆后

為了控制變量,我專門做了一個空項(xiàng)目,用來做support包混淆前后的對比,我們來看一下數(shù)據(jù):

混淆前
混淆后

當(dāng)一個第三方sdk說不要混淆support包,不要混淆我sdk的代碼的時候,我強(qiáng)烈建議你考慮下方法數(shù)的問題。混淆的作用之一是將代碼進(jìn)行優(yōu)化和縮短方法名、字段名;作用之二就是刪除沒有被用到的變量和方法。第三方sdk的方法數(shù)眾多,如果沒辦法混淆,那么會帶來大量的方法數(shù),這點(diǎn)需要十分的小心?;煜m然是一個十分有用的工具,但也是很多錯誤的來源,所以我建議你小心謹(jǐn)慎的多多使用它!

對第三方庫進(jìn)行優(yōu)化

上面講到了優(yōu)化第三方庫會減少方法數(shù),這里簡單講一下一般的優(yōu)化策略:

1.利用debugCompile來依賴debug時才用到的庫
debugCompile我在第三方庫開發(fā)實(shí)踐中已經(jīng)講到了,這里就不多說了。

2.利用更小的庫替代現(xiàn)有的庫
這個就要看開發(fā)人員的經(jīng)驗(yàn)和知識面了,雖然是廢話,如果能真正做到,成果是極其明顯的。

3.利用exclude來排出某些不需要的依賴
以rn舉例,rn是一個龐大的庫,引入rn后會依賴很多別的庫:

rn

在我們的項(xiàng)目中,我利用了自己編寫的網(wǎng)絡(luò)請求模塊進(jìn)行網(wǎng)絡(luò)請求,所以我就想要剔除掉rn引入的okhttp,我又發(fā)現(xiàn)它還引入了support包,而我項(xiàng)目中也肯定有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已經(jīng)被剔除掉了:

exclude okhttp

對于本地的module也是可以這樣處理的:

compile(project(':react-native-custom-module')) {
    exclude group: 'com.facebook.react', module: 'react-native'
}

用公司的倉庫做緩存

我推薦的做法是項(xiàng)目所有的依賴(私有或第三方)都通過公司的倉庫進(jìn)行獲取,公司的倉庫會自己查找jcenter等倉庫,下載好需要的依賴,并進(jìn)行緩存。這樣的好處是,當(dāng)一個同事引入了新庫或者更新庫版本后,別的同事在build時可以直接拿緩存,大大減少了下載依賴的時間。這點(diǎn)雖然是小優(yōu)化,但是對于新人和團(tuán)隊(duì)協(xié)作來說是很重要的。

debug時跳過某些task

我們的項(xiàng)目中用到了很多gradle插件,有些插件會在build時運(yùn)行自己的task:

gradle plugin

tiny是用來壓縮圖片的,buildtime是用來檢測build時間的,dexcount是用來分析方法數(shù)的。這些插件對于我們的開發(fā)帶來了巨大的幫助,但也增加了build時間。

我分享下我的做法:

  1. 在每次發(fā)版本前開啟tiny,直接build一次,壓縮完圖片后將其關(guān)閉。
  2. 在需要檢測和診斷build時間的時候啟用buildtime,一般的debug時不開啟它。
  3. 在release包中開啟dexcount,并且讓其于Jenkins進(jìn)行結(jié)合。這樣既不會影響debug包,又可以進(jìn)行方法數(shù)的持續(xù)監(jiān)控。

關(guān)于dexcount是如何和Jenkins結(jié)合的,并且是如何產(chǎn)生下面的圖表的,請參考:
http://www.th7.cn/Program/Android/201606/870070.shtml

dexcount

放棄lambda表達(dá)式,謹(jǐn)慎使用AspectJ

目前android不支持lambda,所以很多人都引入了 retrolambda。一旦你引入了這個庫,你就必須面臨著字節(jié)碼轉(zhuǎn)換而帶來的build慢的問題。你用的越多,代碼看起來越簡單,但build時間也會越來越長。所以,我不推薦在目前的階段使用它,還是等等看看谷歌jack的表現(xiàn)吧。

AspectJ是aop的很好的工具,但因?yàn)樾枰赽uild時進(jìn)行代碼的插入,所以使用AspectJ后build時間會明顯的增加,具體看使用量而定。AspectJ的優(yōu)缺點(diǎn)十分明顯,我這里只是提出來,具體如何權(quán)衡,就看大家自己了。我的話,因?yàn)橛昧?a target="_blank" rel="nofollow">UiBlock所以引入了AspectJ,讓我debug是build的速度慢了三秒鐘,但UiBlock的好處也十分明顯,所以我還是用了它。

dev包中設(shè)置minSdkVersion為21

因?yàn)樵赿ebug時,我們不會去開啟混淆,所以debug包是需要用mulitdex的

debugApplication

android5.0對于mulitdex做了優(yōu)化,具體可以參考官方的文章,我就直接說怎么做就好。先在gradle的配置中添加一個flavors,比如叫做dev,在dev中配置最小支持的android版本為21.

gradle

然后在build時選中devDebug,這樣你debug的時候就是走最低支持api為21的編譯方式了。


build

特別注意:
你現(xiàn)在為了提速將最低版本寫為21,假設(shè)你最終可能支持的是16。這就有個風(fēng)險(xiǎn)點(diǎn),因?yàn)閍s會在你寫代碼的時候認(rèn)為你的應(yīng)用就是支持21的,所以對于一些1621的api不會有風(fēng)險(xiǎn)提示。因此使用1621之間的api時需要人為的注意,這是最大的風(fēng)險(xiǎn)點(diǎn)?。。?/p>

開啟offline

這個是最簡單直接的加速方案了,效果極其明顯,誰用誰知道!

offline

優(yōu)化gradle

gradle的各種優(yōu)化配置網(wǎng)上已經(jīng)有很多了,這里建議看這篇文章

我自己的配置如下:

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

總的來說除了增加內(nèi)存這一項(xiàng)感覺還有點(diǎn)用處外,其余配置都不痛不癢。我最后直接加了4g的內(nèi)存條,一次性解決了大多數(shù)的問題。

優(yōu)化crashlytics的upload

上面講到的都是build過程中的提速,但打包不僅僅包含了build,還包含了混淆,簽名等過程。如果你的項(xiàng)目用了crashlytics,crashlytics會在混淆時自動上傳map文件到服務(wù)器,這樣可以幫助你在分析崩潰的時候看到的是混淆前的代碼和行數(shù),十分方便。
萬事有利有弊,我們項(xiàng)目的map文件為6m左右,crashlytics的服務(wù)器又是在國外,所以每次都會需要很長的一段時間。
優(yōu)化點(diǎn)主要是提升上行帶寬和網(wǎng)絡(luò)速度,前者需要硬件的支持,后者可以通過vpn進(jìn)行優(yōu)化。在配置release包打包命令的時候,可以不用每次都把build目錄刪除,這在一定程度上也可解決此問題。

利用MultiChannelPackageTool進(jìn)行多渠道打包

我們的應(yīng)用可能會被分發(fā)到多個渠道,而我們又想進(jìn)行多個渠道的數(shù)據(jù)分析,這就產(chǎn)生了目前android要打多個渠道包的現(xiàn)狀。這篇文章詳細(xì)的分析了國內(nèi)最高效的打包方案,文章短小精干,值得一讀。
我選擇的是MultiChannelPackageTool來進(jìn)行打包,它的速度是最快的,而且使用方式十分的簡單。他的原理是在zip文件的comment中加入渠道號,這樣既可以寫入渠道號又不會破壞zip的簽名,因?yàn)閍pk本身就是一個zip文件,所以這個規(guī)則是可靠并完全適用的。

comment

具體的原理和實(shí)現(xiàn)方案也不難,這里可以參考趙林寫的這篇文章進(jìn)行深入了解。

下面我給大家演示下實(shí)際的情況:

package

現(xiàn)在我們可以通過

MCPTool.getChannelId(context, "password", "")

得到渠道名稱,如果你用的是友盟來做監(jiān)控和統(tǒng)計(jì),那么你肯定需要在代碼中設(shè)置友盟的key和channel名。通過友盟的文檔和論壇我發(fā)現(xiàn)友盟最新的sdk提供了這樣的機(jī)制,于是就有了如下代碼:

// 設(shè)置key和渠道號,在application中就需要進(jìn)行設(shè)置
UMAnalyticsConfig config = new UMAnalyticsConfig(context, appKey, channelId);
MobclickAgent.startWithConfigure(config);

// 得到key和渠道號
String appKey = AnalyticsConfig.getAppkey(activity);
String channel = AnalyticsConfig.getChannel(activity);

采用增量編譯

as目前已經(jīng)支持了增量編譯,但是效果真的很差,甚至經(jīng)常會增加build時間,所以這里我還是推薦一直在更新的Jrebel做增量編譯的工具。我之前寫《Android中UI實(shí)時預(yù)覽實(shí)踐》的時候就有推薦過它,只不過那時候真的太貴了?,F(xiàn)在as出了增量編譯,它也坐不住了,立刻降價(jià),價(jià)錢還算是可以接收。至于效果嘛,我可以說是目前android增量編譯做的最好的了,如果你寫的是小型應(yīng)用的話,效果會更好?,F(xiàn)在它已經(jīng)不用我們單獨(dú)配置maven倉庫了,完全和項(xiàng)目解耦,而且它竟然支持注解和aop,堪稱黑科技!所以,如果你有心想要加快打包的速度,我強(qiáng)烈推薦你去試用上21天,看看它是否值得你為之付費(fèi)。

jirebel

升級jdk和gradle

最新的Gradle要比老版本要快,jdk1.8比jdk1.6要快,所以可以跟著官方升級新版本就好。

developer_kale@foxmail.com
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,319評論 25 708
  • 0x01 基本項(xiàng)目結(jié)構(gòu) 使用Android Studio創(chuàng)建的Android項(xiàng)目會劃分成三個層級: project...
    銀小古兒閱讀 2,369評論 1 2
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,688評論 19 139
  • liaoping閱讀 190評論 0 0
  • 當(dāng)時間過了午夜,再沉厚的黑夜也會摻上黎明希望??墒钱?dāng)我嗅到這縷希望,心中滿是對自己的咒罵。 開始動手寫這篇文章的時...
    人生空白期閱讀 187評論 0 1

友情鏈接更多精彩內(nèi)容