Android專案中如何用好構建神器Gradle?
原文地址:http://www.csdn.net/article/2015-08-10/2825420/2
Android Gradle實戰
下面講講在Android Gradle實戰中遇到的一些問題和經驗,感覺還是蠻多幹貨的。
productFlavors
這個東西基本上已經爛大街了,gradle的專案一般都會使用Product Flavor,看完美團的文章,你應該就懂了。
美團Android自動化之旅—適配渠道包
buildTypes
很多App有內測版和正式版,怎麼讓他們同時安裝在一個手機上?同時安裝在一個手機上,要求packageName不同的,用productFlavors可以解決,但可能不夠優雅,alpha版本還要來個debug和release版本豈不是很蛋疼?可以用buildTypes來解決,淘寶資深架構師朱鴻的文章有比較詳細的講解,但有些內容可能有些過時了,需要更改指令碼。
依賴更新
專案依賴的遠端包如果有更新,會有提醒或者自動更新嗎? 不會的,需要你手動設定changing標記為true,這樣gradle會每24小時檢查更新,通過更改resolutionStrategy可以修改檢查週期。
configurations.all {
// check for updates every build
resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
}
dependencies {
compile group: "group", name: "projectA", version: "1.1-SNAPSHOT", changing: true
}
之前上傳aar同一版本到maven倉庫,但依賴卻沒有更新,該怎麼辦呢?可以直接刪除本地快取,快取在~/.gradle/caches
目錄下,刪除快取後,下次執行就會自動重新下載遠端依賴了。
上傳aar到Maven倉庫
在工程的build.gradle中新增如下指令碼:
apply plugin: 'maven'
uploadArchives {
repositories {
mavenDeployer {
pom.groupId = GROUP_ID
pom.artifactId = ARTIFACT_ID
pom.version = VERSION
repository(url: RELEASE_REPOSITORY_URL) {
authentication(userName: USERNAME, password: PASSWORD)
}
}
}
}
在build.gradle同目錄下新增gradle.properties檔案,配置如下:
GROUP_ID=dianping.android.nova.thirdparty
ARTIFACT_ID=zxing
VERSION=1.0
RELEASE_REPOSITORY_URL=http://mvn.dp.com/nova
USERNAME=hello
PASSWORD=hello
gradle.properties的屬性會被build.gradle讀取用來上傳aar,最後執行./gradlew :Zxing:uploadArchives
即可。
更多配置,可參考建立企業內部maven伺服器並使用Android Studio釋出公共專案。
取消任務
專案構建過程中那麼多工,有些test相關的任務可能根本不需要,可以直接關掉,在build.gradle中加入如下指令碼:
tasks.whenTaskAdded { task ->
if (task.name.contains('AndroidTest')) {
task.enabled = false
}
}
tasks會獲取當前project中所有的task,enabled屬性控制任務開關,whenTaskAdded後面的閉包會在gradle配置階段完成。
加入任務
任務可以取消了,但還不盡興啊,想加入任務怎麼搞?前面講了dependsOn的方法,那就拿過來用啊,但是原有任務的依賴關係你又不是很清楚,甚至任務名稱都不知道,怎麼搞?
比如我想在執行dex打包之前,加入一個hello任務,可以這麼寫:
afterEvaluate {
android.applicationVariants.each { variant ->
def dx = tasks.findByName("dex${variant.name.capitalize()}")
def hello = "hello${variant.name.capitalize()}"
task(hello) << {
println "hello"
}
tasks.findByName(hello).dependsOn dx.taskDependencies.getDependencies(dx)
dx.dependsOn tasks.findByName(hello)
}
}
afterEvaluate是什麼鳥?你可以理解為在配置階段要結束,專案評估完會走到這一步。
variant呢?variant = productFlavors+ buildTypes,所以dex打包的任務可能就是dexCommonDebug。
你怎麼知道dex任務的具體名稱?Android Studio中的Gradle Console在執行gradle任務的時候會有輸出,可以仔細觀察一下。
hello任務定義的這麼複雜幹啥?我直接就叫hello不行嗎?不行,each就是遍歷variants,如果每個都叫hello,多個variant都一樣,豈不是傻傻分不清楚,加上variant的name做字尾,才有任務的區分。
關鍵來了,dx.taskDependencies.getDependencies(dx)會獲取dx任務的所有依賴,讓hello任務依賴dx任務的所有依賴,再讓dx任務依賴hello任務,這樣就可以加入某個任務到構建流程了,是不是感覺非常靈活。
我突然想到,用doFirst的方式加入一個action到dx任務中,應該也可以達到上面效果。
gradle加速
gradle加速可以看看這位朋友寫的加速Android Studio/Gradle構建,我就不多嘴了。並行編譯,常駐記憶體,還有離線模式這些思路對gradle的加速感覺還是比較有限。
想要更快,可以嘗試下Facebook出品的Buck,可以看一下Vine團隊適配Buck的技術文章,我們的架構師也有適配Buck,加速效果在10倍左右,但有兩個缺點,不支援Windows系統,不支援遠端依賴。
任務監聽
你想知道每個執行任務的執行時間嗎?你想知道每個執行任務都是幹嘛的嗎?把下面這段指令碼加入build.gradle中即可:
class TimingsListener implements TaskExecutionListener, BuildListener {
private Clock clock
private timings = []
@Override
void beforeExecute(Task task) {
clock = new org.gradle.util.Clock()
}
@Override
void afterExecute(Task task, TaskState taskState) {
def ms = clock.timeInMs
timings.add([ms, task.path])
task.project.logger.warn "${task.path} took ${ms}ms"
}
@Override
void buildFinished(BuildResult result) {
println "Task timings:"
for (timing in timings) {
if (timing[0] >= 50) {
printf "%7sms %s\n", timing
}
}
}
@Override
void buildStarted(Gradle gradle) {}
@Override
void projectsEvaluated(Gradle gradle) {}
@Override
void projectsLoaded(Gradle gradle) {}
@Override
void settingsEvaluated(Settings settings) {}
}
gradle.addListener new TimingsListener()
上面是對每個任務計時的一個例子,想要了解每個任務的作用,你可以修改上面的指令碼,列印出每個任務的inputs和outputs。比如assembleDebug那麼多依賴任務,每個都是幹什麼的,一會compile,一會generate,有什麼區別?看到每個task的輸入輸出,就可以大體看出它的作用。如果對assemble的每個任務監聽,你會發現改一行程式碼build的時間主要花費在了dex上,buck牛逼的地方就是對這個地方進行了優化,大大減少了增量編譯執行的時間。
buildscript方法
Android專案中,根工程預設的build.gradle應該是這樣的:
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:1.2.3'
}
}
allprojects {
repositories {
jcenter()
}
}
一會一個jcenter()這是在幹什麼?buildscript方法的作用是配置指令碼的依賴,而我們平常用的compile是配置project的依賴。repositories的意思就是需要包的時候到哥這裡來找,然後你以為com.android.tools.build:gradle:1.2.3
會從jcenter那裡下載了是吧,圖樣圖森破,不信加入下面這段指令碼看看輸出:
buildscript {
repositories {
jcenter()
}
repositories.each {
println it.getUrl()
}
dependencies {
classpath 'com.android.tools.build:gradle:1.2.3'
}
}
結果是這樣的:
file:/Applications/Android%20Studio.app/Contents/gradle/m2repository/ https://jcenter.bintray.com/
我靠,倉庫竟然直接在Android Studio應用內部,所以說你去掉buildscript的jcenter()完全沒有關係啊,下面還有更爽的,我們知道有依賴傳遞,上面classpath 中的gradle
依賴gradle-core
,gradle-core
依賴lint
,lint
依賴lint-checks
,lint-checks
最後依賴到了asm
,並且這個根目錄中的依賴配置會傳到所有工程的配置檔案,所以如果你要引用asm相關的類,不用設定classpath,直接import就可以了。你怎麼知道前面的依賴關係的?看上面m2repository目錄中對應的pom檔案就可以了。
為什麼講到ASM呢?ASM又是個比較刁的東西,可以直接用來操縱Java位元組碼,達到動態更改class檔案的效果。可以用ASM面向切面程式設計,達到解耦效果。Android DEX自動拆包及動態載入簡介中提到的class依賴分析和R常量替換的指令碼都可以用ASM來搞。
引入指令碼
指令碼寫多了,都擠在一個build.gradle裡也不好,人長大了總要自己出去住,那可以把部分指令碼抽出去嗎?當然可以,新建一個other.gradle把指令碼抽離,然後在build.gradle中新增apply from 'other.gradle'
即可,抽出去以後你會發現本來可以直接import的asm包找不到了,怎麼回事?根工程中配置的buildscript會傳遞到所有工程,但只會傳到build.gradle指令碼中,其他指令碼可不管,所以你要在other.gradle中重新配置buildscript,並且other.gradle中的repositories不再包含m2repository目錄,自己配置jcenter()又會導致依賴重新下載到~/.gradle/caches
目錄。如果不想額外下載,也可以在other.gradle中這麼搞:
buildscript {
repositories {
maven {
url rootProject.buildscript.repositories[0].getUrl()
}
}
dependencies {
classpath 'com.android.tools.build:gradle:1.2.3'
}
}
獲取AndroidManifest檔案
ApplicationId versus PackageName提到,gradle中的applicationid用來區分應用,manifest中packageName用來指定R檔案包名,並且各個productFlavor 的manifest中的packageName應該一致。applicationid只是gradle指令碼中的定義,其實最後生成的apk中的manifest檔案的packageName還是會被applicationid替換掉。
那獲取R檔案的包名怎麼搞?要獲取AndroidManifest中package屬性,並且這個manifest要是起始的檔案,因為最終檔案中的package屬性會被applicationid沖掉,由於各個manifest中的package屬性一樣,並且非主manifest可以沒有package屬性,所以只有獲取主manifest的package屬性才是最準確的。
def manifestFile = android.sourceSets.main.manifest.srcFile
def packageName = new XmlParser().parse(manifestFile).attribute('package')
無用資源
無用的資源就不要打包進APK了。
Resource Shrinking
一個Bug
之前在創業公司,用 Travis做持續繼承,遇到一個讓我很糾結的問題。在Travis上執行構建指令碼如下:
./gradlew clean
./gradlew assembleXR
最後生成的APK在執行的時候報錯,提示找不到某個.so檔案,解壓發現APK中果然缺少某個庫工程的.so檔案,但在本地執行的時候卻是沒有問題,糾結了好久,後來研究發現Android Studio中執行Clean Project的時候,會執行generateSources的任務,把它加入構建指令碼後才打包正確。最近發現,這原來是個Bug,並且已經在android gradle1.3被修復了。
匆匆忙忙間,寫了很多東西。讀完此文,希望你能感受到構建神器的魅力,感受到它的靈活強大,當然也希望能讓你使用Gradle更加得心應手。
相關文章
- Android專案中如何運用好Gradle?AndroidGradle
- 在gradle中構建java專案GradleJava
- Gradle之多專案構建Gradle
- 使用Gradle構建Java專案GradleJava
- Gradle構建多模組專案Gradle
- 深入淺出Android Gradle構建系統(二:專案結構)AndroidGradle
- 深入淺出Android Gradle構建系統(2):專案結構AndroidGradle
- Gradle構建SpringBoot專案GradleSpring Boot
- Gradle構建多模組專案(轉)Gradle
- Gradle學習系列—-多專案構建Gradle
- 使用gradle構建springboot專案GradleSpring Boot
- Gradle快速構建Spring Boot專案GradleSpring Boot
- 使用Gradle構建Spring boot專案GradleSpring Boot
- Gradle學習系列----多專案構建Gradle
- 【Java】【Gradle】Gradle構建SpringBoot專案,Gradle模組化管理JavaGradleSpring Boot
- 06、使用Gradle構建的專案如何打jar包和war包GradleJAR
- Gradle for Android ( 構建變體 )GradleAndroid
- [Android、Java]加快gradle構建AndroidJavaGradle
- Gradle入門及SpringBoot專案構建GradleSpring Boot
- 使用Gradle構建多模組SpringBoot專案GradleSpring Boot
- Gradle入門系列(5):建立多專案構建Gradle
- gradle中的增量構建Gradle
- springboot gradle demo (使用 Gradle 構建的 Spring Boot專案)Spring BootGradle
- Gradle自動化專案構建之Gradle學習及實戰Gradle
- Android中的Gradle之配置及構建優化AndroidGradle優化
- 【Java】【專案構建】Idea中設定Gradle/Maven多模組依賴JavaIdeaGradleMaven
- IDEA使用Gradle構建SpringBoot專案工程IdeaGradleSpring Boot
- Gradle自動化專案構建之快速掌握GroovyGradle
- AndroidStudio配置settings.gradle在工程中構建多個專案AndroidGradle
- 專案構建工具 GradleGradle
- 用Gradle 構建你的android程式GradleAndroid
- 如何構建「大型 Node.js 專案」的專案結構?Node.js
- 如何使用Docker構建前端專案Docker前端
- 如何構建大型的前端專案前端
- 如何使用Webpack工具構建專案Web
- Gradle 與 AGP 構建 API: 配置您的構建檔案GradleAPI
- Gradle中的差異化構建Gradle
- 構建第一個基於 Gradle 的 Spring Boot 專案GradleSpring Boot