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