当前位置:   article > 正文

Android Studio 自定义Gradle Plugin

android studio gradle plugin

一、简介

之前公司的一个项目需要用到Gradle插件来修改编译后的class文件,今天有时间就拿出来整理一下,学习一下Gradle插件的编写还是一件十分有意义的事。

二、Gradle插件类型

  • 一种是直接在项目中的gradle文件里编写,这种方式的缺点是无法复用插件代码,在其他项目中还得复制一遍代码(或者说说复制一遍文件)

  • 另一种是在独立的项目里编写插件,然后发布到中央仓库,之后直接引用就可以了,优点就是可复用。

今天我们主要来讲解下第二种。

三、Gradle插件

Gradle插件是使用Groovy进行开发的,而Groovy其实是可以兼容Java的。Android Studio其实除了开发Android App外,完全可以胜任开发Gradle插件这一工作。

在此之前我们先来了解下 Gradle插件 与 Gradle 的关系:

Gradle插件 版本在项目根目录下的 build.gradle 中,如下:

  1. dependencies {
  2. classpath 'com.android.tools.build:gradle:2.3.0'
  3. }

而每个Gradle插件版本号又对应有一个或一些 Gradle发行版本(一般是限定一个最低版本),也就是我们常见的类似gradle-3.3-all.zip这种东西,如下:

distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip

如果这两个版本对应不上了,那你的工程构建的时候就会报错。

具体的对应关系如下:

插件版本所需的 Gradle 版本
1.0.0 - 1.1.32.2.1 - 2.3
1.2.0 - 1.3.12.2.1 - 2.9
1.5.02.2.1 - 2.13
2.0.0 - 2.1.22.10 - 2.13
2.1.3 - 2.2.32.14.1+
2.3.0+3.3+
3.0.0+4.1+
3.1.0+4.4+
3.2.0 - 3.2.14.6+
3.3.0 - 3.3.34.10.1+
3.4.0 - 3.4.35.1.1+
3.5.0 - 3.5.45.4.1+
3.6.0 - 3.6.45.6.4+
4.0.0+6.1.1+
4.1.0+6.5+

详情请见:Android Gradle 插件版本说明

下面来讲讲具体如何开发。


1、创建插件步骤

第一步:新建一个Android工程

第二步:在该工程中新建一个Android Module项目,类型选择Android Library

第三步:将Module里的内容删除,只保留build.gradle文件和src/main目录,同时移除build.gradle文件里的内容

第四步:建立Gradle插件目录

由于gradle是基于groovy,因此,我们开发的gradle插件相当于一个groovy项目。所以需要在main目录下新建groovy目录,这时候groovy文件夹会被Android识别为groovy源码目录。除了在main目录下新建groovy目录外,你还要在main目录下新建resources目录,同理resources目录会被自动识别为资源文件夹。在groovy目录下新建项目包名,就像Java包名那样。resources目录下新建文件夹META-INFMETA-INF文件夹下新建gradle-plugins文件夹。这样,就完成了gradle 插件的项目的整体搭建。目前项目的结构是这样的:

第五步:修改build.gradle文件

内容如下:

  1. apply plugin: 'groovy'
  2. apply plugin: 'maven'
  3. dependencies{
  4. // gradle sdk
  5. compile gradleApi()
  6. // groovy sdk
  7. compile localGroovy()
  8. compile 'com.android.tools.build:gradle:1.5.0'
  9. }
  10. repositories{
  11. mavenCentral()
  12. }

第六步:在com.davisplugins包名下通过new -> file ->创建PluginImpl.groovy文件

内容如下:

  1. package com.davisplugins
  2. import com.android.build.gradle.AppExtension
  3. import org.gradle.api.Plugin
  4. import org.gradle.api.Project
  5. public class PluginImpl implements Plugin<Project>{
  6. void apply(Project project){
  7. System.out.println("========================");
  8. System.out.println("hello gradle plugin!");
  9. System.out.println("========================");
  10. }
  11. }

第七步:定义插件名称

resources/META-INF/gradle-plugins目录下新建一个properties文件,注意该文件的命名就是你使用插件的名字,这里命名为davis.properties,那么你在其他build.gradle文件中使用自定义的插件时候则需写成:

apply plugin: 'davis'

davis.properties文件内容:

implementation-class=com.davisplugins.PluginImpl

注意包名需要替换为你自己的包名。

现在你的目录结构如下:

2、插件发布

前面我们已经自定义好了插件,接下来就是要打包到Maven库里面去了,你可以选择打包到本地,或者是远程服务器中。

(1)打包到本地Maven仓库

在我们自定义Module目录下的build.gradle添加如下代码:

  1. uploadArchives {
  2. repositories {
  3. mavenDeployer {
  4. pom.groupId = 'com.davisplugins'
  5. pom.artifactId = 'davis'
  6. pom.version = 1.0
  7. // maven本地仓库的目录
  8. repository(url: uri('../DavisPlugin'))
  9. }
  10. }
  11. }

这时候,右侧的gradle Toolbar就会在module下多出一个task

点击uploadArchives这个Task,就会在项目下多出一个DavisPlugin目录,里面存着这个gradle插件。

(2)发布到远程Jcenter仓库

内容更新中…

3、插件的使用

我们来看下,发布到本地maven仓库的插件如何使用,在项目根目录下的gradle.build的文件中加入:

  1. buildscript {
  2. repositories {
  3. // maven插件目录
  4. maven{
  5. url uri('DavisPlugin')
  6. }
  7. jcenter()
  8. }
  9. dependencies {
  10. classpath 'com.android.tools.build:gradle:2.1.0'
  11. // 使用自定义插件
  12. classpath 'com.davisplugins:davis:1.0'
  13. }
  14. }
  15. allprojects {
  16. repositories {
  17. jcenter()
  18. }
  19. }
  20. task clean(type: Delete) {
  21. delete rootProject.buildDir
  22. }

app目录下的build.gradle文件中加入:

apply plugin: 'davis'

然后我们就可以使用该插件了,执行一次打包命令看看会发生啥吧!

在打包之前输出了我们打印的日志信息。

4、最佳实践

(1)修改编译后的class文件

我们回到如何修改class文件,首先我们得知道什么时候编译完成,并且我们要赶在class文件被转化为dex文件之前去修改。从1.5.0-beta1开始,android的gradle插件引入了com.android.build.api.transform.Transform,可以点击 http://tools.android.com/tech-docs/new-build-system/transform-api 查看相关内容。Transform每次都是将一个输入进行处理,然后将处理结果输出,而输出的结果将会作为另一个Transform的输入,过程如下:

注意:输出地址不是由你任意指定的。而是根据输入的内容、作用范围等由TransformOutputProvider生成,比如,你要获取输出路径:

  1. String dest = outputProvider.getContentLocation(directoryInput.name,
  2. directoryInput.contentTypes,
  3. directoryInput.scopes,
  4. Format.DIRECTORY)

Transform是一个抽象类,我们先自定义一个Transform,如下:

  1. package com.davisplugins
  2. import com.android.build.api.transform.*
  3. import com.android.build.gradle.internal.pipeline.TransformManager
  4. import org.apache.commons.codec.digest.DigestUtils
  5. import org.apache.commons.io.FileUtils
  6. public class InsertTransform extends Transform {
  7. //设置我们自定义的Transform对应的Task名称
  8. @Override
  9. String getName() {
  10. return "DavisPlugin"
  11. }
  12. //指定输入的类型,通过这里设定,可以指定我们要处理的文件类型
  13. //这样确保其他类型的文件不会传入
  14. @Override
  15. Set<QualifiedContent.ContentType> getInputTypes() {
  16. return TransformManager.CONTENT_CLASS
  17. }
  18. //指定Transfrom的作用范围
  19. @Override
  20. Set<QualifiedContent.Scope> getScopes() {
  21. return TransformManager.SCOPE_FULL_PROJECT
  22. }
  23. @Override
  24. boolean isIncremental() {
  25. return false
  26. }
  27. @Override
  28. void transform(Context context, Collection<TransformInput> inputs,
  29. Collection<TransformInput> referencedInputs,
  30. TransformOutputProvider outputProvider,
  31. boolean isIncremental) throws IOException,
  32. TransformException, InterruptedException {
  33. }
  34. }

看到函数transform,我们还没有具体实现这个函数。这个函数就是具体如何处理输入和输出。可以运行一下看看,注意,这里的运行时直接编译执行我们的apk,而不是像之前那样直接rebuild,因为rebuild并没有执行到编译这一步。由于我们没有实现transform这个函数,导致没有输出!使得整个过程中断了!最终导致apk运行时找不到MainActivity,所以会报错。接下来我们去实现以下这个函数,我们啥也不干,就是把输入内容写入到作为输出内容,不做任何处理:

  1. @Override
  2. void transform(Context context, Collection<TransformInput> inputs,
  3. Collection<TransformInput> referencedInputs,
  4. TransformOutputProvider outputProvider,
  5. boolean isIncremental) throws IOException, TransformException, InterruptedException {
  6. // Transform的inputs有两种类型,一种是目录,一种是jar包,要分开遍历
  7. inputs.each { TransformInput input ->
  8. //对类型为“文件夹”的input进行遍历
  9. input.directoryInputs.each { DirectoryInput directoryInput ->
  10. //文件夹里面包含的是我们手写的类以及R.class、BuildConfig.class以及R$XXX.class等
  11. // 获取output目录
  12. def dest = outputProvider.getContentLocation(directoryInput.name,
  13. directoryInput.contentTypes, directoryInput.scopes,
  14. Format.DIRECTORY)
  15. //这里执行字节码的注入,不操作字节码的话也要将输入路径拷贝到输出路径
  16. FileUtils.copyDirectory(directoryInput.file, dest)
  17. }
  18. //对类型为jar文件的input进行遍历
  19. input.jarInputs.each { JarInput jarInput ->
  20. //jar文件一般是第三方依赖库jar文件
  21. // 重命名输出文件(同目录copyFile会冲突)
  22. def jarName = jarInput.name
  23. def md5Name = DigestUtils.md5Hex(jarInput.file.getAbsolutePath())
  24. if (jarName.endsWith(".jar")) {
  25. jarName = jarName.substring(0, jarName.length() - 4)
  26. }
  27. //生成输出路径 + md5Name
  28. def dest = outputProvider.getContentLocation(jarName + md5Name,
  29. jarInput.contentTypes, jarInput.scopes, Format.JAR)
  30. //这里执行字节码的注入,不操作字节码的话也要将输入路径拷贝到输出路径
  31. FileUtils.copyFile(jarInput.file, dest)
  32. }
  33. }
  34. }

注意input的类型,分为“文件夹”和“jar文件”,”文件夹”里面的就是我们写的类对应的class文件,jar文件一般为第三方库。此时,能成功运行,但是这里我们没有注入任何代码。

Transform类我们实现了,那么如何调用的呢?调用方式如下:

  1. public class PluginImpl implements Plugin<Project>{
  2. void apply(Project project){
  3. def android = project.extensions.findByType(AppExtension);
  4. android.registerTransform(new InsertTransform())
  5. }
  6. }

(2)监控每一个Task任务执行

在我们的工程目录中我们可以看到还有一个TaskListener.groovy类,内容如下:

  1. package com.davisplugins
  2. import org.gradle.BuildListener
  3. import org.gradle.BuildResult
  4. import org.gradle.api.Task
  5. import org.gradle.api.execution.TaskExecutionListener
  6. import org.gradle.api.initialization.Settings
  7. import org.gradle.api.invocation.Gradle
  8. import org.gradle.api.tasks.TaskState
  9. public class TaskListener implements TaskExecutionListener, BuildListener {
  10. private static final String TAG = "[DAVIS] ";
  11. /**
  12. * 此类可以监控每一个task的执行开始和结束,以及工程build的情况
  13. */
  14. public TaskListener(){
  15. }
  16. @Override
  17. void beforeExecute(Task task) {
  18. println(TAG + "task before : " + task.getName())
  19. }
  20. /**
  21. * 比如,我们要在packageRelease这个task任务执行完后,做一些操作,
  22. * 我们就可以在此方法中判断
  23. * @param task
  24. * @param taskState
  25. */
  26. @Override
  27. void afterExecute(Task task, TaskState taskState) {
  28. println(TAG + "task after : " + task.getName())
  29. if(task.getName().equals("packageRelease")){
  30. //做自己的任务
  31. }
  32. }
  33. @Override
  34. void buildFinished(BuildResult result) {
  35. //项目build完成之后,会调用此方法
  36. println(TAG + "build finished.")
  37. }
  38. @Override
  39. void buildStarted(Gradle gradle) {
  40. println(TAG + "build started.")
  41. }
  42. @Override
  43. void projectsEvaluated(Gradle gradle) {
  44. println(TAG + "project evaluated.")
  45. }
  46. @Override
  47. void projectsLoaded(Gradle gradle) {
  48. println(TAG + "project loaded.")
  49. }
  50. @Override
  51. void settingsEvaluated(Settings settings) {
  52. println(TAG + "setting evaluated.")
  53. }
  54. }

调用方式:

  1. public class PluginImpl implements Plugin<Project>{
  2. void apply(Project project){
  3. project.gradle.addListener(new TaskListener())
  4. }
  5. }

这个类是做啥用的呢,此类可以用来监控每一个Task任务的执行情况,比如我们在打apk包的过程中,其实就是调用了一连串的Task任务。下面是我们在未使用插件的情况下打一个release包过程中Gradle Console输出的日志:

  1. Executing tasks: [:app:assembleRelease]
  2. Configuration on demand is an incubating feature.
  3. Incremental java compilation is an incubating feature.
  4. :app:preBuild UP-TO-DATE
  5. :app:preReleaseBuild UP-TO-DATE
  6. :app:checkReleaseManifest
  7. :app:prepareReleaseDependencies
  8. :app:compileReleaseAidl UP-TO-DATE
  9. :app:compileReleaseRenderscript UP-TO-DATE
  10. :app:generateReleaseBuildConfig UP-TO-DATE
  11. :app:mergeReleaseShaders UP-TO-DATE
  12. :app:compileReleaseShaders UP-TO-DATE
  13. :app:generateReleaseAssets UP-TO-DATE
  14. :app:mergeReleaseAssets UP-TO-DATE
  15. :app:generateReleaseResValues UP-TO-DATE
  16. :app:generateReleaseResources UP-TO-DATE
  17. :app:mergeReleaseResources UP-TO-DATE
  18. :app:processReleaseManifest UP-TO-DATE
  19. :app:processReleaseResources UP-TO-DATE
  20. :app:generateReleaseSources UP-TO-DATE
  21. :app:incrementalReleaseJavaCompilationSafeguard UP-TO-DATE
  22. :app:compileReleaseJavaWithJavac UP-TO-DATE
  23. :app:compileReleaseNdk UP-TO-DATE
  24. :app:compileReleaseSources UP-TO-DATE
  25. :app:lintVitalRelease
  26. :app:prePackageMarkerForRelease
  27. :app:transformClassesWithDexForRelease
  28. To run dex in process, the Gradle daemon needs a larger heap.
  29. It currently has approximately 1365 MB.
  30. For faster builds, increase the maximum heap size for the Gradle daemon to more than 2048 MB.
  31. To do this set org.gradle.jvmargs=-Xmx2048M in the project gradle.properties.
  32. For more information see https://docs.gradle.org/current/userguide/build_environment.html
  33. :app:mergeReleaseJniLibFolders UP-TO-DATE
  34. :app:transformNative_libsWithMergeJniLibsForRelease UP-TO-DATE
  35. :app:processReleaseJavaRes UP-TO-DATE
  36. :app:transformResourcesWithMergeJavaResForRelease UP-TO-DATE
  37. :app:validateExternalOverrideSigning
  38. :app:packageRelease UP-TO-DATE
  39. :app:zipalignRelease UP-TO-DATE
  40. :app:assembleRelease
  41. BUILD SUCCESSFUL
  42. Total time: 5.557 secs

那么我们使用了该插件之后输出的日志是怎样的那,如下:

  1. Executing tasks: [:app:assembleRelease]
  2. Configuration on demand is an incubating feature.
  3. Incremental java compilation is an incubating feature.
  4. [DAVIS] project evaluated.
  5. :app:preBuild
  6. [DAVIS] task before : preBuild
  7. :app:preBuild UP-TO-DATE
  8. [DAVIS] task after : preBuild
  9. :app:preReleaseBuild
  10. [DAVIS] task before : preReleaseBuild
  11. :app:preReleaseBuild UP-TO-DATE
  12. [DAVIS] task after : preReleaseBuild
  13. :app:checkReleaseManifest
  14. [DAVIS] task before : checkReleaseManifest
  15. [DAVIS] task after : checkReleaseManifest
  16. :app:prepareReleaseDependencies
  17. [DAVIS] task before : prepareReleaseDependencies
  18. [DAVIS] task after : prepareReleaseDependencies
  19. :app:compileReleaseAidl
  20. [DAVIS] task before : compileReleaseAidl
  21. :app:compileReleaseAidl UP-TO-DATE
  22. [DAVIS] task after : compileReleaseAidl
  23. :app:compileReleaseRenderscript
  24. [DAVIS] task before : compileReleaseRenderscript
  25. :app:compileReleaseRenderscript UP-TO-DATE
  26. [DAVIS] task after : compileReleaseRenderscript
  27. :app:generateReleaseBuildConfig
  28. [DAVIS] task before : generateReleaseBuildConfig
  29. :app:generateReleaseBuildConfig UP-TO-DATE
  30. [DAVIS] task after : generateReleaseBuildConfig
  31. :app:mergeReleaseShaders
  32. [DAVIS] task before : mergeReleaseShaders
  33. :app:mergeReleaseShaders UP-TO-DATE
  34. [DAVIS] task after : mergeReleaseShaders
  35. :app:compileReleaseShaders
  36. [DAVIS] task before : compileReleaseShaders
  37. :app:compileReleaseShaders UP-TO-DATE
  38. [DAVIS] task after : compileReleaseShaders
  39. :app:generateReleaseAssets
  40. [DAVIS] task before : generateReleaseAssets
  41. :app:generateReleaseAssets UP-TO-DATE
  42. [DAVIS] task after : generateReleaseAssets
  43. :app:mergeReleaseAssets
  44. [DAVIS] task before : mergeReleaseAssets
  45. :app:mergeReleaseAssets UP-TO-DATE
  46. [DAVIS] task after : mergeReleaseAssets
  47. :app:generateReleaseResValues
  48. [DAVIS] task before : generateReleaseResValues
  49. :app:generateReleaseResValues UP-TO-DATE
  50. [DAVIS] task after : generateReleaseResValues
  51. :app:generateReleaseResources
  52. [DAVIS] task before : generateReleaseResources
  53. :app:generateReleaseResources UP-TO-DATE
  54. [DAVIS] task after : generateReleaseResources
  55. :app:mergeReleaseResources
  56. [DAVIS] task before : mergeReleaseResources
  57. :app:mergeReleaseResources UP-TO-DATE
  58. [DAVIS] task after : mergeReleaseResources
  59. :app:processReleaseManifest
  60. [DAVIS] task before : processReleaseManifest
  61. :app:processReleaseManifest UP-TO-DATE
  62. [DAVIS] task after : processReleaseManifest
  63. :app:processReleaseResources
  64. [DAVIS] task before : processReleaseResources
  65. :app:processReleaseResources UP-TO-DATE
  66. [DAVIS] task after : processReleaseResources
  67. :app:generateReleaseSources
  68. [DAVIS] task before : generateReleaseSources
  69. :app:generateReleaseSources UP-TO-DATE
  70. [DAVIS] task after : generateReleaseSources
  71. :app:incrementalReleaseJavaCompilationSafeguard
  72. [DAVIS] task before : incrementalReleaseJavaCompilationSafeguard
  73. :app:incrementalReleaseJavaCompilationSafeguard UP-TO-DATE
  74. [DAVIS] task after : incrementalReleaseJavaCompilationSafeguard
  75. :app:compileReleaseJavaWithJavac
  76. [DAVIS] task before : compileReleaseJavaWithJavac
  77. :app:compileReleaseJavaWithJavac UP-TO-DATE
  78. [DAVIS] task after : compileReleaseJavaWithJavac
  79. :app:compileReleaseNdk
  80. [DAVIS] task before : compileReleaseNdk
  81. :app:compileReleaseNdk UP-TO-DATE
  82. [DAVIS] task after : compileReleaseNdk
  83. :app:compileReleaseSources
  84. [DAVIS] task before : compileReleaseSources
  85. :app:compileReleaseSources UP-TO-DATE
  86. [DAVIS] task after : compileReleaseSources
  87. :app:lintVitalRelease
  88. [DAVIS] task before : lintVitalRelease
  89. [DAVIS] task after : lintVitalRelease
  90. :app:prePackageMarkerForRelease
  91. [DAVIS] task before : prePackageMarkerForRelease
  92. [DAVIS] task after : prePackageMarkerForRelease
  93. :app:transformClassesWithDavisPluginForRelease
  94. [DAVIS] task before : transformClassesWithDavisPluginForRelease
  95. :app:transformClassesWithDavisPluginForRelease UP-TO-DATE
  96. [DAVIS] task after : transformClassesWithDavisPluginForRelease
  97. :app:transformClassesWithDexForRelease
  98. [DAVIS] task before : transformClassesWithDexForRelease
  99. To run dex in process, the Gradle daemon needs a larger heap.
  100. It currently has approximately 1365 MB.
  101. For faster builds, increase the maximum heap size for the Gradle daemon to more than 2048 MB.
  102. To do this set org.gradle.jvmargs=-Xmx2048M in the project gradle.properties.
  103. For more information see https://docs.gradle.org/current/userguide/build_environment.html
  104. [DAVIS] task after : transformClassesWithDexForRelease
  105. :app:mergeReleaseJniLibFolders
  106. [DAVIS] task before : mergeReleaseJniLibFolders
  107. :app:mergeReleaseJniLibFolders UP-TO-DATE
  108. [DAVIS] task after : mergeReleaseJniLibFolders
  109. :app:transformNative_libsWithMergeJniLibsForRelease
  110. [DAVIS] task before : transformNative_libsWithMergeJniLibsForRelease
  111. :app:transformNative_libsWithMergeJniLibsForRelease UP-TO-DATE
  112. [DAVIS] task after : transformNative_libsWithMergeJniLibsForRelease
  113. :app:processReleaseJavaRes
  114. [DAVIS] task before : processReleaseJavaRes
  115. :app:processReleaseJavaRes UP-TO-DATE
  116. [DAVIS] task after : processReleaseJavaRes
  117. :app:transformResourcesWithMergeJavaResForRelease
  118. [DAVIS] task before : transformResourcesWithMergeJavaResForRelease
  119. :app:transformResourcesWithMergeJavaResForRelease UP-TO-DATE
  120. [DAVIS] task after : transformResourcesWithMergeJavaResForRelease
  121. :app:validateExternalOverrideSigning
  122. [DAVIS] task before : validateExternalOverrideSigning
  123. [DAVIS] task after : validateExternalOverrideSigning
  124. :app:packageRelease
  125. [DAVIS] task before : packageRelease
  126. :app:packageRelease UP-TO-DATE
  127. [DAVIS] task after : packageRelease
  128. :app:zipalignRelease
  129. [DAVIS] task before : zipalignRelease
  130. :app:zipalignRelease UP-TO-DATE
  131. [DAVIS] task after : zipalignRelease
  132. :app:assembleRelease
  133. [DAVIS] task before : assembleRelease
  134. [DAVIS] task after : assembleRelease
  135. BUILD SUCCESSFUL
  136. Total time: 3.6 secs
  137. [DAVIS] build finished.

从上面的日志我们可以看出,我们可以在项目打包前、某个Task任务执行前或执行后以及整个项目打包完成后来做自己想做的事了。

GitHub源码地址:https://github.com/881205wzs/GradlePluginDemo
源码下载地址:https://download.csdn.net/download/wangzhongshun/11010210

原文鏈接:https://blog.csdn.net/wangzhongshun/article/details/88381058

关注我获取更多知识或者投稿

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家小花儿/article/detail/269828?site
推荐阅读
相关标签
  

闽ICP备14008679号