導(dǎo)語
Android Studio中使用了Gradle進(jìn)行build咽笼。我閱讀了groovy官方文檔箩张,Gradle官方文檔及一些源代碼,Android插件的官方文檔及一些源代碼,希望給大家介紹一下Gradle腳本的語法和處理流程。簡單Groovy是一種運(yùn)行在JVM上的語言猿规, Gradle是使用Groovy實(shí)現(xiàn)的, Android插件提供了android特定的功能宙橱。
1. Gradle腳本的build流程
1.1Groovy和腳本
Groovy會(huì)把腳本編譯成groovy.lang.Script的子類坎拐。groovy.lang.Script是一個(gè)抽象類,它有一個(gè)抽象方法run(). 如果有一個(gè)腳本的文件名是Main养匈,它的內(nèi)容是:
println 'Hello from Groovy'
它編譯后生成的類是:
class Main extends groovy.lang.Script {
def run() {
println 'Hello from Groovy'
}
static void main(String[] args) {
InvokerHelper.runScript(Main, args)
}
腳本中的語句會(huì)成為run方法的實(shí)現(xiàn)哼勇。
Gradle腳本編譯生成的類當(dāng)然也繼承自groovy.lang.Script,并同時(shí)實(shí)現(xiàn)了Gradle自己的script接口org.gradle.api.Script呕乎。
1.2 Gradle腳本對(duì)象的代理對(duì)象
每一個(gè)Gradle腳本對(duì)象都有一個(gè)代理對(duì)象积担。Settings腳本的代理對(duì)象是Setting對(duì)象,Build腳本的代理對(duì)象是Project對(duì)象猬仁。每一個(gè)在腳本對(duì)象未找到的屬性和方法都會(huì)轉(zhuǎn)到代理對(duì)象帝璧。
Groovy和Java的一個(gè)不同之處就是Groovy可以動(dòng)態(tài)的添加方法和屬性。動(dòng)態(tài)添加的方式之一是覆蓋propertyMissing和methodMissing方法湿刽,Gradle就是采用這種方式實(shí)現(xiàn)了腳本對(duì)象的代理對(duì)象的烁。下面是Gradle腳本對(duì)象的基類BasicScript的實(shí)現(xiàn)代碼片段:
public abstract class BasicScript extends org.gradle.groovy.scripts.Script implements org.gradle.api.Script, FileOperations, ProcessOperations {
......
private Object target;
private DynamicObject dynamicTarget;
......
public Object propertyMissing(String property) {
if ("out".equals(property)) {
return System.out;
} else {
return dynamicTarget.getProperty(property);
}
}
......
public Object methodMissing(String name, Object params) {
return dynamicTarget.invokeMethod(name, (Object[])params);
}
}
1.3 Gradle腳本的build流程
Gradle腳本的build流程分為3個(gè)階段:
(1)初始化階段
Gradle支持單個(gè)和多個(gè)工程的編譯。在初始化階段诈闺,Gradle判斷需要參與編譯的工程渴庆,為每個(gè)工程創(chuàng)建一個(gè)Project對(duì)象,并建立工程之間的層次關(guān)系雅镊。這個(gè)階段執(zhí)行Settings腳本襟雷。
(2)配置階段
Gradle對(duì)上一步創(chuàng)建的Project對(duì)象進(jìn)行配置。這個(gè)階段執(zhí)行Build腳本
(3)執(zhí)行階段
Gradle執(zhí)行選中的task仁烹。
1.4一個(gè)demo
下面是demo工程的文件層次耸弄,這個(gè)demo會(huì)被后面的部分使用。這個(gè)例子包含一個(gè)app子工程和一個(gè)library子工程卓缰。settings.gradle是Setttings腳本计呈,三個(gè)build.gradle都是Build腳本砰诵。
--settings.gradle
--build.gradle
--app
--build.gradle
--mylibrary
--build.gradle
2. Settings腳本
2.1 Settings腳本的內(nèi)容
Settings腳本通常比較簡單,用于初始化project樹捌显。demo中settings.gradle的內(nèi)容是:
include ':app', ':mylibrary'
2.2 groovy的相關(guān)語法
groovy允許省略語句結(jié)尾的分號(hào)茁彭,并允許在方法調(diào)用時(shí)省略括號(hào),上面的代碼等價(jià)于:
include(':app', ':mylibrary');
2.3 Gradle的相關(guān)語法
初始化腳本的Script對(duì)象會(huì)有一個(gè)Project代理對(duì)象苇瓣。在Script對(duì)象沒有定義的屬性和方法調(diào)用就會(huì)被轉(zhuǎn)到Project對(duì)象尉间。上面的語句實(shí)際上調(diào)用的是Project對(duì)象的include方法,該方法的原型如下:
void include(String[] projectPaths)
這個(gè)方法將給定的工程添加到build中击罪。工程路徑的格式是: 以一個(gè)可選的”:”的開始哲嘲,它表示”:”前面有一個(gè)不需要名字的根工程;剩下的部分是以”:”分隔的工程名媳禁。例如眠副, “:app”中”:”的是可選的,它表示”:”前面有一個(gè)不需要名字的根工程竣稽。
運(yùn)行”gradle projects”可以獲得這個(gè)demo的project樹:
Root project 'AndroidGradleDemo'
+--- Project ':app'
\--- Project ':mylibrary'
3. Build腳本
3.1 app工程的Build腳本
apply plugin: 'com.android.application'
android {
compileSdkVersion 23
......
buildTypes {
debug {
applicationIdSuffix ".debug"
}
release {
minifyEnabled false
//getDefaultProguardFile() will return the full path of 'proguard-android.txt'
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
.......
3.2 Groovy相關(guān)的語法
1)Map
Groovy可以直接使用Java中的各種集合類型囱怕,例如Map和List等等。在初始化語法上有一些擴(kuò)展
def key = 'name'
//創(chuàng)建一個(gè)Map毫别。注意"Guillaume"的key是字符串"key"而不是變量key的值
def person = [key: 'Guillaume']
assert !person.containsKey('name')
assert person.containsKey('key')
//創(chuàng)建一個(gè)Map娃弓。我們使用()包圍了key。這種情況下岛宦,"Guillaume"的key是變量key的值"name"
def person2 = [(key): 'Guillaume']
assert person2.containsKey('name')
assert !person2.containsKey('key')
(2)閉包
(2.1)Syntax
Groovy中的閉包是一個(gè)匿名的代碼塊台丛。它的語法規(guī)則是:
{ [closureParameters -> ] statements }
)[closureParameters→]是可選的以”,”分隔的參數(shù)列表
*)statements是0或多條Groovy語句
下面是閉包的一些例子:
{ item++ }
//A closure using an implicit parameter (it)
{ println it }
//In that case it is often better to use an explicit name for the parameter
{ it -> println it }
{ String x, int y ->
println "hey ${x} the value is ${y}"
}
{ reader ->
def line = reader.readLine()
line.trim()
}
(2.2)Owner, delegate and thisGroovy中的閉包有三個(gè)重要的概念: Owner, delegate and this
*)this是指定義閉包的類
*)Owner是指直接包含閉包的類或閉包
*)delegate是指用于解析閉包中屬性和方法調(diào)用的第三方對(duì)象
下面的代碼段說明了this和閉包的區(qū)別:
class Enclosing {
void run() {
def whatIsOwnerMethod = { getOwner() }
//calling the closure will return the instance of Enclosing where the the closure is defined
assert whatIsOwnerMethod() == this
def whatIsOwner = { owner }
assert whatIsOwner() == this
}
}
class NestedClosures {
void run() {
def nestedClosures = {
def cl = { owner }
cl()
}
//then owner corresponds to the enclosing closure, hence a different object from this!
assert nestedClosures() == nestedClosures
}
}
下面的代碼演示了delegate的作用。
class Person {
String name
}
def p = new Person(name:'Igor')
def cl = { name.toUpperCase() }
cl.delegate = p
//在設(shè)置了delegate后砾肺,閉包中的name屬性被解析成作為delegate的Person的屬性
assert cl() == 'IGOR'
Gradle中大量使用閉包的delegate來實(shí)現(xiàn)對(duì)各種對(duì)象的配置挽霉。
(3)轉(zhuǎn)化成Java語法后的代碼
Map<String, Object> map = new HashMap<String, Object>();
map.put("plugin", "com.android.appliaction");
apply(map);//這個(gè)方法會(huì)代理給Project對(duì)象
android({
//這個(gè)閉包的delegate是Android插件添加的extension
compileSdkVersion(23);
......
buildTypes({
//這個(gè)閉包的delegate是 NamedDomainObjectContainerConfigureDelegate.
debug({
applicationIdSuffix(".debug")
});
release({
minifyEnabled(false);
//getDefaultProguardFile() will return the full path of 'proguard- android.txt'
proguardFiles(getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro');
});
});
});
3.3 Gradle的相關(guān)語法
(1)Project查找方法的規(guī)則
Build腳本的代理對(duì)象是Project對(duì)象。Project對(duì)象從6個(gè)范圍中查找方法:
*)Project對(duì)象本身定義的方法
*)腳本文件中定義的方法
*)被插件添加的extension. extension的名字可以做為方法名
*)被插件添加的convension方法变汪。
*)工程中的task侠坎。task的名字可以作為方法名
*)父工程中的方法。
(2)apply plugin: ‘com.android.application’
“apply plugin: ‘com.android.application’”等價(jià)于調(diào)用Project對(duì)象的apply方法裙盾,該方法的原型是:
void apply(Map<String,?> options)
Project.apply方法用于應(yīng)用腳本和插件实胸。我們指定了鍵值對(duì)”plugin”:”com.android.application”, 因此這個(gè)調(diào)用會(huì)添加Android插件。Android插件會(huì)添加一個(gè)名字是”android”闷煤,類型是com.android.build.gradle.AppExtension的extension.
(3)android{…}
這個(gè)代碼塊等價(jià)于”android({…});”, 即調(diào)用了一個(gè)方法名是”android”的方法童芹,該方法的參數(shù)是一個(gè)閉包。當(dāng)調(diào)用Project對(duì)象的android方法時(shí)鲤拿,實(shí)際上是找到了名字是”android”的extension, 把這個(gè)extension設(shè)為閉包的代理并運(yùn)行這個(gè)閉包署咽。從而閉包中的方法調(diào)用實(shí)際上都是對(duì)com.android.build.gradle.AppExtension的調(diào)用近顷。這就是上面提到的Project查找方法的規(guī)則中的一條:被插件添加的extension的名字可以做為方法名生音。
*)查找extension的邏輯是在ExtensionsStoage.configureExtension中,代碼如下
public <T> T configureExtension(String methodName, Object ... arguments) {
Closure closure = (Closure) arguments[0];
ClosureBackedAction<T> action = new ClosureBackedAction<T>(closure);
//根據(jù)名字查找extension
ExtensionHolder<T> extensionHolder = extensions.get(methodName);
return extensionHolder.configure(action);//Line 69
}
*)設(shè)置閉包的delegate并運(yùn)行閉包的邏輯是在ClosureBackedAction.execute中, 代碼如下:
public void execute(T delegate) {
if (closure == null) {
return;
}
try {
if (configureableAware && delegate instanceof Configurable) {
((Configurable) delegate).configure(closure);
} else {
Closure copy = (Closure) closure.clone();
copy.setResolveStrategy(resolveStrategy);
//設(shè)置delegate
copy.setDelegate(delegate);
if (copy.getMaximumNumberOfParameters() == 0) {
copy.call();
} else {
//運(yùn)行閉包
copy.call(delegate);//
}
}
} catch (groovy.lang.MissingMethodException e) {
if (Objects.equal(e.getType(), closure.getClass()) && Objects.equal(e.getMethod(), "doCall")) {
throw new InvalidActionClosureException(closure, delegate);
}
throw e;
}
}
(4)buildTypes{…}
buildTypes{…}位于android{…}代碼塊中窒升,它等價(jià)于AppExtension的buildTypes方法缀遍,該方法的參數(shù)是一個(gè)閉包。AppExtension中定義了一個(gè)buildTypes方法饱须,代碼如下:
void buildTypes(Action<? super NamedDomainObjectContainer<DefaultBuildType>> action) {
plugin.checkTasksAlreadyCreated();
action.execute(buildTypes)
}
buildTypes的閉包的delegate是一個(gè)NamedDomainObjectContainerConfigureDelegate類型的實(shí)例域醇,因此該閉包內(nèi)部的方法都會(huì)被delegate到這個(gè)對(duì)象,諸如”debug”方法蓉媳,”release”方法譬挚,或者其他的以用戶自定義的build type作為名字的方法。相應(yīng)的代碼是在NamedDomainObjectContainerConfigureDelegate的基類ConfigureDelegate的invokeMethod中酪呻,代碼如下:
public Object invokeMethod(String name, Object paramsObj) {
......
//對(duì)已經(jīng)創(chuàng)建過的build type進(jìn)行配置
_delegate.invokeMethod(name, result, params);
if (result.isFound()) {
return result.getResult();
}
MissingMethodException failure = null;
if (!isAlreadyConfiguring) {
// Try to configure element
try {
//創(chuàng)建新的build type并進(jìn)行配置
_configure(name, params, result);
} catch (MissingMethodException e) {
// Workaround for backwards compatibility. Previously, this case would unintentionally cause the method to be invoked on the owner
// continue below
failure = e;
}
if (result.isFound()) {
return result.getResult();
}
}
}
*)對(duì)于已經(jīng)創(chuàng)建過的build type减宣,調(diào)用_delegate.invokeMethod(),進(jìn)而調(diào)用DefaultNamedDomainObjectCollection$ContainerElementsDynamicObject.invokeMethod()進(jìn)行配置
*)對(duì)于需要?jiǎng)?chuàng)建的build type,調(diào)用_configure(), 進(jìn)而調(diào)用AbstractNamedDomainObjectContainer.create(String name, Closure configureClosure)進(jìn)行創(chuàng)建和配置
如果大家喜歡玩荠,請(qǐng)不吝打賞F犭纭!