從Android團隊開始宣佈放棄Eclipse轉投Android Studio時,構建工具Gradle進入了Android開發者的視野。而隨着熱修復、插件化、編譯時註解的流行,深刻了解Gradle就變得頗有必要了。那麼什麼是Gradle ?html
Gradle是一個基於Ant構建工具,用Groovy DSL描述依賴關係的jar包。咱們都知道早期的Android開發使用的是Eclipse,而Eclipse的構建工具使用的是Ant,用XML描述依賴關係,而XML存在太多的弊端,不如動態語言。因此動態語言Groovy代替了XML,最後集成爲Gradle。而Groovy的誕生正是因爲Java在後端某些地方不足,對於配置信息處理比較差,因此Apache開發了這門語言而且開源了代碼。各家公司也對其進行了大量使用,其中LinkedIn公司開源了許多的Gradle插件,有興趣的能夠下載源碼看看。Gradle的使用場景也不少,單元測試,自動化集成,依賴庫管理等。既然說到了Java在後端的應用,必然要說道Android端的Java,與之搭配的就是最近很火的Kotlin,Kotlin也是一門動態語言,並且Kotlin和Groovy同樣也能夠寫build.gradle文件,它們都是基於JVM的動態語言,均可以使用DSL去描述項目依賴關係。講到這裏我不由佩服JVM生態,除了Kotlin、Groovy,還有Scala、 Clojure等,經過這些不一樣的語言能夠去寫不一樣層級的代碼,而最後都是字節碼。前端
咱們會介紹DSL、Gradle相關知識。java
首先Groovy語言的基本知識咱們不進行探討,網上與之相關的資料有不少。咱們來說講它的DSL,由於Gradle提供的build.gradle配置文件就是用DSL來寫的。那麼什麼是DSL?維基百科裏面描述的很清楚,可是具體到代碼有哪些呢?就像Android裏面的AIDL(Java DSL)、HIDL,前端的JQUERY(JavaScript DSL)。因爲DSL是一種爲解決某種問題的領域指定語言,而不像Java這種通用性計算機語言有語法解析器,因此Android團隊寫了解析AIDL的語法解析器,Gradle團隊寫了解析Groovy DSL的語法解析器。若是想要開發針對本身公司業務的DSL,那麼能夠自行到網上查找相關的學習資料。不過對於中小公司都是使用成熟的DSL框架,而不是重零開始,咱們只要學會使用某個DSL框架就能夠了,就好比Gradle框架,只要理解框架中插件的建立,任務的定義就能夠了。react
說了這麼多不如來個代碼感覺一下。android
apply plugin: 'com.android.application'
android {
compileSdkVersion 23
buildToolsVersion "27.0.0"
defaultConfig {
applicationId "com.hawksjamesf.myapplication"
minSdkVersion 14
targetSdkVersion 26
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation 'com.android.support:appcompat-v7:23.0.0'
implementation 'com.android.support.constraint:constraint-layout:1.0.2'
// testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.1'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
}
若是你是第一次接觸Gradle的話,你必定表示看不懂這門語言,可是若是把它當作配置文件,是否是就能理解了。Gradle使用了大量的閉包和lamda這樣簡潔的語法來表示配置信息,並且Gradle中不少地方作了省略。好比去掉分號,去掉方法括號等等,力求作到精簡。
若是你想要配置一些簡單的屬性,能夠經過API查看,好比添加debug的配置。git
buildTypes {
debug{
println 'haha'
}
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
通常DSL造成的框架都要有足夠完整的API,由於其專業性太強了,就是所謂的行話,通常人看不懂,要經過查百科才能明白。
若是你想要根據公司的業務添加一些代碼的話,那麼就須要咱們寫任務或者插件,而這須要咱們熟悉Gradle框架和Groovy語言了。程序員
咱們都知道Gradle的生命流程要經歷三個部分:初始化、配置、執行。github
基於這個流程Android團隊提供了本身的插件給Android開發者寫Android項目,而且有豐富的DSL文檔Android Plugin DSL Reference。若是想要看看Gradle官方提供的插件能夠看看這個文檔Gradle Build Language Reference。web
爲了驗證流程,我在本身的項目SimpleWeather中添加以下log。sql
SimpleWeather/settings.gradle
println("settings start")
include ':app', ':location'
println("settings end")
//include ':viewpagerindicator_library'
SimpleWeather/build.gradle
// Top-level build file where you can add configuration options common to all sub-projects/modules.
println("root project start")
buildscript {
repositories {
jcenter()
google()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.0.1'
//
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
jcenter()
google()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
ext{
compileSdkVersion=26
buildToolsVersion ='27.0.0'
minSdkVersion =17
targetSdkVersion =26
versionCode=2
versionName="2.0"
}
println("root project end")
SimpleWeather/app/build.gradle
println("app start")
apply plugin: 'com.android.application'
android {
compileSdkVersion rootProject.ext.compileSdkVersion
buildToolsVersion rootProject.ext.buildToolsVersion
defaultConfig {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode rootProject.ext.versionCode
versionName rootProject.ext.versionName
multiDexEnabled = true
applicationId "com.hawksjamesf.simpleweather"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
ndk {
// 設置支持的SO庫架構
abiFilters 'x86_64'
abiFilters 'x86'
abiFilters 'armeabi-v7a'
abiFilters 'arm64-v8a'
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
buildConfigField("String", "BASE_URL", '"https://weatherapi.market.xiaomi.com"')
}
debug {
// buildConfigField ("String","BASE_URL",'"https://api.caiyunapp.com/"')
buildConfigField("String", "BASE_URL", '"https://weatherapi.market.xiaomi.com"')
}
}
lintOptions {
// abortOnError false
}
productFlavors {
}
}
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
// implementation project(':viewpagerindicator_library')
implementation 'com.jakewharton:butterknife:8.8.1'
annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'
implementation 'com.google.dagger:dagger:2.11'
annotationProcessor 'com.google.dagger:dagger-compiler:2.11'
implementation 'com.squareup.retrofit2:retrofit:2.3.0'
implementation 'com.squareup.retrofit2:retrofit-converters:2.3.0'
implementation 'com.squareup.retrofit2:converter-gson:2.3.0'
implementation 'com.squareup.retrofit2:adapter-rxjava2:2.+'
implementation 'com.squareup.okhttp3:okhttp:3.8.1'
implementation 'com.squareup.okhttp3:logging-interceptor:3.4.1'
// implementation 'com.jakewharton.timber:timber:4.5.1'
implementation 'com.orhanobut:logger:2.1.1'
implementation 'com.google.code.gson:gson:2.8.2'
implementation 'org.greenrobot:eventbus:3.0.0'
implementation 'io.reactivex.rxjava2:rxjava:2.+'
implementation 'com.tencent.bugly:crashreport:2.6.6.1'
implementation 'com.tencent.bugly:nativecrashreport:3.3.1'
// Test helpers for Room
testImplementation 'android.arch.persistence.room:testing:1.0.0'
// Room (use 1.1.0-alpha1 for latest alpha)
implementation 'android.arch.persistence.room:runtime:1.0.0'
annotationProcessor "android.arch.persistence.room:compiler:1.0.0"
// RxJava support for Room
implementation 'android.arch.persistence.room:rxjava2:1.+'
// implementation "com.android.support:multidex:1.0.1"
//noinspection GradleCompatible
implementation 'com.android.support:appcompat-v7:26.1.0'
implementation 'com.android.support:design:26.1.0'
implementation 'com.android.support:recyclerview-v7:26.1.0'
compile project(path: ':location')
}
println("app end")
SimpleWeather/location/build.gradle
println("lib location start")
apply plugin: 'com.android.library'
android {
compileSdkVersion rootProject.ext.compileSdkVersion
buildToolsVersion rootProject.ext.buildToolsVersion
defaultConfig {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode rootProject.ext.versionCode
versionName rootProject.ext.versionName
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:26.1.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.1'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
}
println("lib location end")
咱們能夠清醒的看到初始化階段和配置階段。
[0] % ./gradlew clean
Starting a Gradle Daemon, 1 incompatible Daemon could not be reused, use --status for details
settings start settings end > Configure project : root project start root project end > Configure project :app app start Configuration 'compile' in project ':app' is deprecated. Use 'implementation' instead. app end > Configure project :location lib location start lib location end
在前面咱們粗糙地講了task,這裏咱們在細講一下。在Android DSL中默認的任務有編譯、打包、簽名、安裝,它們按照順序被一一執行。而咱們也能夠寫一些hook它們流程的任務.
下面是定義task的三種方式
task(hello) {
println "config hello"
}
task('hello') {
println "config hello"
}
tasks.create(name: 'hello') {
println "config hello"
}
在Gradle中爲了使得配置文件看起來更加精簡,精簡版以後的代碼以下
task hello {
println "config hello"
}
tasks.create('hello') {
println "config hello"
}
精簡版是開發者最爲經常使用的。當咱們定義了任務內容,經過./gradle hello
就能夠執行task,可是咱們都知道Gradle的流程中會先配置task在執行task,而上面的代碼會在配置階段
就被調用。那麼問題來了,若是咱們想要代碼在執行階段
被調用要怎麼辦呢 ?
看代碼。
task hello {
println "config hello"
doLast {
println "excute hello"
}
}
hello.doLast {
println "excute hello"
}
hello.leftShift {
println "excute hello"
}
hello << {
println "excute hello"
}
<<
符號的出現就是爲了精簡代碼,因此和leftShift、doLast同樣沒什麼可說的。因此在執行階段執行代碼的方式也就兩種。
task hello {
println "config hello"
doLast {
println "excute hello"
}
}
hello << {
println "excute hello"
}
對於第一種能夠將配置階段的代碼和執行階段的代碼寫在一個閉包裏面,對於第二種只能寫執行階段的代碼,其中各類利弊相比已經很清晰了。
除了簡單的定義task,還能夠進階的定義task。
來看個代碼。
task clean(type: Delete) {
delete rootProject.buildDir
}
task copy(type: Copy) {
from 'resources'
into 'target'
include('**/*.txt', '**/*.xml', '**/*.properties')
}
Delete是Gradle提供的,咱們可讓本身的task擁有其特性,好比delete那個文件/文件夾。不是很明白的話,咱們能夠看第二個例子。想要複製一個文件可使用Copy,from
表示源文件,into
表示目標文件,include
表示要複製的文件。固然了這種書寫方式還有另一種,來看個代碼。
task myCopy(type: Copy)
myCopy {
from 'resources'
into 'target'
include('**/*.txt', '**/*.xml', '**/*.properties')
}
Gradle並無論給咱們提供了這兩個task type,還有不少具體查看Project API,頁面左側欄。固然了咱們還能夠寫一個類繼承Copy,而後重寫一些屬性、方法。
有的時候咱們須要增長任務的相關性,好比一個任務的執行須要另一個任務執行完才能執行。
用代碼說話
1.
project('projectA') {
task taskX(dependsOn: ':projectB:taskY') {
doLast {
println 'taskX'
}
}
}
project('projectB') {
task taskY {
doLast {
println 'taskY'
}
}
}
2.
task taskX {
doLast {
println 'taskX'
}
}
task taskY {
doLast {
println 'taskY'
}
}
taskX.dependsOn taskY
有兩種寫法,一種是寫在定義時,另一種是寫在調用時。
對於第二種還有它的變種版本。
task taskX {
doLast {
println 'taskX'
}
}
taskX.dependsOn {
tasks.findAll { task -> task.name.startsWith('lib') }
}
task lib1 {
doLast {
println 'lib1'
}
}
task lib2 {
doLast {
println 'lib2'
}
}
task notALib {
doLast {
println 'notALib'
}
}
除了dependsOn,你還可使用mustRunAfter、shouldRunAfter來進行排序。
Gradle插件分爲兩種:script plugins 和 binary plugins。script plugins經過apply from: 'other.gradle'
來引用;而binary plugins經過apply plugin: 'com.android.application'
引用。這裏咱們將會講解第二種,在講解第二種的過程當中可能會涉及到第二種。既然咱們已經知道如何使用插件,那麼接下來就要知道怎麼定義插件。
寫插件的三種方式:
buildSrc
project第一種咱們的代碼應該這麼寫
class GreetingPlugin implements Plugin<Project> {
void apply(Project project) {
project.task('hello') {
doLast {
println 'Hello from the GreetingPlugin'
}
}
}
}
// Apply the plugin
apply plugin: GreetingPlugin
若是代碼量較多咱們就不能寫在build.gradle文件,須要像編寫一個庫同樣來寫插件。而這個時候咱們就能夠建立一個模塊,也就是第二種。
項目的地址SimpleWeather*
這裏有幾點須要注意的
versionplugin
對應的就是apply plugin: versionplugin
的versionplugin,文件中經過「implementation-class=com.hawksjamesf.plugin.VersionPlugin」代表Plugin子類的位置。那麼對於第三種,想必我已經不用多說了吧,就是建立一個插件項目。
若是你想要研究Android團隊寫的Gradle插件源碼,能夠經過這裏Build Overview獲取到源碼。
最最後在說一句,因爲Kotlin的特性,咱們能夠用它來替代Groovy寫Gradle腳本,這樣就能夠減小學習Groovy的成本,並且Kotlin自從被google扶正以後,也受到了不少開發者的喜好,不少項目也在開始用它來作開發。不過千外不要說Java的地位又不行了,身爲Java程序員又在自我恐慌,戒驕戒躁,以其浪費時間在恐慌還不如多學幾門不一樣類型語言提高本身。
Groovy官網
使用 Groovy 構建 DSL
DSL編程技術的介紹
CREATING YOUR OWN DSL IN KOTLIN
Gradle的官網
Kotlin Meets Gradle
深刻理解Android之Gradle
全面理解Gradle - 定義Task