GRADLE腳本的語法和BUILD流程

導(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)不吝打賞?。?/p>

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,215評(píng)論 25 708
  • 這篇文章講給大家?guī)韌radle打包系列中的高級(jí)用法-自己動(dòng)手編寫gradle插件。我們平常在做安卓開發(fā)時(shí),都會(huì)在...
    呆萌狗和求疵喵閱讀 16,340評(píng)論 22 80
  • Gradle是基于Groovy的動(dòng)態(tài)DSL,而Groovy是基于JVM的,Groovy的語法和Java很類似。 C...
    HoooChan閱讀 7,658評(píng)論 0 7
  • Gradle對(duì)于很多開發(fā)者來說有一種既熟悉又陌生的感覺,他是離我們那么近,以至于我每天做項(xiàng)目都需要他,但是他又是離...
    阿_希爸閱讀 9,716評(píng)論 10 199
  • 今年的元宵節(jié)除了少了吵鬧的鞭炮聲外,與往年無異,耳邊倒是清凈了不少,照例蜷縮在自己的床頭,劃拉著手機(jī),看央視...
    懂我笑容任閱讀 394評(píng)論 0 1

友情鏈接更多精彩內(nèi)容