本文介绍 Plugin 的编写以及 bulidSrc 的使用

Plugin基本的写法

build.gradle

  1. class PluginDemo implements Plugin<Project> {
  2. @Override
  3. void apply(Project project) {
  4. println 'hi !!!'
  5. }
  6. }
  7. apply plugin: PluginDemo

Terminal 窗口执行 ./gradlew 得到如下结果
image.png

Extension

同样在 build.gradle

  1. class ExtensionDemo {
  2. def extensionName = 'test'
  3. }

总体代码:

  1. class PluginDemo implements Plugin<Project> {
  2. @Override
  3. void apply(Project project) {
  4. def extension = project.extensions.create('testExtension',
  5. ExtensionDemo)
  6. println "hi ${extension.extensionName}!"
  7. }
  8. }
  9. class ExtensionDemo {
  10. def extensionName = 'test'
  11. }
  12. apply plugin: PluginDemo

执行 ./gradlew 得出结果
image.png
这时就可以在gradle文件下引用 testExtension

  1. // 在project.extensions.create()方法中注册了testExtension这个扩展
  2. // 这段代码在apply plugin: PluginDemo后面
  3. testExtension {
  4. extensionName 'new test'
  5. }

这时候执行,会发现在 Terminal 窗口里打印的依然是 hi test! 而不是 hi new test!

这是因为代码执行顺序问题,赋予新的值的时候已经执行完println,所以无法打印hi new test!

只需要加上afterEvaluate{}便可:

  1. def extension = project.extensions.create('testExtension',
  2. ExtensionDemo)
  3. // 表示在最后执行
  4. project.afterEvaluate {
  5. println "hi ${extension.extensionName}!"
  6. }

image.png

插件写在 buildSrc 下

如果每一个插件都像上述这种写法,必然会无法管理和难读(假设项目要定义起码三个插件)
所以插件应该写在 buildSrc 下

插件的本质:把逻辑独立的代码抽取和封装

关于buildSrc目录

  • 这是 gradle 的一个特殊目录,这个目录的 build.gradle 会自动被执行,即使不配置进 settings.gradle。(实际上在 gradle 的 6.0 之后, buildSrc 已经成为了一个保留字,你在 settings.gradle 里配置的项目已经不允许叫 buildSrc )

  • buildSrc 所配置出来的 Plugin 会被自动添加到编译过程中的每一个 project 的 classpath,因此它们才可以直接使用 apply plugin: ‘xxx’ 的方式来便捷应用这些 plugin

在项目新建一个 Module 取名为 bulidSrc
新建后一般会报错 ‘buildSrc’ cannot be used as a project name as it is a reserved name
需要在 settings.gradke里把 include ':buildSrc'去掉即可
image.png

  1. 把之前写的 PluginDemoExtensionDemo 写在 bulidSrc 下
  2. 在 main 目录下新建 *.properties 文件
  3. 把 main 下的 java 目录改成 groovy

如图:
image.png

  • resources/META-INF/gradle-plugins/*.properties 中的 是插件的名称,例如 `.propertiescom.tongsr.plugindemo.properties` ,最终在应用插件是的代码就应该是: ```groovy apply plugin: ‘com.tongsr.plugindemo’

testExtension { extensionName ‘new test~’ }

  1. - *.properties 中只有一行,格式是:
  2. ```groovy
  3. implementation-class=com.tongsr.buildsrc.PluginDemo
  • Plugin 和 Extension 写法和在 build.gradle 里的写法一样

    运行结果:
    image.png

    Transform

  • 是什么:是由 Android 提供了,在项目构建过程中把编译后的文件(jar 文件和 class 文件)添加自定义的中间处理过程的工具

  • 怎么写

    • 先加上依赖 ```groovy // 因为 buildSrc 需要自己添加仓库 repositories { google() jcenter() }

dependencies { implementation ‘com.android.tools.build:gradle:7.0.4’ }

  1. - 然后继承 `com.android.build.api.transform.Transform` ,创建 一个子类:
  2. ```groovy
  3. class TransformDemo extends Transform {
  4. // 构造方法
  5. TransformDemo() {
  6. }
  7. // 对应的 task 名 @Override
  8. String getName() {
  9. return 'tongsrTransform'
  10. }
  11. // 你要对那些类型的结果进行转换(是字节码还是资源文件?)
  12. @Override
  13. Set<QualifiedContent.ContentType> getInputTypes() {
  14. return TransformManager.CONTENT_CLASS
  15. }
  16. // 适用范围包括什么(整个 project 还是别的什么?)
  17. @Override
  18. Set<? super QualifiedContent.Scope> getScopes() {
  19. return TransformManager.SCOPE_FULL_PROJECT
  20. }
  21. @Override
  22. boolean isIncremental() {
  23. return false
  24. }
  25. // 具体的「转换」过程
  26. @Override
  27. void transform(TransformInvocation transformInvocation) throws TransformException,
  28. InterruptedException, IOException {
  29. def inputs = transformInvocation.inputs
  30. def outputProvider = transformInvocation.outputProvider
  31. inputs.each {
  32. // jarInputs:各个依赖所编译成的 jar 文件
  33. it.jarInputs.each {
  34. // dest: /app/build/intermediates/transforms/tongsrTransform/ ...
  35. File dest = outputProvider.getContentLocation(it.name, it.contentTypes,
  36. it.scopes, Format.JAR)
  37. FileUtils.copyFile(it.file, dest)
  38. }
  39. // derectoryInputs:本地 project 编译成的多个 class 文件存放的目录
  40. it.directoryInputs.each {
  41. // dest: /app/build/intermediates/transforms/tongsrTransform/ ...
  42. File dest = outputProvider.getContentLocation(it.name, it.contentTypes,
  43. it.scopes, Format.DIRECTORY)
  44. FileUtils.copyDirectory(it.file, dest)
  45. }
  46. }
  47. }
  48. }
  • 还能做什么:修改字节码 上面的这段代码只是把编译完的内容原封不动搬运到目标位置,没有实际用处。要修改字节码,需要引入其他工具,例如 javassist。 javassist 的使用教程在网上有很多,可以搜索一下

用buildSrc管理项目版本

目的:管理项目第三方SDK以及Module之间的版本号等

  1. 新建一个 buildSrc,和前文叙述无差别
  2. 把 build.gradle 改成 build.gradle.kts
  3. 在 java 目录下新建一个 Dependencies 类 ```kotlin object Libs { const val CORE = “androidx.core:core-ktx:1.7.0” }

object Version { const val MIN_SDK = 21 }

  1. 4. Sync项目后,在 app build.gradle 引用定义好的 Libs Version,这里做个简单示例
  2. ```groovy
  3. android {
  4. compileSdk 31
  5. defaultConfig {
  6. applicationId "com.tongsr.mytest"
  7. minSdk Version.MIN_SDK // 注意这里
  8. targetSdk 31
  9. versionCode 1
  10. versionName "1.0"
  11. testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
  12. }
  13. }
  14. dependencies {
  15. implementation Libs.CORE // 注意这里
  16. implementation 'androidx.appcompat:appcompat:1.4.1'
  17. implementation 'com.google.android.material:material:1.5.0'
  18. implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
  19. testImplementation 'junit:junit:4.+'
  20. androidTestImplementation 'androidx.test.ext:junit:1.1.3'
  21. androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
  22. }

附上buildSrc参考链接

用Kotlin DSL构建Android项目

同样也是利用 buildSrc 来管理项目,不同的点是用 Kotlin DSL 代替 Groovy

把所有的 .gradle 文件换成 .kts 文件。把 Groovy 的语法换成 Kotlin 语法

附上Kotlin DSL参考链接