Groovy Closure & Action

Android现在的构建工具用的是gradle,很早以前就有过接触,只不过从来没有用到实际的项目中。

这两天在看gradle的一些官方文档和官方推荐的书,并结合着项目的build脚本。对gradle及其所用的groovy语言大致有了个认识。不过期间一直有一个问题在困扰我:

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }

如上述代码所示 buildTypes 的是一个method,那么其后的block应该是一个closure,但是buildTypes的方法声明却是这样的

    /**
     * Configures the build types.
     */
    void buildTypes(Action> action) {
        checkWritability()
        action.execute(buildTypes)
    }

这里,我就纳闷了,明明接受的是一个Action 类型的参数啊,为啥build脚本里面给的是一个Closure呢?我第一反应,肯定是哪里把Closure强转成了Action(事实证明,第一反应得方向是对的,哈哈)。

不过我找了N久,就是找不到哪里调用了buildTypes,并在这之前把参数类型做了强转。尼玛,纠结了好久。后来我就想,既然在代码里面找不到直接的转换的代码,是不是可能是在gradle内部做了呢?

顺着这个思路,我自己写了个gradle脚本,很简单

    def buildTypes(Action action) {
        println ''
        def text = "action is called here"
        action.execute(text)
    }

    interface Command {
        void balabala(String test);
    }

    def testCommand(Command test) {
        println ''
        test.balabala("balabala......")
    }

    task hello << {
        println 'hello task is called'

        buildTypes {
            println 'Action for buildTypes'
        }

        testCommand { String para ->
            println 'Command->' + para
        }
    }

       上面这段代码的运行结果如下:

       

       可以看到task hello中buildTypes和testCommand方法后的closure都已经被调用了,而且通过Command接口中的balabala方法传递的参数,testCommand后的closure也能收到。

那么可以确定的是,Closure一定在某个时候被强转成了Action(其实对了一半)。另外可以看到不仅仅是gradle自己的Action接口可以这样,自己写得Command接口也是可以的,所以

干这个事情的很大程度上应该不是gradle,那么剩下的应该就是groovy啦。

 

     于是我又做了一个实验,直接写一个groovy的脚本来验证一下

    interface Command {
        void excute(String a, Integer b)
    }

    def buildTypes(Command cmd) {
        println '++++++++buildTypes was called'
        cmd.excute('meituan', 2015)
    }

    buildTypes { String a, Integer b ->
        println 'fairysword ' + a + '###' + b
    }

    buildTypes { String a, Integer b ->
        println 'uabearbest ' + a + '@@@' + b
    }

        上面这段脚本运行结果如下

        

        嗯,到这里,应该可以看出来,的确是groovy干的,不过怎么干的,咱还是不知道。不过我们知道groovy和java都是运行在JVM上,groovy也是编译成java字节码的,

所以,我试着把上述脚本直接编译后,研究一下。先把脚本编译成class文件,在反编译成java文件。结果如下

/*
 * Decompiled with CFR 0_102.
 * 
 * Could not load the following classes:
 *  Command
 *  groovy.lang.Binding
 *  groovy.lang.GroovyObject
 *  groovy.lang.MetaClass
 *  groovy.lang.Script
 *  org.codehaus.groovy.reflection.ClassInfo
 *  org.codehaus.groovy.runtime.InvokerHelper
 *  org.codehaus.groovy.runtime.ScriptBytecodeAdapter
 *  org.codehaus.groovy.runtime.callsite.CallSite
 *  org.codehaus.groovy.runtime.callsite.CallSiteArray
 *  test$_run_closure1
 *  test$_run_closure2
 */

import Command;
import groovy.lang.Binding;
import groovy.lang.GroovyObject;
import groovy.lang.MetaClass;
import groovy.lang.Script;

import org.codehaus.groovy.reflection.ClassInfo;
import org.codehaus.groovy.runtime.InvokerHelper;
import org.codehaus.groovy.runtime.ScriptBytecodeAdapter;
import org.codehaus.groovy.runtime.callsite.CallSite;
import org.codehaus.groovy.runtime.callsite.CallSiteArray;

import test;

public class test
        extends Script {
    private static /* synthetic */ ClassInfo $staticClassInfo;
    public static transient /* synthetic */ boolean __$stMC;
    private static /* synthetic */ SoftReference $callSiteArray;

    public test() {
        test test;
        CallSite[] arrcallSite = test.$getCallSiteArray();
    }

    public test(Binding context) {
        CallSite[] arrcallSite = test.$getCallSiteArray();
        super(context);
    }

    public static /* varargs */ void main(String... args) {
        CallSite[] arrcallSite = test.$getCallSiteArray();
        arrcallSite[0].call((Object) InvokerHelper.class, (Object) test.class, (Object) args);
    }

    public Object run() {
        CallSite[] arrcallSite = test.$getCallSiteArray();
        arrcallSite[1].callCurrent((GroovyObject) this, (Object) new _run_closure1((Object) this, (Object) this));
        return arrcallSite[2].callCurrent((GroovyObject) this, (Object) new _run_closure2((Object) this, (Object) this));
    }

    public Object buildTypes(Command cmd) {
        CallSite[] arrcallSite = test.$getCallSiteArray();
        arrcallSite[3].callCurrent((GroovyObject) this, (Object) "++++++++buildTypes was called");
        return arrcallSite[4].call((Object) cmd, (Object) "xiong", (Object) 1987);
    }

    protected /* synthetic */ MetaClass $getStaticMetaClass() {
        if (this.getClass() != test.class) {
            return ScriptBytecodeAdapter.initMetaClass((Object) this);
        }
        ClassInfo classInfo = $staticClassInfo;
        if (classInfo == null) {
            $staticClassInfo = classInfo = ClassInfo.getClassInfo(this.getClass());
        }
        return classInfo.getMetaClass();
    }

    private static /* synthetic */ void $createCallSiteArray_1(String[] arrstring) {
        arrstring[0] = "runScript";
        arrstring[1] = "buildTypes";
        arrstring[2] = "buildTypes";
        arrstring[3] = "println";
        arrstring[4] = "excute";
    }

    private static /* synthetic */ CallSiteArray $createCallSiteArray() {
        String[] arrstring = new String[5];
        test.$createCallSiteArray_1(arrstring);
        return new CallSiteArray((Class) test.class, arrstring);
    }

    private static /* synthetic */ CallSite[] $getCallSiteArray() {
        CallSiteArray callSiteArray;
        if ($callSiteArray == null || (callSiteArray = (CallSiteArray) $callSiteArray.get()) == null) {
            callSiteArray = test.$createCallSiteArray();
            $callSiteArray = new SoftReference<CallSiteArray>(callSiteArray);
        }
        return callSiteArray.array;
    }
}

    研究这段代码,可以看出所用的调用点(即groovy所谓的CallSite)都被groovy统一做了处理,在这个groovy的脚本中总共存在5处调用

    private static /* synthetic */ void $createCallSiteArray_1(String[] arrstring) {
        arrstring[0] = "runScript";
        arrstring[1] = "buildTypes";
        arrstring[2] = "buildTypes";
        arrstring[3] = "println";
        arrstring[4] = "excute";
    }

    对buildTypes的两处调用体现在run函数中

public Object run() {
    CallSite[] arrcallSite = test.$getCallSiteArray();
    arrcallSite[1].callCurrent((GroovyObject) this, (Object) new _run_closure1((Object) this, (Object) this));
    return arrcallSite[2].callCurrent((GroovyObject) this, (Object) new _run_closure2((Object) this, (Object) this));
}

    至此,我们终于可以看到groovy并没有把Closure转成Action,而是无差别的都转成了Object,这也解释了为啥buildTypes方法接受的是Command类型的参数,但是实际上你传给他一个Closure参数,也能正常work

 

posted @ 2015-10-15 11:48  lotusJade  阅读(899)  评论(0编辑  收藏  举报