您的浏览器不支持CSS3,建议使用Firfox、Chrome等浏览器,以取得最佳显示效果

Gradle插件开发与执行原理浅析

Android 1,710℃ 0 7个月前 (11-25)

环境

配置gradle(加入环境变量)

简单插件开发

新建一个gradle项目,在工程(主工程或子工程均可)根目录添加一个HelloPlugin.gradle文件

  1. apply plugin: HelloPlugin
  2. class MyExtension {
  3. Boolean enable = true
  4. String text = ''
  5. }
  6. class HelloPlugin implements Plugin<Project> {
  7. @Override
  8. void apply(Project project) {
  9. project.extensions.create('hello', MyExtension)
  10. project.task('hello') << {
  11. MyExtension ext = project.extensions.hello;
  12. if (ext.enable) {
  13. println "Hello ${ext.text}!"
  14. } else {
  15. println 'HelloPlugin is disabled.'
  16. }
  17. }
  18. }
  19. }

在工程根目录对应的build.gradle中添加以下代码

  1. // 调用HelloPlugin.gradle中的代码
  2. apply from: 'HelloPlugin.gradle'
  3. // 设置参数
  4. hello {
  5. enable = true
  6. text = 'World'
  7. }

在工程根目录执行命令行,即可看到插件中定义的Task(hello)被执行

  1. GradleStudy gradle hello
  2. :hello
  3. Hello World!
  4. BUILD SUCCESSFUL
  5. Total time: 0.675 secs

独立工程中开发插件

创建工程

后面的示例用命令行直接开发,先创建一个文件夹(project-dir)用于存放工程。用命令行写Java比较麻烦,但是之所以用命令行,是为了更好的理解gradle。

  1. ~ mkdir hello_proj
  2. ~ cd hello_proj
  3. hello_proj

也可以使用AndroidStudio或IDEA创建一个空的gradle项目。直接使用RootProject开发,则project-dir就是根目录;如果新建子模块开发,则project-dir就是这个模块的目录。

工程根目录下创建工程的gradle配置文件:<project-dir>/build.gradle

  1. hello_proj vim build.gradle
  1. apply plugin: 'groovy'
  2. apply plugin: 'maven'
  3. repositories {
  4. mavenCentral()
  5. }
  6. dependencies {
  7. // compile 'org.codehaus.groovy:groovy-all:2.3.11' // Groovy支持(远程)
  8. compile localGroovy() // Groovy支持(本地)
  9. compile gradleApi() // GradleAPI支持
  10. }

根目录再创建一个settings.build文件配置工程(如果直接使用RootProject,也可以不创建这个文件):<project-dir>/settings.gradle

  1. hello_proj vim settings.gradle
  1. rootProject.name = 'HelloPlugin'

创建源码

源码放在groovy插件默认的SourceSet源码目录下:<project-dir>/src/main/groovy/<package>/<class>.groovy

也可以在build.gradle中通过SourceSet命令指定源码和资源所在目录。

  1. hello_proj mkdir -pv src/main/groovy/com/jzj/groovy/
  2. hello_proj cd src/main/groovy/com/jzj/groovy
  3. groovy vim MyExtension.groovy
  4. groovy vim HelloPlugin.groovy
  1. package com.jzj.groovy
  2. class MyExtension {
  3. Boolean enable = true
  4. String text = ''
  5. }
  1. package com.jzj.groovy
  2. import org.gradle.api.Plugin
  3. import org.gradle.api.Project
  4. class HelloPlugin implements Plugin<Project> {
  5. @Override
  6. void apply(Project project) {
  7. project.extensions.create('hello', MyExtension)
  8. project.task('hello') << {
  9. MyExtension ext = project.extensions.hello;
  10. if (ext.enable) {
  11. println "Hello ${ext.text}!"
  12. } else {
  13. println 'HelloPlugin is disabled.'
  14. }
  15. }
  16. }
  17. }

创建资源文件

资源文件放在groovy插件默认的SourceSet资源目录下:<module-dir>/src/main/resources/META-INF/gradle-plugins/<plugin-name>.properties

示例中定义的plugin-name是HelloPlugin

  1. groovy cd ..
  2. jzj cd ..
  3. com cd ..
  4. groovy cd ..
  5. main mkdir -pv resources/META-INF/gradle-plugins
  6. main ls
  7. groovy resources
  8. main cd resources/META-INF/gradle-plugins
  9. gradle-plugins vim HelloPlugin.properties
  1. implementation-class=com.jzj.groovy.HelloPlugin

此时在命令行中查看文件结构如下。如果是IDEA或AndroidStudio,源码和资源文件的目录会被显示成对应的图标。

  1. hello_proj tree
  2. .
  3. ├── build.gradle
  4. ├── settings.gradle
  5. └── src
  6. └── main
  7. ├── groovy
  8. └── com
  9. └── jzj
  10. └── groovy
  11. ├── HelloPlugin.groovy
  12. └── MyExtension.groovy
  13. └── resources
  14. └── META-INF
  15. └── gradle-plugins
  16. └── HelloPlugin.properties
  17. 9 directories, 5 files

源码可在此下载

https://github.com/jzj1993/HelloGradlePlugin

打包

在命令行所在目录执行gradle build打包

  1. hello_proj gradle build
  2. :compileJava UP-TO-DATE
  3. :compileGroovy
  4. :processResources
  5. :classes
  6. :jar
  7. :assemble
  8. :compileTestJava UP-TO-DATE
  9. :compileTestGroovy UP-TO-DATE
  10. :processTestResources UP-TO-DATE
  11. :testClasses UP-TO-DATE
  12. :test UP-TO-DATE
  13. :check UP-TO-DATE
  14. :build
  15. BUILD SUCCESSFUL
  16. Total time: 1.714 secs

打包后目录结构如下,默认文件输出到build目录下,build/libs/HelloPlugin.jar就是最终生成的插件

  1. hello_proj tree
  2. .
  3. ├── build
  4. ├── classes
  5. └── main
  6. └── com
  7. └── jzj
  8. └── groovy
  9. ├── HelloPlugin$_apply_closure1.class
  10. ├── HelloPlugin.class
  11. └── MyExtension.class
  12. ├── libs
  13. └── HelloPlugin.jar
  14. ├── resources
  15. └── main
  16. └── META-INF
  17. └── gradle-plugins
  18. └── HelloPlugin.properties
  19. └── tmp
  20. ├── compileGroovy
  21. └── groovy-java-stubs
  22. └── jar
  23. └── MANIFEST.MF
  24. ├── build.gradle
  25. ├── settings.gradle
  26. └── src
  27. └── main
  28. ├── groovy
  29. └── com
  30. └── jzj
  31. └── groovy
  32. ├── HelloPlugin.groovy
  33. └── MyExtension.groovy
  34. └── resources
  35. └── META-INF
  36. └── gradle-plugins
  37. └── HelloPlugin.properties
  38. 24 directories, 11 files

插件使用

在需要使用插件的工程build.gradle中配置如下

BuildScript配置

通常在RootProject中的build.gradle中配置buildscript。和项目中的dependencies不同,buildscript代码块中的dependencies是在编译阶段需要依赖的包,而不会被编译进工程中。

在buildscript中添加对Gradle插件的依赖。

  • 对于已经发布到远程的插件,可以使用classpath 'group:name:version'的格式。
  • 也可以将插件发布到本地Maven仓库,在repositories中添加本地maven仓库。
  • 还可以直接用本地jar文件,使用classpath files('xxx.jar')的方式依赖。
  1. buildscript {
  2. repositories {
  3. jcenter()
  4. // maven { url uri('../repo') } // 指定本地maven仓库的路径
  5. // ...
  6. }
  7. dependencies {
  8. // classpath 'com.jzj.gradle:HelloPlugin:0.0.1' // 依赖远程插件
  9. classpath files('HelloPlugin.jar') // 依赖本地文件,需要将jar文件放到项目根目录
  10. }
  11. }

Project配置

对于需要使用插件的Project,在其build.gradle中添加下面的脚本。

  1. // 应用插件
  2. apply plugin: 'HelloPlugin'
  3. // 插件配置
  4. hello {
  5. enable = true
  6. text = 'World'
  7. }

执行

在Project根目录运行gradle指令即可看到效果。

  1. GradleStudy gradle hello
  2. :hello
  3. Hello World!
  4. BUILD SUCCESSFUL
  5. Total time: 0.675 secs

执行原理浅析

Groovy

  1. Groovy是一种脚本语言,在Java基础上进行了一些扩展,支持闭包、动态类型等特性,兼容Java代码。
  2. 每个Groovy脚本文件会编译生成一个继承自groovy.lang.Script的Java class。

动态类型

  1. def var = 'text'
  2. println var
  3. var = 5
  4. println var + 1
  1. Object var = "text";
  2. System.out.println((String)o);
  3. var = 5;
  4. System.out.println(String.valueof((Integer)o + 1));

闭包 Closure

  1. Closure c = { a, b ->
  2. println a
  3. println b
  4. }
  5. c.call('text1', 5)
  1. public static void main(String[] args) {
  2. abstract class MyClosure {
  3. abstract void call(Object a, Object b);
  4. }
  5. MyClosure c = new MyClosure() {
  6. @Override
  7. void call(Object a, Object b) {
  8. System.out.println(a);
  9. System.out.println(b);
  10. }
  11. };
  12. c.call("text1", 5);
  13. }

代理对象 DelegateObject

  • 每个闭包都有一个代理对象,在闭包上未找到的属性和方法都会转给代理对象。
  1. class MyDelegate {
  2. def func() {
  3. println 'func'
  4. }
  5. }
  6. Closure c = {
  7. func();
  8. }
  9. c.delegate = new MyDelegate()
  10. c.call()
  1. public static void main(String[] args) {
  2. class MyDelegate {
  3. void func() {
  4. System.out.println("func");
  5. }
  6. }
  7. abstract class MyClosure {
  8. Object delegate;
  9. boolean callMethod(Object o, String method, Object... args) {
  10. try {
  11. Method func = o.getClass().getDeclaredMethod(method);
  12. if (func != null) {
  13. func.invoke(o, args);
  14. return true;
  15. }
  16. } catch (Exception e) {
  17. // do nothing.
  18. }
  19. return false;
  20. }
  21. abstract void call();
  22. }
  23. MyClosure c = new MyClosure() {
  24. @Override
  25. void call() {
  26. if (!callMethod(this, "func")) {
  27. callMethod(delegate, "func");
  28. }
  29. }
  30. };
  31. c.delegate = new MyDelegate();
  32. c.call();
  33. }

this, owner, delegate

  1. class Cls {
  2. def mCls = this;
  3. def a, b, c;
  4. def static assertSameObj(x, y) {
  5. assert x.is(y);
  6. }
  7. def static assertSameObj(Closure x, y) {
  8. assert x.is(y);
  9. }
  10. def start() {
  11. println 'this = ' + this
  12. a = {
  13. println 'a.this = ' + this
  14. println 'a.owner = ' + owner
  15. println 'a.delegate = ' + delegate
  16. assertSameObj(this, mCls)
  17. assertSameObj(owner, mCls)
  18. assertSameObj(delegate, owner)
  19. b = {
  20. println 'b.this = ' + this
  21. println 'b.owner = ' + owner
  22. println 'b.delegate = ' + delegate
  23. assertSameObj(this, mCls)
  24. assertSameObj(owner, a)
  25. assertSameObj(delegate, owner)
  26. c = {
  27. println 'c.this = ' + this
  28. println 'c.owner = ' + owner
  29. println 'c.delegate = ' + delegate
  30. assertSameObj(this, mCls)
  31. assertSameObj(owner, b)
  32. assertSameObj(delegate, owner)
  33. }
  34. println('c = ' + c)
  35. c.call();
  36. // 修改c的delegate,并调用c没有但delegate有的方法
  37. c.delegate = new String('123');
  38. println('length = ' + c.length())
  39. }
  40. println('b = ' + b)
  41. b.call();
  42. }
  43. println('a = ' + a)
  44. a.call()
  45. }
  46. }
  47. new Cls().start();
  1. this = Cls@33990a0c
  2. a = Cls$_start_closure1@50b5ac82
  3. a.this = Cls@33990a0c
  4. a.owner = Cls@33990a0c
  5. a.delegate = Cls@33990a0c
  6. b = Cls$_start_closure1$_closure2@6babf3bf
  7. b.this = Cls@33990a0c
  8. b.owner = Cls$_start_closure1@50b5ac82
  9. b.delegate = Cls$_start_closure1@50b5ac82
  10. c = Cls$_start_closure1$_closure2$_closure3@7ea9e1e2
  11. c.this = Cls@33990a0c
  12. c.owner = Cls$_start_closure1$_closure2@6babf3bf
  13. c.delegate = Cls$_start_closure1$_closure2@6babf3bf
  14. length = 3

结论:

  • 语法上直接将Closure赋值给Object编译器会有警告,但不影响实际运行。闭包最终也是通过Object实现的。

  • this指向其外部的Object对象,指定义闭包的类

  • owner指向其外部的Object/Closure,指直接包含闭包的类或闭包

  • delegate默认和owner一致,且可以修改,指用于解析闭包中属性和方法调用的第三方对象

Gradle

  1. Gradle为基于Groovy的一种领域专用语言(DSL/Domain Specific Language)

  2. 每个Gradle脚本文件编译生成的类除了继承自groovy.lang.Script,同时还实现了接口org.gradle.api.Script

  3. Gradle工程build时,会执行settings.gradlebuild.gradle脚本;settings脚本的代理对象是Setting对象,build脚本的代理对象是Project对象。

Gradle Delegate

Build脚本对应的Project对象从6个范围中查找方法:

  1. Project对象本身定义的方法
  2. 脚本文件中定义的方法
  3. 被插件添加的extension. extension的名字可以做为方法名
  4. 被插件添加的convension方法。
  5. 工程中的task。task的名字可以作为方法名
  6. 父工程中的方法。

例如在build.gradle中,常会使用dependencies语句块。

  1. // build.gradle
  2. dependencies {
  3. compile 'xxx:xxx:1.0'
  4. testCompile 'xxx:xxx:1.0'
  5. }
  • dependencies是Project对象中定义的DSL方法,后面的大括号是其接受的闭包参数;这里Groovy的括号可以省略;

  • 闭包的delegate是DependencyHandler getDependencies(),因此其内部可以直接调用DependencyHandler定义的compile/testCompile等方法;

下面的写法也是可以的。

  1. // build.gradle
  2. dependencies {
  3. compile 'xxx'
  4. }
  5. getProject().dependencies ({
  6. compile('xxx')
  7. })
  8. getProject().getDependencies().compile('xxx')
  9. project.dependencies.compile('xxx')

apply plugin

如果分析gradle的源码可以知道,执行apply plugin时,会执行Plugin的apply方法,apply中project.extensions.create('hello', MyExtension)动态给Project对象创建了名为hello的extensions,因此在apply之后,可以使用hello(Closure)

如果把apply和MyExtention的位置调换,gradle编译时就会报错,提示找不到DSL。

  1. Error:(4, 0) Gradle DSL method not found: 'hello()'
  2. Possible causes:<ul><li>The project 'Gradle' may be using a version of Gradle that does not contain the method.
  3. <a href="open.wrapper.file">Open Gradle wrapper file</a></li><li>The build file may be missing a Gradle plugin.
  4. <a href="apply.gradle.plugin">Apply Gradle plugin</a></li>

Internal Gradle Plugins

  • 从Gradle源码可以看到其内部实现了JavaPluginGroovyPluginWarPlugin(JavaWebApplication)等基础插件。

  • 实际上dependencies中的compile就是由JavaBasePlugin动态创建的一个Configuration DSL。

  • groovy、android等插件在apply时也会先apply JavaBasePlugin,所以就有了compile、sourceSet等DSL。

  1. // org.gradle.api.plugins.JavaBasePlugin.java
  2. private void defineConfigurationsForSourceSet(SourceSet sourceSet, ConfigurationContainer configurations) {
  3. Configuration compileConfiguration = configurations.maybeCreate(sourceSet.getCompileConfigurationName());
  4. compileConfiguration.setVisible(false);
  5. compileConfiguration.setDescription(String.format("Dependencies for %s.", sourceSet));
  6. // ...
  7. sourceSet.setCompileClasspath(compileClasspathConfiguration);
  8. sourceSet.setRuntimeClasspath(sourceSet.getOutput().plus(runtimeConfiguration));
  9. }
  1. // groovy plugin
  2. public void apply(ProjectInternal project) {
  3. project.getPluginManager().apply(JavaBasePlugin.class);
  4. // ...
  5. }

build流程

Gradle脚本的build流程分为3个阶段:

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

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

  3. 执行阶段
    执行选中的task,例如build,assembleDebug等。

参考资料与扩展阅读

  1. 《Gradle脚本基础全攻略》 http://blog.csdn.net/yanbober/article/details/49314255

  2. 《Groovy脚本基础全攻略》 http://blog.csdn.net/yanbober/article/details/49047515

  3. 《Chapter 40. Writing Custom Plugins》 https://docs.gradle.org/current/userguide/custom_plugins.html

  4. 《GRADLE脚本的语法和BUILD流程》 http://www.jianshu.com/p/20f6695a9bd5

  5. 《深入理解Android(一):Gradle详解》 http://www.infoq.com/cn/articles/android-in-depth-gradle/

来自为知笔记(Wiz)

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!

0

暂无评论

评论前:需填写以下信息,或 登录

用户登录

忘记密码?