今天,我會經過這篇文章一步一步的編寫gradle文件,從項目的建立,到gradle的配置。相信有了這篇文章,你將對gradle的內部運行將有一個全新的認識。html
在講gradle以前,咱們還需明白一點,gradle語法是基於groovy的。因此咱們先來了解一些groovy的知識,這有助於咱們以後的理解。固然若是你已經有groovy的基礎你能夠直接跳過,沒有的也不用慌,由於只要你懂java就不是什麼難題。java
下面我將經過code的形式,列出幾點react
def printAge(String name, int age) {
print("$name is $age years old")
}
def printEmptyLine() {
println()
}
def callClosure(Closure closure) {
closure()
}
printAge "John", 24 //輸出John is 24 years old
printEmptyLine() //輸出空行
callClosure { println("From closure") } //輸出From closure
複製代碼
def callWithParam(String param, Closure<String> closure) {
closure(param)
}
callWithParam("param", { println it }) //輸出param
callWithParam("param") { println it } //輸出param
callWithParam "param", { println it } //輸出param
複製代碼
def printPersonInfo(Map<String, Object> person) {
println("${person.name} is ${person.age} years old")
}
def printJobInfo(Map<String, Object> job, String employeeName) {
println("${employeeName} works as ${job.name} at ${job.company}")
}
printPersonInfo name: "Jake", age: 29
printJobInfo "Payne", name: "Android Engineer", company: "Google"
複製代碼
你會發現他們的調用都不須要括號,同時printJobInfo的調用參數的順序不受影響。android
在gradle中你會發現許多閉包,因此咱們須要對閉包有必定的瞭解。若是你熟悉kotlin,它與Function literals with receiver相似。git
在groovy中咱們能夠將Closures當作成lambdas,因此它能夠直接當作代碼塊執行,能夠有參數,也能夠有返回值。可是不一樣的是它能夠改變其自身的代理。例如:github
class DelegateOne {
def callContent(String content) {
println "From delegateOne: $content"
}
}
class DelegateTow {
def callContent(String content) {
println "From delegateTwo: $content"
}
}
def callClosure = {
callContent "I am bird"
}
callClosure.delegate = new DelegateOne()
callClosure() //輸出From delegateOne: I am bird
callClosure.delegate = new DelegateTow()
callClosure() //輸出From delegateTow: I am bird
複製代碼
經過改變callClosure的delegate,讓其調用不一樣的callContent。 若是你想了解更多,能夠直接閱讀groovy文檔api
在上篇文章中已經提到有關gradle的腳步相關的知識,這裏就再也不累贅。 下面咱們來一步一步構建gradle。bash
首先咱們新建一個文件夾example,cd進入該文件夾,在該目錄下執行gradle projects,你會發現它已是一個gradle項目了微信
$ gradle projects
> Task :projects
------------------------------------------------------------
Root project
------------------------------------------------------------
Root project 'example'
No sub-projects
To see a list of the tasks of a project, run gradle <project-path>:tasks
For example, try running gradle :tasks
BUILD SUCCESSFUL in 5s
複製代碼
由於這裏不是在Android Studio中建立的項目,因此若是你本地沒有安裝與配置gradle環境,將不會有gradle命令。因此這一點要注意一下。閉包
每個android項目在它的root project下都須要配置一個settings.gradle,它表明着項目的全局配置。同時使用void include(String[] projectPaths)方法來添加子項目,例如咱們爲example添加app子項目
$ echo "include ':app'" > settings.gradle
$ gradle projects
> Task :projects
------------------------------------------------------------
Root project
------------------------------------------------------------
Root project 'example'
\--- Project ':app'
To see a list of the tasks of a project, run gradle <project-path>:tasks
For example, try running gradle :app:tasks
BUILD SUCCESSFUL in 1s
1 actionable task: 1 executed
複製代碼
:app中的:表明的是路徑的分隔符,同時在settings.gradle中默認root project是該文件的文件夾名稱,也能夠經過rootProject.name = name來進行修改。
如今須要作的是將子項目app構建成Android項目,因此咱們須要配置app的build.gradle。由於gradle只是構建工具,它是根據不一樣的插件來構建不一樣的項目,因此爲了符合Android的構建,須要申明應用的插件。這裏經過apply方法,它有如下三種類型
void apply(Closure closure)
void apply(Map<String, ?> options)
void apply(Action<? super ObjectConfigurationAction> action)
複製代碼
這裏咱們使用的是第二種,它的map參數須要與ObjectConfigurationAction中的方法名相匹配,而它的方法名有如下三種
由於咱們要使用android插件,因此須要使用apply(plugin: 'com.android.application'),又因爲groovy的語法特性,能夠將括號省略,因此最終在build.gradle中的表現能夠以下:
$ echo "apply plugin: 'com.android.application'" > app/build.gradle
複製代碼
添加完之後,再來執行一下
$ gradle app:tasks
FAILURE: Build failed with an exception.
* Where:
Build file '/Users/idisfkj/example/app/build.gradle' line: 1
* What went wrong:
A problem occurred evaluating project ':app'.
> Plugin with id 'com.android.application' not found.
* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights.
* Get more help at https://help.gradle.org
BUILD FAILED in 6s
複製代碼
發現報錯了,顯示com.android.application的插件id找不到。這正常,由於咱們尚未聲明它。因此下面咱們要在project下的build.gradle中聲明它。爲何不直接到app下的build.gradle聲明呢?是由於咱們是android項目,project能夠有多個sub-project,因此爲了防止在子項目中重複聲明,統一到主項目中聲明。
project的build.gradle聲明插件須要在buildscript中,而buildscript會經過ScriptHandler來執行,以致於sub-project也可以使用。因此最終的申明以下:
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.3.2'
}
}
複製代碼
上面的buildscript、repositories與dependencies方法都是以Closure做爲參數,而後再經過delegate進行調用
相應的google()與jcenter()會在RepositoryHandler執行,classpaht(String)會在DependencyHandler(*)執行。
若是你想更詳細的瞭解能夠查看文檔
讓咱們再一次執行gradle projects
$ gradle projects
FAILURE: Build failed with an exception.
* What went wrong:
A problem occurred configuring project ':app'.
> compileSdkVersion is not specified.
* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights.
* Get more help at https://help.gradle.org
BUILD FAILED in 1s
複製代碼
發現報沒有指定compileSdkVersion,由於咱們尚未對app進行相關的配置,只是引用了android插件。因此咱們如今來進行基本配置,在app/build.gradle中添加
android {
buildToolsVersion "28.0.1"
compileSdkVersion 28
}
複製代碼
咱們在android中進行聲明,android方法會加入到project實例中。buildToolsVersion與compileSdkVersion將經過Closure對象進行delegate。
android方法會是如何與project進行關聯的?在咱們聲明的Android插件中,會註冊一個AppExtension類,這個extension將會與android命名。因此gradle可以調用android方法,而在AppExtension中已經聲明瞭各類方法屬性,例如buildTypes、defaultConfig與signingConfigs等。這也就是爲何咱們可以在android方法中調用它們的緣由。下面是extension的建立部分源碼
@Override
void apply(Project project) {
super.apply(project)
// This is for testing.
if (pluginHolder != null) {
pluginHolder.plugin = this;
}
def buildTypeContainer = project.container(DefaultBuildType,
new BuildTypeFactory(instantiator, project.fileResolver))
def productFlavorContainer = project.container(GroupableProductFlavorDsl,
new GroupableProductFlavorFactory(instantiator, project.fileResolver))
def signingConfigContainer = project.container(SigningConfig,
new SigningConfigFactory(instantiator))
extension = project.extensions.create('android', AppExtension,
this, (ProjectInternal) project, instantiator,
buildTypeContainer, productFlavorContainer, signingConfigContainer)
setBaseExtension(extension)
...
}
複製代碼
android方法下面就是dependencies,下面咱們再來看dependencies
dependencies {
implementation 'io.reactivex.rxjava2:rxjava:2.0.4'
testImplementation 'junit:junit:4.12'
annotationProcessor 'org.parceler:parceler:1.1.6'
}
複製代碼
有了上面的基礎,應該會容易理解。dependencies是會被delegate給DependencyHandler,不過若是你到DependencyHandler中去查找,會發現找不到上面的implementation、testImplementation等方法。那它們有究竟是怎麼來的呢?亦或者若是咱們添加了dev flavor,那麼我又可使用devImplementation。這裏就涉及到了groovy的methodMissing方法。它可以在runtime(*)中捕獲到沒有定義的方法。
至於(*)是gradle的methodMissing中的一個抽象感念,它申明在MethodMixIn中。
對於DependencyHandler的實現規則是: 在DependencyHandler中若是咱們回調了一個沒有定義的方法,且它有相應的參數;同時它的方法名在configuration(*)中;那麼將會根據方法名與參數類型來調用doAdd的相應方法。
對於configuration(*),每個plugin都有他們本身的配置,例如java插件定義了compile、compileClassPath、testCompile等。而對於Android插件在這基礎上還會定義annotationProcessor,(variant)Implementation、(variant)TestImplementation等。對於variant則是基於你設置的buildTypes與flavors。
另外一方面,因爲doAdd()是私用的方法,但add()是公用的方法,因此在dependencies中咱們能夠直接使用add
dependencies {
add('implementation', 'io.reactivex.rxjava2:rxjava:2.0.4')
add('testImplementation', 'junit:junit:4.12')
add('annotationProcessor', 'org.parceler:parceler:1.1.6')
}
複製代碼
注意,這種寫法並不推薦,這裏只是爲了更好的理解它的原理。
gradle的知識點還有不少,這只是對有關Android的一部分進行分析。當咱們進行gradle配置的時,不至於對gradle的語法感到魔幻,或者對它的一些操做感到不解。
我在github上建了一個倉庫Android精華錄,收集Android相關的文章,若是有須要的能夠去看一下,有好的文章能夠加我微信fan331100推薦給我。