美文网首页androidAndroidAndroid开发经验谈
GRADLE脚本的语法和BUILD流程

GRADLE脚本的语法和BUILD流程

作者: 恶魔殿下_HIM | 来源:发表于2016-08-12 15:09 被阅读4186次

    导语

    Android Studio中使用了Gradle进行build。我阅读了groovy官方文档,Gradle官方文档及一些源代码,Android插件的官方文档及一些源代码,希望给大家介绍一下Gradle脚本的语法和处理流程。简单Groovy是一种运行在JVM上的语言, Gradle是使用Groovy实现的, Android插件提供了android特定的功能。

    1. Gradle脚本的build流程

    1.1Groovy和脚本

    Groovy会把脚本编译成groovy.lang.Script的子类。groovy.lang.Script是一个抽象类,它有一个抽象方法run(). 如果有一个脚本的文件名是Main,它的内容是:

    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) 
    }
    

    脚本中的语句会成为run方法的实现。

    Gradle脚本编译生成的类当然也继承自groovy.lang.Script,并同时实现了Gradle自己的script接口org.gradle.api.Script。

    1.2 Gradle脚本对象的代理对象

    每一个Gradle脚本对象都有一个代理对象。Settings脚本的代理对象是Setting对象,Build脚本的代理对象是Project对象。每一个在脚本对象未找到的属性和方法都会转到代理对象。

    Groovy和Java的一个不同之处就是Groovy可以动态的添加方法和属性。动态添加的方式之一是覆盖propertyMissing和methodMissing方法,Gradle就是采用这种方式实现了脚本对象的代理对象。下面是Gradle脚本对象的基类BasicScript的实现代码片段:

    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个阶段:

    (1)初始化阶段

    Gradle支持单个和多个工程的编译。在初始化阶段,Gradle判断需要参与编译的工程,为每个工程创建一个Project对象,并建立工程之间的层次关系。这个阶段执行Settings脚本。

    (2)配置阶段

    Gradle对上一步创建的Project对象进行配置。这个阶段执行Build脚本

    (3)执行阶段

    Gradle执行选中的task。

    1.4一个demo

    下面是demo工程的文件层次,这个demo会被后面的部分使用。这个例子包含一个app子工程和一个library子工程。settings.gradle是Setttings脚本,三个build.gradle都是Build脚本。

    --settings.gradle
    --build.gradle
    --app
    --build.gradle
    --mylibrary
    --build.gradle
    

    2. Settings脚本

    2.1 Settings脚本的内容

    Settings脚本通常比较简单,用于初始化project树。demo中settings.gradle的内容是:

    include ':app', ':mylibrary'
    

    2.2 groovy的相关语法

    groovy允许省略语句结尾的分号,并允许在方法调用时省略括号,上面的代码等价于:

    include(':app', ':mylibrary');
    

    2.3 Gradle的相关语法

    初始化脚本的Script对象会有一个Project代理对象。在Script对象没有定义的属性和方法调用就会被转到Project对象。上面的语句实际上调用的是Project对象的include方法,该方法的原型如下:

    void include(String[] projectPaths)
    

    这个方法将给定的工程添加到build中。工程路径的格式是: 以一个可选的”:”的开始,它表示”:”前面有一个不需要名字的根工程;剩下的部分是以”:”分隔的工程名。例如, “:app”中”:”的是可选的,它表示”:”前面有一个不需要名字的根工程。

    运行”gradle projects”可以获得这个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相关的语法

    1)Map

    Groovy可以直接使用Java中的各种集合类型,例如Map和List等等。在初始化语法上有一些扩展

    def key = 'name'
    
    //创建一个Map。注意"Guillaume"的key是字符串"key"而不是变量key的值
    
    def person = [key: 'Guillaume'] 
    
    
    
    assert !person.containsKey('name') 
    
    assert person.containsKey('key') 
    
    
    
    //创建一个Map。我们使用()包围了key。这种情况下,"Guillaume"的key是变量key的值"name"
    
    def person2 = [(key): 'Guillaume'] 
    
    
    
    assert person2.containsKey('name') 
    
    assert !person2.containsKey('key')
    

    (2)闭包

    (2.1)Syntax

    Groovy中的闭包是一个匿名的代码块。它的语法规则是:

    { [closureParameters -> ] statements }
    

    )[closureParameters→]是可选的以”,”分隔的参数列表

    *)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中的闭包有三个重要的概念: Owner, delegate and this

    *)this是指定义闭包的类

    *)Owner是指直接包含闭包的类或闭包

    *)delegate是指用于解析闭包中属性和方法调用的第三方对象

    下面的代码段说明了this和闭包的区别:

    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
    
    //在设置了delegate后,闭包中的name属性被解析成作为delegate的Person的属性 
    
    assert cl() == 'IGOR'
    

    Gradle中大量使用闭包的delegate来实现对各种对象的配置。

    (3)转化成Java语法后的代码

    Map<String, Object> map = new HashMap<String, Object>();
    
    map.put("plugin", "com.android.appliaction"); 
    
    apply(map);//这个方法会代理给Project对象
    
    
    
    android({
    
      //这个闭包的delegate是Android插件添加的extension
    
    
    
      compileSdkVersion(23);
    
      ......
    
    
    
      buildTypes({
    
      //这个闭包的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的相关语法

    (1)Project查找方法的规则

    Build脚本的代理对象是Project对象。Project对象从6个范围中查找方法:

    *)Project对象本身定义的方法

    *)脚本文件中定义的方法

    *)被插件添加的extension. extension的名字可以做为方法名

    *)被插件添加的convension方法。

    *)工程中的task。task的名字可以作为方法名

    *)父工程中的方法。

    (2)apply plugin: ‘com.android.application’

    “apply plugin: ‘com.android.application’”等价于调用Project对象的apply方法,该方法的原型是:

    void apply(Map<String,?> options)
    

    Project.apply方法用于应用脚本和插件。我们指定了键值对”plugin”:”com.android.application”, 因此这个调用会添加Android插件。Android插件会添加一个名字是”android”,类型是com.android.build.gradle.AppExtension的extension.

    (3)android{…}

    这个代码块等价于”android({…});”, 即调用了一个方法名是”android”的方法,该方法的参数是一个闭包。当调用Project对象的android方法时,实际上是找到了名字是”android”的extension, 把这个extension设为闭包的代理并运行这个闭包。从而闭包中的方法调用实际上都是对com.android.build.gradle.AppExtension的调用。这就是上面提到的Project查找方法的规则中的一条:被插件添加的extension的名字可以做为方法名。

    *)查找extension的逻辑是在ExtensionsStoage.configureExtension中,代码如下

    public <T> T configureExtension(String methodName, Object ... arguments) {
    
      Closure closure = (Closure) arguments[0];
    
      ClosureBackedAction<T> action = new ClosureBackedAction<T>(closure);
    
      //根据名字查找extension
    
      ExtensionHolder<T> extensionHolder = extensions.get(methodName);
    
          return extensionHolder.configure(action);//Line 69
    
    }
    

    *)设置闭包的delegate并运行闭包的逻辑是在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);
    
          //设置delegate
    
          copy.setDelegate(delegate);
    
          if (copy.getMaximumNumberOfParameters() == 0) {
    
                copy.call();
    
          } else {
    
            //运行闭包
    
          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{…}代码块中,它等价于AppExtension的buildTypes方法,该方法的参数是一个闭包。AppExtension中定义了一个buildTypes方法,代码如下:

    void buildTypes(Action<? super     NamedDomainObjectContainer<DefaultBuildType>> action) {
    
      plugin.checkTasksAlreadyCreated();
    
      action.execute(buildTypes)
    
    }
    

    buildTypes的闭包的delegate是一个NamedDomainObjectContainerConfigureDelegate类型的实例,因此该闭包内部的方法都会被delegate到这个对象,诸如”debug”方法,”release”方法,或者其他的以用户自定义的build type作为名字的方法。相应的代码是在NamedDomainObjectContainerConfigureDelegate的基类ConfigureDelegate的invokeMethod中,代码如下:

    public Object invokeMethod(String name, Object paramsObj) {
    
    ......
    
      //对已经创建过的build type进行配置
    
    _delegate.invokeMethod(name, result, params);
    
    if (result.isFound()) {
    
        return result.getResult();
    
    }
    
    
    
    MissingMethodException failure = null;
    
    if (!isAlreadyConfiguring) {
    
        // Try to configure element
    
    try {
    
    //创建新的build type并进行配置
    
        _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();
    
    }
    
    }
    }
    

    *)对于已经创建过的build type,调用_delegate.invokeMethod(),进而调用DefaultNamedDomainObjectCollection$ContainerElementsDynamicObject.invokeMethod()进行配置

    *)对于需要创建的build type,调用_configure(), 进而调用AbstractNamedDomainObjectContainer.create(String name, Closure configureClosure)进行创建和配置


    如果大家喜欢,请不吝打赏!!

    相关文章

      网友评论

      • 蜗牛学开车:你好,我想问下在build脚本中能不能通过代码获取到当前正在构建的是release还是debug?
        蜗牛学开车:@恶魔殿下_HIM 在build阶段还没有产生BuildConfig文件啊。
        恶魔殿下_HIM:可以直接读取你java文件的内容的,读到你所需要的值。或者可以直接读取BuildConfig

      本文标题:GRADLE脚本的语法和BUILD流程

      本文链接:https://www.haomeiwen.com/subject/ceeojttx.html