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