現(xiàn)在你已經(jīng)知道了Gradle是如何工作的哟冬,如何創(chuàng)建你自己的任務(wù)和插件秸苗,如何運(yùn)行測試疚察,以及如何設(shè)置持續(xù)集成派继,你差不多可以稱呼自己為Gradle專家了。本章會(huì)包含一些之前沒有提及的竅門和技巧捻艳,使使用Gradle進(jìn)行構(gòu)建驾窟、開發(fā)和部署變得簡單。
本章內(nèi)容有:
- 減小APK體積
- 加速構(gòu)建
- 忽略Lint檢查
- 在Gradle中使用Ant
- 高級應(yīng)用部署
減小APK體積
在過去的幾年里认轨,apk文件的體積有了顯著的增大绅络。這里有幾個(gè)原因:Android開發(fā)者有更多的庫可以使用,增加了更多的密度嘁字,以及應(yīng)用有了更多的功能恩急。
保證APK有更小的體積是很好的想法。不僅是因?yàn)镚oole Play有50M大小的限制纪蜒,還因?yàn)楦〉腁PK意味著用戶可以更快地下載并安裝應(yīng)用衷恭,以及占用更少的內(nèi)存。
本節(jié)纯续,我們會(huì)討論幾個(gè)Gradle構(gòu)建文件的屬性随珠,來幫助減小APK文件體積。
ProGuard
ProGuard是一個(gè)java工具猬错,在編譯階段不僅可以壓縮窗看,還可以優(yōu)化,混淆倦炒,預(yù)先審核代碼显沈。它會(huì)遍歷應(yīng)用中的所有代碼路徑,找到不用的代碼并刪除它逢唤。ProGuard也會(huì)重命名你的類和成員拉讯。這個(gè)過程可以減少應(yīng)用的內(nèi)存占用,并增大反編譯的難度智玻。
Gradle Android插件有一個(gè)minifyEnabled
屬性可以設(shè)置開啟ProGuard:
android {
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
當(dāng)你設(shè)置minifyEnabled
為true后遂唧,proguardRelease
任務(wù)就會(huì)執(zhí)行,并且在構(gòu)建過程中觸發(fā)ProGuard吊奢。
最好在打開ProGuard后重新檢查一下整個(gè)應(yīng)用盖彭,因?yàn)樗赡芴蕹恍┠闳匀恍枰拇a。這是ProGuard令很多開發(fā)者苦惱的問題页滚。為了解決這個(gè)問題召边,你可以定義ProGuard規(guī)則,將不需要移除或者混淆的類排除出去裹驰。proguardFiles
屬性用來定義包含ProGuard規(guī)則的文件隧熙。比如,要保持一個(gè)類不變幻林,你可以添加如下規(guī)則:
-keep public class <MyClass>
getDefaultProguardFile('proguard-android.txt')
方法從proguard-android.txt
文件獲取ProGuard設(shè)置贞盯,該文件在Android SDK的tools/proguard
目錄下音念。proguard-rules.pro
文件由Android Studio默認(rèn)添加到模塊中,所以你可以很簡單的在該文件添加這個(gè)模塊的規(guī)則躏敢。
ProGuard規(guī)則對于每個(gè)app或者library都是不同的闷愤,所以本書不會(huì)深入研究細(xì)節(jié)。如果你想了解更多相關(guān)內(nèi)容件余,可以瀏覽http://developer.android.com/tools/help/proguard.html
除了壓縮Java代碼讥脐,也可以壓縮不用的資源。
壓縮資源
Gradle和Gradle Android插件可以在構(gòu)建時(shí)去掉不用的資源啼器。這在你有一些舊的忘掉刪除的資源時(shí)非常有用旬渠。另一個(gè)場景是你引入了一個(gè)包含很多資源的庫,但你只用了很少一部分端壳。你可以打開資源壓縮來處理這種情況告丢。有兩種方式可以進(jìn)行資源壓縮:自動(dòng)和手動(dòng)。
自動(dòng)壓縮
最簡單的方式是在你的構(gòu)建中配置shrinkResources
屬性更哄。如果設(shè)置為true芋齿,Android構(gòu)建工具會(huì)自動(dòng)檢測哪些資源沒有用到,不應(yīng)該包含到APK中成翩。
使用這個(gè)特性有一個(gè)需求觅捆,就是你也必須使用ProGuard。這源于資源壓縮的工作方式麻敌,因?yàn)锳ndroid構(gòu)建工具在引用資源的代碼被移除之前無法確定哪些資源是沒有用到的栅炒。
下面的代碼片段展示了如何在一個(gè)特定的構(gòu)建類型中配置自動(dòng)壓縮資源:
android {
buildTypes {
release {
minifyEnabled = true
shrinkResources = true
}
}
}
如果你想準(zhǔn)確知道開啟資源壓縮后,APK減小了多少术羔,你可以運(yùn)行shrinkReleaseResources
任務(wù)赢赊。這個(gè)任務(wù)會(huì)打印出包減小的大小:
:app:shrinkReleaseResources
Removed unused resources: Binary resource data reduced from 433KB
to 354KB: Removed 18%
通過給構(gòu)建命令添加--info
標(biāo)識(shí)级历,你可以得到關(guān)于APK去除的資源的詳細(xì)概況:
$ gradlew clean assembleRelease --info
使用這個(gè)標(biāo)識(shí)后释移,Gradle會(huì)打印出構(gòu)建過程的更多的信息,包括最后的輸出不包含的資源寥殖。
自動(dòng)資源壓縮的一個(gè)問題是它可能移除過多的資源玩讳。尤其是被動(dòng)態(tài)使用的資源可能被意外刪除。為防止這種情況嚼贡,你可以在res/raw/
目錄放置keep.xml
文件來定義一些特殊情況熏纯。一個(gè)簡單的keep.xml
文件如下:
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"
tools:keep="@layout/keep_me,@layout/also_used_*"/>
keep.xml
本身會(huì)在最終的輸出中被移除。
手動(dòng)壓縮
一個(gè)不那么極端的移除資源的方式是除去特定的語言文件或者特定密度的圖像粤策。某些庫樟澜,比如Google Play Services,包含很多語言。如果你的應(yīng)用只支持一種或者兩種語言秩贰,就沒必要在最終的APK中包含所有的語言文件霹俺。你可以使用resConfigs
屬性來配置你想保留的資源,其余的將被移除萍膛。
如果你只想保留英語吭服、丹麥語和荷蘭語的字符串資源,可以如下配置:
android {
defaultConfig {
resConfigs "en", "da", "nl"
}
}
你也可以配置密度:
android {
defaultConfig {
resConfigs "hdpi", "xhdpi", "xxhdpi", "xxxhdpi"
}
}
你還可以結(jié)合語言和密度蝗罗。實(shí)際上,可以使用此屬性限制所有類型的資源蝌戒。
如果你在艱難地設(shè)置ProGuard串塑,或者你只想去除應(yīng)用不支持的語言和密度資源,使用resConfigs
是一個(gè)很好的壓縮資源的方式北苟。
加速構(gòu)建
很多剛開始使用Gradle的Android開發(fā)者會(huì)抱怨它編譯時(shí)間長桩匪。Gradle比Ant的構(gòu)建時(shí)間要長,因?yàn)槊看文銏?zhí)行任務(wù)友鼻,它都需要走完生命周期的三個(gè)階段傻昙。這使它的整個(gè)過程是可配置的,但是也會(huì)變慢很多彩扔。幸運(yùn)的是妆档,有幾種方式可以加快Gradle的構(gòu)建。
Gradle屬性
一個(gè)加速Gradle構(gòu)建速度的方法是修改默認(rèn)的配置虫碉。我們在第五章已經(jīng)提及了并行構(gòu)建執(zhí)行贾惦,除此之外,你仍然可以調(diào)整一些其他的配置敦捧。
回顧一下须板,你可以通過設(shè)置項(xiàng)目根目錄的gradle.properties
文件打開并行構(gòu)建。只需要添加如下屬性:
org.gradle.parallel=true
另一個(gè)簡單的方式是打開Gradle守護(hù)進(jìn)程兢卵,這會(huì)在你第一次構(gòu)建的時(shí)候啟動(dòng)一個(gè)后臺(tái)進(jìn)程习瑰。任何串行構(gòu)建都會(huì)重復(fù)利用這個(gè)后臺(tái)進(jìn)程,這就減少了啟動(dòng)成本秽荤。這個(gè)進(jìn)程在你使用Gradle期間會(huì)一直保留甜奄,直到空閑3個(gè)小時(shí)后才會(huì)殺死。在你短時(shí)間內(nèi)多次使用Gradle的情況下,使用守護(hù)進(jìn)程是很有用的游沿『凡可以如下打開守護(hù)進(jìn)程:
org.gradle.daemon=true
在Android Studio中,Gradle守護(hù)進(jìn)程是默認(rèn)打開的第喳。這意味著在IDE第一次構(gòu)建之后,接下來的構(gòu)建會(huì)稍微快一些踱稍。如果你通過命令行進(jìn)行構(gòu)建曲饱,Gradle守護(hù)進(jìn)程是關(guān)閉的悠抹,除非你在屬性中打開它。
為了加速編譯扩淀,你可以修改JVM的參數(shù)楔敌。你可以使用jvmargs
這個(gè)Gradle屬性來設(shè)置JVM內(nèi)存分配池的值。兩個(gè)對構(gòu)建速度有直接影響的參數(shù)是Xms
和Xmx
驻谆。Xms
參數(shù)用來設(shè)置初始的內(nèi)存值卵凑,Xmx
參數(shù)用來設(shè)置最大值。你可以在gradle.properties
文件中手動(dòng)設(shè)置這兩個(gè)值:
org.gradle.jvmargs=-Xms256m -Xmx1024m
你需要設(shè)置一個(gè)值和一個(gè)單位(k,m,g)胜臊。最大內(nèi)存分配(Xmx)默認(rèn)是256MB勺卢,起始內(nèi)存分配(Xms)默認(rèn)沒有設(shè)置∠蠖裕可設(shè)置的值依賴你電腦的內(nèi)存大小黑忱。
最后一個(gè)你可以配置的屬性是org.gradle.configureondemand
。如果你的工程有幾個(gè)模塊勒魔,這會(huì)非常有用甫煞。因?yàn)樗梢院雎援?dāng)前任務(wù)不需要的模塊,來嘗試減少配置階段的耗時(shí)冠绢。如果該屬性設(shè)置為true
抚吠,Gradle在運(yùn)行配置階段之前,會(huì)嘗試計(jì)算出哪些模塊更改了配置唐全,哪些沒有埃跷。如果你的項(xiàng)目是單app或者library的項(xiàng)目,這個(gè)特性將不會(huì)有太大的作用邮利。如果你有很多松散耦合的模塊弥雹,這個(gè)特性可以節(jié)省你很多構(gòu)建時(shí)間。
系統(tǒng)范圍的Gradle屬性
如果你想將這些屬性應(yīng)用到所有的基于Gradle的項(xiàng)目中延届,你可以在home目錄的.gradle文件夾創(chuàng)建一個(gè)gradle.properties
文件剪勿。在home目錄設(shè)置這些屬性是很好的實(shí)踐。因?yàn)橥ǔD阆虢档驮跇?gòu)建服務(wù)器上的內(nèi)存消耗方庭,構(gòu)建時(shí)間相對來說不是那么重要厕吉。
Android Studio
Android Studio的設(shè)置中同樣包含這些可以加速構(gòu)建時(shí)間的配置。打開Settings械念,導(dǎo)航到Build,Execution,Deployment|Compiler头朱。該頁面,你可以找到并行構(gòu)建龄减、JVM選項(xiàng)项钮,configure on demand等配置。這些配置只在基于Gradle的Android模塊中才會(huì)出現(xiàn)。
性能分析
如果你想找到哪一部分減慢了構(gòu)建速度烁巫,可以分析整個(gè)構(gòu)建過程署隘。你可以在執(zhí)行Gradle任務(wù)的時(shí)候添加--profile
標(biāo)識(shí)。有了這個(gè)標(biāo)識(shí)亚隙,Gradle會(huì)生成一個(gè)性能報(bào)告磁餐,告訴你哪一部分耗時(shí)過長。知道了瓶頸阿弃,你就可以進(jìn)行必要的優(yōu)化诊霹。報(bào)告以HTML的形式保存在模塊的build/reports/profile
目錄。
性能報(bào)告展示任務(wù)執(zhí)行過程中渣淳,每個(gè)階段耗時(shí)的概況畅哑。Summary展示了Gradle在配置階段為每一個(gè)模塊消耗的時(shí)間。Dependency Resolution展示了每個(gè)模塊解決依賴的時(shí)間水由。Task Execution展示了任務(wù)執(zhí)行細(xì)節(jié),包含每個(gè)單獨(dú)任務(wù)的耗時(shí)赛蔫,排列順序從耗時(shí)高到耗時(shí)低排序砂客。
Jack和Jill
如果你想使用實(shí)驗(yàn)工具,你可以打開Jack和Jill來加速構(gòu)建時(shí)間呵恢。Jack(Java Android Compiler Kit)是一個(gè)新的Android構(gòu)建工具鏈鞠值,可以直接將Java資源代碼編譯成dex格式。它有自己的.java
庫格式渗钉,同時(shí)接管了打包和壓縮彤恶。**Jill(Jack Intermediate Library Linker)是一個(gè)將.aar
和.jar
文件轉(zhuǎn)換為.jack
庫的工具。這些工具仍然是實(shí)驗(yàn)性質(zhì)的鳄橘,但它們可以用來加速構(gòu)建時(shí)間声离,簡化Android構(gòu)建過程。雖然不建議在生產(chǎn)版本使用它們瘫怜,但可以嘗試一下术徊。
為了使用Jack和Jill,你需要使用21.1.1或更高版本的build tools鲸湃,1.0.0或更高版本的Gradle Android插件赠涮。在defaultConfig
塊打開Jack和Jill:
android {
buildToolsRevision '22.0.1'
defaultConfig {
useJack = true
}
}
你也可以在特定的構(gòu)建類型或product flavor中打開Jack和Jill。這種情況下暗挑,你可以繼續(xù)使用常規(guī)構(gòu)建工具鏈笋除,并額外使用實(shí)驗(yàn)性質(zhì)的構(gòu)建:
android {
productFlavors {
regular {
useJack = false
}
experimental {
useJack = true
}
}
}
一旦設(shè)置了useJack=true
,精簡和混淆將不再使用ProGuard炸裆,但你仍然可以使用ProGuard規(guī)則語法來指定特定的規(guī)則和例外垃它,同樣可以使用proguardFiles
方法。
忽略Lint
在使用Gradle執(zhí)行release構(gòu)建時(shí),Lint檢查將會(huì)執(zhí)行嗤瞎。Lint是一個(gè)靜態(tài)代碼檢查工具墙歪,會(huì)標(biāo)識(shí)出你的布局和代碼中潛在的問題。在某些情況下贝奇,甚至?xí)枞麡?gòu)建過程虹菲。如果你的工程之前沒有使用過Lint,并且你想遷移到Gradle中掉瞳,Lint檢查可能會(huì)有很多錯(cuò)誤毕源。為使構(gòu)建可以進(jìn)行,你可以通過關(guān)閉abortOnError
忽略Lint檢查陕习,阻止它中斷構(gòu)建霎褐。這只是一個(gè)臨時(shí)解決方案,因?yàn)楹雎訪int錯(cuò)誤可能導(dǎo)致一些問題该镣,比如丟失翻譯冻璃,這可能引起應(yīng)用崩潰。代碼如下:
android {
lintOptions {
abortOnError false
}
}
臨時(shí)關(guān)閉Lint檢查可以很容將Ant構(gòu)建遷移到Gradle中损合。另一種平滑過渡方式是在Gradle中執(zhí)行Ant任務(wù)省艳。
在Gradle中使用Ant
如果你已經(jīng)花費(fèi)了大量時(shí)間來設(shè)置Ant構(gòu)建,那么切換到Gradle可能聽起來會(huì)很恐怖嫁审。在這種情況下跋炕,Gradle不僅能夠執(zhí)行Ant任務(wù),還能擴(kuò)展它們律适。這意味著你可以用很少的步驟從Ant遷移到Gradle,而不是花費(fèi)好幾天來進(jìn)行項(xiàng)目轉(zhuǎn)換辐烂。
Gradle為Ant集成使用Groovy的AntBuilder。AntBuilder使你可以執(zhí)行任何標(biāo)準(zhǔn)的Ant任務(wù)捂贿,你自定義的Ant任務(wù)和整個(gè)Ant構(gòu)建纠修。你還可以在Gradle構(gòu)建配置中定義Ant屬性。
在Gradle中運(yùn)行Ant任務(wù)
Gradle可以直接運(yùn)行Ant任務(wù)眷蜓。你只需要為相應(yīng)的Ant任務(wù)添加ant.
前綴就可以了分瘾。比如,創(chuàng)建一個(gè)歸檔:
task archive << {
ant.echo 'Ant is archiving...'
ant.zip(destfile: 'archive.zip') {
fileset(dir: 'zipme')
}
}
這個(gè)任務(wù)在Gradle中定義吁系,使用了echo
和zip
這兩個(gè)Ant任務(wù)德召。
你更應(yīng)該首先考慮Gradle相同功能的任務(wù)。上例中汽纤,你可以定義相應(yīng)的Gradle任務(wù):
task gradleArchive(type:Zip) << {
from 'zipme/'
archiveName 'grarchive.zip'
}
相應(yīng)的Gradle任務(wù)更加簡潔和易于理解上岗,也更加高效。
引入整個(gè)Ant腳本
如果你創(chuàng)建了一個(gè)Ant腳本來構(gòu)建應(yīng)用蕴坪,你可以使用ant.importBuild
來引入整個(gè)構(gòu)建配置肴掷。所有的Ant targets會(huì)自動(dòng)轉(zhuǎn)換為Gradle任務(wù)敬锐,你可以通過原始名稱訪問它們。比如下面這個(gè)Ant構(gòu)建文件:
<project>
<target name="hello">
<echo>Hello, Ant</echo>
</target>
</project>
你可以這樣引入到Gradle:
ant.importBuild 'build.xml'
hello
任務(wù)將會(huì)出現(xiàn)在Gradle構(gòu)建中呆瞻,你可以像常規(guī)Gradle任務(wù)一樣執(zhí)行它:
$ gradlew hello
:hello
[ant:echo] Hello, Ant
因?yàn)锳nt任務(wù)被轉(zhuǎn)換為了Gradle任務(wù)台夺,所以你可以使用doFirst
和doLast
或者<<
來進(jìn)行擴(kuò)展。比如痴脾,你可以在控制臺(tái)打印另一行:
hello << {
println 'Hello, Ant. It\'s me, Gradle'
}
執(zhí)行結(jié)果為:
$ gradlew hello
:hello
[ant:echo] Hello, Ant
Hello, Ant. It's me, Gradle
你也可以依賴這些來自Ant的任務(wù)颤介。比如:
task hi(dependsOn: hello) << {
println 'Hi!'
}
結(jié)果如下:
$ gradlew intro
:hello
[ant:echo] Hello, Ant
Hello, Ant. It's me, Gradle
:hi
Hi!
如果需要,你甚至可以創(chuàng)建依賴Gradle任務(wù)的Ant任務(wù)赞赖。你需要在build.xml
文件中為該任務(wù)添加depends
屬性:
<target name="hi" depends="intro">
<echo>Hi</echo>
</target>
如果你有一個(gè)很大的Ant構(gòu)建文件滚朵,并且你想保證任務(wù)名稱不重復(fù),你可以在引入Ant任務(wù)時(shí)進(jìn)行重命名:
ant.importBuild('build.xml') { antTargetName ->
'ant-' + antTargetName
}
如果你決定重命名所有的Ant任務(wù)前域,你需要時(shí)刻謹(jǐn)記如果你有Ant任務(wù)依賴于一個(gè)Gradle任務(wù)辕近,那么這個(gè)Gradle任務(wù)也需要添加前綴。否則匿垄,Gradle會(huì)找不到它并拋出UnknownTaskException
移宅。
屬性
Gradle和Ant不僅可以共享任務(wù),你還可以在Gradle中定義屬性椿疗,然后在Ant構(gòu)建文件中使用它們吞杭。下面的Ant target打印一個(gè)version
的屬性:
<target name="appVersion">
<echo>${version}</echo>
</target>
你可以在Gradle的構(gòu)建配置中定義version屬性,屬性前添加ant.
前綴变丧,就像任務(wù)一樣。這是定義Ant屬性最簡短的方式:
ant.version = '1.0'
Groovy隱藏了很多實(shí)現(xiàn)細(xì)節(jié)绢掰。屬性定義的全寫如下:
ant.properties['version'] = '1.0'
運(yùn)行version任務(wù)痒蓬,輸出如下:
$ gradlew appVersion
:appVersion
[ant:echo] 1.0
Gradle的深度Ant集成使你很容易的將基于Ant的構(gòu)建移植到Gradle中。
高級應(yīng)用部署
在第四章滴劲,我們講解了幾種方式來創(chuàng)建同一個(gè)應(yīng)用的不同版本攻晒,即使用構(gòu)建類型和product flavors。而某些情況下班挖,使用一些特殊技巧將更加方便鲁捏,比如APK分割。
分割A(yù)PK
構(gòu)建變體可以看做單獨(dú)的實(shí)體萧芙,它有自己的代碼给梅、資源和清單文件。另一方面双揪,APK分割只影響應(yīng)用的打包动羽。編譯、壓縮和混淆等仍是共享的渔期。這個(gè)機(jī)制允許你基于密度或者application binary interface(ABI)來分割A(yù)PK运吓。
你可以使用android
塊中的splits
塊來配置分割渴邦。配置密度分割,可以在splits
塊中創(chuàng)建density
塊拘哨。同理abi
塊用于設(shè)置ABI分割谋梭。
如果你打開密度分割,Gradle會(huì)為每個(gè)密度創(chuàng)建單獨(dú)的APK倦青。你可以手動(dòng)去除不需要的密度瓮床,以加速構(gòu)建過程。如下代碼展示了如何打開密度分割姨夹,并去掉較低的密度:
android {
splits {
density {
enable true
exclude 'ldpi', 'mdpi'
compatibleScreens 'normal', 'large', 'xlarge'
}
}
}
如果你只支持幾個(gè)密度纤垂,可以使用include
來定義白名單。使用include
磷账,你首先需要reset()
方法來將密度白名單列表置空峭沦。
compatibleScreens
屬性是可選的,它會(huì)在manifest文件中注入匹配的節(jié)點(diǎn)逃糟。上例中配置應(yīng)用支持normal及以上的屏幕吼鱼,不支持小屏。
基于ABI分割A(yù)PK也是同樣的方式绰咽,所有的屬性和密度分割一致菇肃,除了compatibleScreens
。
執(zhí)行配置了密度分割的構(gòu)建取募,Gradle會(huì)創(chuàng)建一個(gè)universal APK和幾個(gè)特定密度的APK琐谤。也就是說你會(huì)得到幾個(gè)APK文件:
app-hdpi-release.apk
app-universal-release.apk
app-xhdpi-release.apk
app-xxhdpi-release.apk
app-xxxhdpi-release.apk
使用APK分割有一個(gè)警告。如果你想向Google Play推幾個(gè)APK玩敏,你需要確保每個(gè)APK有不同的版本號(hào)斗忌。也就是說每個(gè)分割有一個(gè)單獨(dú)的版本號(hào)。幸運(yùn)的是旺聚,現(xiàn)在你可以使用applicationVariants
屬性织阳。
下面的片段來自Gradle Android插件的文檔,展示了如何為每個(gè)APK生成不同的版本號(hào):
ext.versionCodes = ['armeabi-v7a':1, mips:2, x86:3]
import com.android.build.OutputFile
android.applicationVariants.all { variant ->
// assign different version code for each output
variant.outputs.each { output ->
output.versionCodeOverride = project.ext.versionCodes.get(output.getFilter(OutputFile.ABI)) * 1000000 + android.defaultConfig.versionCode
}
}
這個(gè)小片段會(huì)檢查每個(gè)構(gòu)建變體使用的ABI砰粹,然后將一個(gè)乘數(shù)應(yīng)用到版本代碼唧躲,以確保每個(gè)變體都有一個(gè)獨(dú)特的版本號(hào)。