来到新公司后,小灵通开始接手了核心技术-快编插件,看到传说中的核心技术,小灵通傻眼了,啊这,groovy 写的插件,groovy 认真的嘛,2202 年了,插件咋还用 groovy 写呢,我新手写插件也换 kotlin 了,张嘴就是 这辈子都不可能写 groovy,甭想了。 但是嘛,工作不寒碜,学学呗。

一开始和组里几个大佬聊下来,磨刀霍霍准备对历史代码动刀,全迁移到android/harmonyos kotlin 上爽一发,但发现。。。咦,代码好像看不懂诶,我不知道 kt 对应kotlin语言的写法是啥样的。文章结束,小灵通因此被辞退。

开个玩笑,我现在还是在岗状态。工作还是要继续的。既然能力有限我全部迁不过去,那我可以做到新需求用gradle依赖冲突强制指定 kotlin 来写嘛,咦,这就有意思了。

Groovy 和 java 以及 kotlin 如何混编

怎么实现混编

我不会嘛,看看官方怎么写的。gradle 源码有这么段代码来阐释了是怎么优先 gr效率计算公式oovy 编译 而非 java 编译.

// tag::compile-task-classpath[]
tasks.named('compileGroovy') {
  // Groovy only needs the declared dependencies
  // (and not longer the output of compileJava)
  classpath = sourceSets.main.compileClasspath
}
tasks.named('compileJava') {
  // Java also depends on the result of Groovy compilation
  // (which automatically makes it depend of compileGroovy)
  classpath += files(sourceSets.main.groovy.classesDirectory)
}
// end::compile-task-classpath[]

噢,gradle文件夹可以删吗可以这么写啊,那我是不是抄下就可以了,把名字改改。我就可以写 kotlin 了,欧耶!

compileKotlin {
  classpath = sourceSets.main.compileClasspath
}
compileGroovy {
  classpath += files(sourceSets.main.kotlin.classesDirectory)
}

跑一发,没有意外的话,你会看到这个报错。

我与 Groovy 不共戴天

诶,为啥我照着抄就跑不起来呢?我怀疑是 kotlin classesDiretory 有问题,断点看一波 compileGroovy 这个 task 的 sourceSets.main.kotlin.classesDirectory 是个啥。大概长这样, 是个 DefaultDirectoryVar 类。

我与 Groovy 不共戴天

诶,这是个啥,一kotlin是什么开始我也看不太懂,觉得这里的 value 是 uAndroidndefined 怪怪的,也不确定,那我看看其他正常的 classesDirectory 是啥

我与 Groovy 不共戴天

其实到这里可以确定应该是 kotlin 的 classDirectory 在此时是不可用的状态,印证下自己猜想,尝试添加 catch 的断点,确实是这样

我与 Groovy 不共戴天
具体为啥此时还不可用,我没有更详细的深入了,有大佬知道的,android的drawable类可以不吝赐教下kotlin什么意思

SO 搜了一波解答,看到一篇靠谱的回复 compile-groovy-and-kotlin.

compileGroovy.dependsOn compileKotlin
compileGroovy.classpath += files(compileKotlin.destinationDir)

试了一下确实是可以的,但为啥这样可以了呢?以及android是什么系统最上面官方的代码是啥意思呢?还有一些奇奇怪怪的名词是啥,下面吹一下

关于 souceset

我们入门写 android 时,都看到 / 写过类似这样的代码

sourceSets {
  main.java.srcDirs = ['src/java']
}

我对他的理解是指接口卡定 main sourceset 下的 java 的源码目录。 SourceSets 是一个 Sourset 的容器用来创建一个个效率的 SourceSet, 比如 main, test. 而 main 下的 java, groovy, kotlin 目录是一个编译目录(SourceDirectorySet),编译实质是找到一个个的编译目录,然后将他们变成 .class 文件放在 build/classes/sourceDirectorySet 下面, 也就是 destinationDirectory。

像 main 对应的是 SourceSet 接口,其实现是 DefaultSourceSet。而 main 下面的 groovy, java, kotlin 是 SourceDirectorySet 接口,其实现是 DefaultS效率公式ourceDirectorySet。

官方接口自动化 gradle 对于 sgradle是什么ourceset 的定义是:

  • the source files and where they’re located 定位源码的位置
  • the compilation classpath, including any required dependencandroid的drawable类ies (via Gradle configurations) 编译时kotlin怎么读的 class path
  • where the compandroid是什么手机牌子iled clasandroid平板电脑价格s files are placed 编译出的 class 放在哪

输入文件 + 编效率译时 classpath 经过 AbstractCoandroid的drawable类mpile Task 得到 输出的 class 目录

我与 Groovy 不共戴天

第二个 编译时的 classpath,在项目里也见过,sourceSetImplementation 声明 sourceSet 的依赖。第三个我很少见到,印象不深,SourceDirectorySet#destinat接口crc错误计数ionDirectory 用来指定 compile task 的效率的拼音输出目录。而 SourceDirectorySet#classeskotlin为什么流行不起来Directory 和这个值是一致的。再重申一遍这里的 SourceDireckotlin是什么torySet 想成是 DSL 里写的 java,kotlin为什么流行不起来 groovy,kt 就好了。

官方文档对于 class效率意识方面存在的问题esDirectory 的描述是

The directory propegradle是什么rty that is bound to the tasandroid/harmonyosk that produces the output vi接口crc错误计数a SourceDirectorySet.compiledBy(org.gradle.api.tasks.TaskProvider, java.util.function.Function). Use this as part of a classpath or input to another task to eandroid平板电脑价格nsure that the output is created before it iandroid平板电脑价格s used. Nandroidstudio安装教程ot接口crc错误计数e: To define the path of the output folder ukotlin什么意思se SourceDirectorySet.getDestinationDirectory()

大意是android是什么系统 classesDirectory 与这个 compile task 的输出是相关联的,具体是通过 SourceDirectorySet.compiledBy() 方法,这个字段由gradle项目 destinationDirectory 字段决定。查看 Defa接口测试ultSourceDirectorySet#compiledBy 方法

  public <T extends Task> void compiledBy(TaskProvider<T> taskProvider, Function<T, DirectoryProperty> mapping) {
    this.compileTaskProvider = taskProvider;
    taskProvider.configure(task -> {
      if (taskProvider == this.compileTaskProvider) {
        mapping.apply(task).set(destinationDirectory);
       }
     });
    classesDirectory.set(taskProvider.flatMap(mapping::apply));
   }

雀食语义上 classesDirectory ==androidstudio安装教程 destinationDirectory。

现在我们可以去理解下 官方的接口crc错误计数 demo 了,官方的 demo 简gradle文件夹可以删吗单说就是优先执行 C效率ompile Gandroid手机roovy task, 再去执行 Compile Java taskgradle教程.

tasks.named('compileGroovy') {
  classpath = sourceSets.main.compileClasspath // 1
}
tasks.named('compileJava') {
  classpath += files(sourceSets.main.groovy.classesDirectory) // 2
}

可能看不懂的地方是 1,2 注释处做了啥, 1 处我问了我们组大佬,这是重置了 compileGr接口测试用例设计oovy task 的 classpath 使其不依赖 compile java cl接口测试asspath,在 Grandroid是什么系统oovyPlugin 源码中有那么一句代码

    classpath.from((Callable<Object>) () -> sourceSet.getCompileClasspath().plus(target.files(sourceSet.getJava().getClassesDirectory())));

可以看到 GroovyPlugin 其实是依赖于 java 的 clas接口是什么spath 的。gradle项目这里我们需要改变 groovy 和 java 的编译android平板电脑价格时序需要把效率意识方面存在的问题这层依赖断开。

2呢,使 compileJava 依赖上 compileGrkotlin和javaoovy 的 output property,间接使 compileJava dependson compileGroovy 任务。

具体为啥 Kotlin 的不行,android什么意思俺还没搞清楚,知道的大佬可以指教下。

而 SO 上的这个答效率公式复其实也是类似的,而且更直接

compileGroovy.dependsOn compileKotlin
compileGroovy.classpath += files(compileKotlin.destinationDir)

使 compileGroovy 依赖于 compileKotlin 任务,再让 compileGroovy 的 classPath 添加上 compileKotlin 的 output. 既然任务的 classPath 添加 另一个效率集任务的 output 会自动依赖上另一个 task。那其实这么写也是可以的

compileGroovy.classpath += files(compileKotlin.destinationDir)

实验了下雀食是可以跑的. 那既然 Groovy 和 Java 都包含 maAndroidin 的 classpath,是不是 compileKotlin 的效率计算公式 classpath 置为 main,那 compileGroovy 会自动依赖上 compileKotlin。试试呗

compileKotlin.classpath = sourceSets.main.compileClasspath

我与 Groovy 不共戴天
可以看到 kotlin 的执行顺序雀食跑到了最前面。kotlin怎么读

在项目实操中,我发现 Kotlin 跑在了 compile 的最前面,那其实 kotlin 的类里面是不能依赖 java 或者 groovy 的任何依赖的。这也符合预期,不然就gradle依赖冲突强制指定会出现依赖成环,报 Circular dependsOn hierarchy found in the Kotlin source sets 错误。我个人观点这是一种对历史代码改造的折衷,在新需求上使用 kotlandroid的drawable类in 进行开发,一些功能相同androidstudio安装教程的工具类能翻译成 kt 就翻译,不能就重写一套。

小结

  • 在这节讲了两种实现混编的方案。写法不同,本质都是使一个任务依赖另androidstudio安装教程一个任务的 output
// 1
compileGroovy.classpath += files(compileKotlin.destinationDir)
// 2
compileKotlin.classpath = sourceSets.main.compileClasspath
  • 我对于 SourceSet 和 SourceDirectogradle是什么rySet 的理解
  • 项目中实践混编方案的现状

Groovy 有趣的语法糖

在写kotlin匿名函数 Groovy 的过程中,我遇到一个头大的问题,代码看不懂,里面有一些奇奇怪怪没见过的语法糖,乍一看就懵了,你要不一起瞅瞅。

includes*.tasks

我司的仓库是大仓的结构,仓库和子仓之间是通过 Composite build 构建联系接口自动化的。那么怎么使主仓的 task 触发 includeBuild 的仓库执行对应仓库呢?是通过这行代码实现的

tasks.register('publishDeps') {
  dependsOn gradle.includedBuilds*.task(':publishIvyPublicationToIvyRepository')
}

这里的 includeBuilds*.task 后面的 *.task 是啥?includegradle是什么Builds 看源码发现是个 List。我不懂 groovy,但好歹我能gradle和maven看懂 kotli效率公式n, 我kotlin和java看看官方文档右边对应的 kt 写法是啥?

tasks.register("publishDeps") {
  dependsOn(gradle.includedBuilds.map { it.task(":publishMavenPublicationToMavenRepository") })
}

咦嘿,原来是个 List 的 map 操作,骚里骚气的。翻了翻原来是个 gandroid手机roovy 的语法糖,写个代码试试看看他编译到 class 是啥样子

def list = ["1", "22", "333"]
def lengths = list*.size()
lengths.forEach{
  println it
}

编译成 class

     Object list = ScriptBytecodeAdapter.createList(new Object[]{"1", "22", "333"});
     Object lengths = ScriptBytecodeAdapter.invokeMethod0SpreadSafe(Groovy.class, list, (String)"size");
     var1[0].call(lengths, new Groovy._closure1(this, this));

在 ScriptBytecandroid什么意思ode接口Adapter.invokeMethod0SpreadSafe 实现内部其实还是新建了一个 List 再逐个对 List 中元素进行 map.

String.ekotlin什么意思xecute

这是执行一个 shell 指令,比如 “landroid下载s -al”.execute(), 刚看到这个的时候认为这个接口文档东西类似 kotlin 的扩展函数,点进去看实现发现不一样

public static Process execute(final String self) throws IOException {
    return Runtime.getRuntime().exec(self);
 }

可以看到 receiver 是他的第一个参数,莫非android是什么手机牌子这是通用的语法糖,我试试写了个

public static String deco(final String self) throws IOException {
    return self + "deco"
   }
// println "".deco()

运行下,哦吼,跑不了,报了 MissingMethodException。看样子是不通用的。翻了翻 groovy 文档,找到了这个文档

Static methods are used with the first parameterandroid什么意思 being thekotlin语言 destination class, i.e. public static String reverse(String self) provides a reverse() method for String.

看样子这个语法糖是 groovy 内部android什么意思定制的,我不清楚有没有支持开发定制的方式,效率是什么意思知道的大佬可以评论区留言下。

Range 怎么写

groovy 也有类似 kotlin 的接口crc错误计数 Range 的概念,包含的 Range 是 .. , 不包含右边界(kotlin是什么until)的是 ..<

Try with resources

我遇到过一个 OKHttp 连接泄露的问题,代码原型大概是这样

if (xxx) {
 response.close()
} else {
 // behavior
}

定位到是 Response 没有在 else 的分支上进行 close,当然可以效率集简单在 else 分支上进行 close, 并在外层补上 try, catch 兜底,但在 Effective Javakotlin语言书提及针对资源关闭 try-with-resou效率计算公式rce 优于 try cactch。但我尝试像 java 一样写 try-with-resource,发现嗝屁了,直接报红,我去 SO 上搜了一波 groovy 的 try-with-resource. Groovy 是通过 withCloseable 扩展来实现,看这个方法的声明与 Prockotlin和java区别ess效率的英文#execute 语法糖类似—public static def withCloseable(Closeable self, Clkotlin是什么osure action) . 最终改造后的代码是这样的

Response.withCloseable { reponse ->
 if (xxx) {
  
  } else {
  
  }
}
<<

这个是 groovy 中的左移运算符效率也是可以重载的,而 kotlin 是不支持的。他运用比较多的场景。起初我印象中 Task 的 是覆写了这个运算符作为gradle怎么读 doLast 简易写法,现在 gradle7.效率集X 的版本上是没有了。其它常见的是文件写入操作, 列表添加元素。

def file = new File("xxx")
file << "text"
def list = []
list << "aaa"

Groovy 的一家之言

如果 kotlinandroid是什么系统 是 better java, 那么 groovy 应该是 more than java,它的定接口文档位更加偏向脚本一些,更加Kotlin动态化(从它反编译的字节码可见一斑),上手曲线较高,但一个人精通这个语言,并且独立维护一个项目,其实 groov接口英文y 的开发效率并不会比gradle菜鸟教程 kotlkotlin是什么in 和 java 差,感受比较深切的是 maven publish 的例子,看看插件中 groovy 和 kotlin效率高发票查验 的写法上的不同。

// Groovy
def mavenSettings = {
      groupId 'org.gradle.sample'
      artifactId 'library'
      version '1.1'
     }
 def repSettings = {
      repositories {
        maven {
          url = mavenUrl
         }
       }
     }
​
afterEvaluate {
  publishing {
    publications {
      maven(MavenPublication) {
              ConfigureUtil.configure(mavenSettings, it)
        from components.java
       }
     }
    ConfigureUtil.configure(repoSettings, it)
  }
 def publication = publishing.publications.'maven' as MavenPublication
  publication.pom.withXml { 
   // inject msg
  }
}
// Kotlin
// Codes are borrowed from (sonatype-publish-plugin)[https://github.com/johnsonlee/sonatype-publish-plugin/]
fun Project.publishing(
    config: PublishingExtension.() -> Unit
) = extensions.configure(PublishingExtension::class.java, config)
val Project.publishing: PublishingExtension
  get() = extensions.getByType(PublishingExtension::class.java)
​
val mavenClosure = closureOf<MavenPublication> { 
  groupId = "org.gradle.sample"
   artifactId = "library"
   version = "1.1"
}
val repClosure = closureOf<PublishingExtension> {
  repositories {
    maven {
      url = mavenUrl
     }
   }
}
afterEvaluate {
    publishing {
      publications {
        create<MavenPublication>("maven") {
          ConfigureUtil.configure(mavenClosure, this)
          from(components["java"])
         }
       }
       ConfigureUtil.configure(repoClosure, this)
    }
    val publication = publishing.publications["maven"] as MavenPublication
   publication.pom.withXml { 
       // inject msg
   }
}

我觉得吧,kotlin是什么如果像我们大佬擅长 groovy 的话,而且是一个人开发的商业项目,插件里gradle下载的确写 groovy 会更快,更简洁,那为什么不呢?这对他来说是种善,语言没有优劣,动态性和静态语言优劣我不想较高下,这因人而异。

我与 Groovy 不共戴天
我选择 kotlin 是俺不擅长写 groovy 啊,我写了几个月 groovy 每次改动插件发布后再应用第一次都会有语法错误,调试的头皮发麻,所以最后搞了个折衷方案,新代码用 kotlin, 旧代码用 groovy 继续写。而且参考了 KOGE@2BAB 文档,发现咦,gradle 正面回应过 groovy 与 kotlin 之争. “Prefer using a statically-typed language to implement a plugin”@Gradle。嗯, 我还是继续写 Kot效率高发票查验lin 吧kotlin是什么