赞
踩
一种是直接在项目中的gradle文件里编写,这种方式的缺点是无法复用插件代码,在其他项目中还得复制一遍代码(或者说说复制一遍文件)
另一种是在独立的项目里编写插件,然后发布到中央仓库,之后直接引用就可以了,优点就是可复用。
今天我们主要来讲解下第二种。
在此之前我们先来了解下 Gradle插件
与 Gradle
的关系:
Gradle插件
版本在项目根目录下的 build.gradle
中,如下:
dependencies {
classpath 'com.android.tools.build:gradle:2.3.0'
}
而每个Gradle插件版本号又对应有一个或一些 Gradle发行版本(一般是限定一个最低版本),也就是我们常见的类似gradle-3.3-all.zip这种东西,如下:
distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip
如果这两个版本对应不上了,那你的工程构建的时候就会报错。
具体的对应关系如下:
插件版本 | 所需的 Gradle 版本 |
---|---|
1.0.0 - 1.1.3 | 2.2.1 - 2.3 |
1.2.0 - 1.3.1 | 2.2.1 - 2.9 |
1.5.0 | 2.2.1 - 2.13 |
2.0.0 - 2.1.2 | 2.10 - 2.13 |
2.1.3 - 2.2.3 | 2.14.1+ |
2.3.0+ | 3.3+ |
3.0.0+ | 4.1+ |
3.1.0+ | 4.4+ |
3.2.0 - 3.2.1 | 4.6+ |
3.3.0 - 3.3.3 | 4.10.1+ |
3.4.0 - 3.4.3 | 5.1.1+ |
3.5.0 - 3.5.4 | 5.4.1+ |
3.6.0 - 3.6.4 | 5.6.4+ |
4.0.0+ | 6.1.1+ |
4.1.0+ | 6.5+ |
下面来讲讲具体如何开发。
第一步:新建一个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-INF
,META-INF
文件夹下新建gradle-plugins
文件夹。这样,就完成了gradle 插件的项目的整体搭建。目前项目的结构是这样的:
第五步:修改build.gradle文件
内容如下:
apply plugin: 'groovy'
apply plugin: 'maven'
dependencies{
// gradle sdk
compile gradleApi()
// groovy sdk
compile localGroovy()
compile 'com.android.tools.build:gradle:1.5.0'
}
repositories{
mavenCentral()
}
第六步:在com.davisplugins
包名下通过new
-> file
->创建PluginImpl.groovy
文件
内容如下:
package com.davisplugins
import com.android.build.gradle.AppExtension
import org.gradle.api.Plugin
import org.gradle.api.Project
public class PluginImpl implements Plugin<Project>{
void apply(Project project){
System.out.println("========================");
System.out.println("hello gradle plugin!");
System.out.println("========================");
}
}
第七步:定义插件名称
在resources/META-INF/gradle-plugins
目录下新建一个properties
文件,注意该文件的命名就是你使用插件的名字,这里命名为davis.properties
,那么你在其他build.gradle文件中使用自定义的插件时候则需写成:
apply plugin: 'davis'
davis.properties文件内容:
implementation-class=com.davisplugins.PluginImpl
注意包名需要替换为你自己的包名。
现在你的目录结构如下:
前面我们已经自定义好了插件,接下来就是要打包到Maven库里面去了,你可以选择打包到本地,或者是远程服务器中。
在我们自定义Module目录下的build.gradle添加如下代码:
uploadArchives {
repositories {
mavenDeployer {
pom.groupId = 'com.davisplugins'
pom.artifactId = 'davis'
pom.version = 1.0
// maven本地仓库的目录
repository(url: uri('../DavisPlugin'))
}
}
}
这时候,右侧的gradle Toolbar就会在module下多出一个task
点击uploadArchives
这个Task,就会在项目下多出一个DavisPlugin
目录,里面存着这个gradle插件。
内容更新中…
我们来看下,发布到本地maven仓库的插件如何使用,在项目根目录下的gradle.build
的文件中加入:
buildscript { repositories { // maven插件目录 maven{ url uri('DavisPlugin') } jcenter() } dependencies { classpath 'com.android.tools.build:gradle:2.1.0' // 使用自定义插件 classpath 'com.davisplugins:davis:1.0' } } allprojects { repositories { jcenter() } } task clean(type: Delete) { delete rootProject.buildDir }
app目录下的build.gradle文件中加入:
apply plugin: 'davis'
然后我们就可以使用该插件了,执行一次打包命令看看会发生啥吧!
在打包之前输出了我们打印的日志信息。
我们回到如何修改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生成,比如,你要获取输出路径:
String dest = outputProvider.getContentLocation(directoryInput.name,
directoryInput.contentTypes,
directoryInput.scopes,
Format.DIRECTORY)
Transform
是一个抽象类,我们先自定义一个Transform,如下:
package com.davisplugins import com.android.build.api.transform.* import com.android.build.gradle.internal.pipeline.TransformManager import org.apache.commons.codec.digest.DigestUtils import org.apache.commons.io.FileUtils public class InsertTransform extends Transform { //设置我们自定义的Transform对应的Task名称 @Override String getName() { return "DavisPlugin" } //指定输入的类型,通过这里设定,可以指定我们要处理的文件类型 //这样确保其他类型的文件不会传入 @Override Set<QualifiedContent.ContentType> getInputTypes() { return TransformManager.CONTENT_CLASS } //指定Transfrom的作用范围 @Override Set<QualifiedContent.Scope> getScopes() { return TransformManager.SCOPE_FULL_PROJECT } @Override boolean isIncremental() { return false } @Override void transform(Context context, Collection<TransformInput> inputs, Collection<TransformInput> referencedInputs, TransformOutputProvider outputProvider, boolean isIncremental) throws IOException, TransformException, InterruptedException { } }
看到函数transform
,我们还没有具体实现这个函数。这个函数就是具体如何处理输入和输出。可以运行一下看看,注意,这里的运行时直接编译执行我们的apk,而不是像之前那样直接rebuild,因为rebuild并没有执行到编译这一步。由于我们没有实现transform
这个函数,导致没有输出!使得整个过程中断了!最终导致apk运行时找不到MainActivity,所以会报错。接下来我们去实现以下这个函数,我们啥也不干,就是把输入内容写入到作为输出内容,不做任何处理:
@Override void transform(Context context, Collection<TransformInput> inputs, Collection<TransformInput> referencedInputs, TransformOutputProvider outputProvider, boolean isIncremental) throws IOException, TransformException, InterruptedException { // Transform的inputs有两种类型,一种是目录,一种是jar包,要分开遍历 inputs.each { TransformInput input -> //对类型为“文件夹”的input进行遍历 input.directoryInputs.each { DirectoryInput directoryInput -> //文件夹里面包含的是我们手写的类以及R.class、BuildConfig.class以及R$XXX.class等 // 获取output目录 def dest = outputProvider.getContentLocation(directoryInput.name, directoryInput.contentTypes, directoryInput.scopes, Format.DIRECTORY) //这里执行字节码的注入,不操作字节码的话也要将输入路径拷贝到输出路径 FileUtils.copyDirectory(directoryInput.file, dest) } //对类型为jar文件的input进行遍历 input.jarInputs.each { JarInput jarInput -> //jar文件一般是第三方依赖库jar文件 // 重命名输出文件(同目录copyFile会冲突) def jarName = jarInput.name def md5Name = DigestUtils.md5Hex(jarInput.file.getAbsolutePath()) if (jarName.endsWith(".jar")) { jarName = jarName.substring(0, jarName.length() - 4) } //生成输出路径 + md5Name def dest = outputProvider.getContentLocation(jarName + md5Name, jarInput.contentTypes, jarInput.scopes, Format.JAR) //这里执行字节码的注入,不操作字节码的话也要将输入路径拷贝到输出路径 FileUtils.copyFile(jarInput.file, dest) } } }
注意input
的类型,分为“文件夹
”和“jar文件
”,”文件夹
”里面的就是我们写的类对应的class文件
,jar文件一般为第三方库。此时,能成功运行,但是这里我们没有注入任何代码。
Transform类我们实现了,那么如何调用的呢?调用方式如下:
public class PluginImpl implements Plugin<Project>{
void apply(Project project){
def android = project.extensions.findByType(AppExtension);
android.registerTransform(new InsertTransform())
}
}
在我们的工程目录中我们可以看到还有一个TaskListener.groovy类,内容如下:
package com.davisplugins import org.gradle.BuildListener import org.gradle.BuildResult import org.gradle.api.Task import org.gradle.api.execution.TaskExecutionListener import org.gradle.api.initialization.Settings import org.gradle.api.invocation.Gradle import org.gradle.api.tasks.TaskState public class TaskListener implements TaskExecutionListener, BuildListener { private static final String TAG = "[DAVIS] "; /** * 此类可以监控每一个task的执行开始和结束,以及工程build的情况 */ public TaskListener(){ } @Override void beforeExecute(Task task) { println(TAG + "task before : " + task.getName()) } /** * 比如,我们要在packageRelease这个task任务执行完后,做一些操作, * 我们就可以在此方法中判断 * @param task * @param taskState */ @Override void afterExecute(Task task, TaskState taskState) { println(TAG + "task after : " + task.getName()) if(task.getName().equals("packageRelease")){ //做自己的任务 } } @Override void buildFinished(BuildResult result) { //项目build完成之后,会调用此方法 println(TAG + "build finished.") } @Override void buildStarted(Gradle gradle) { println(TAG + "build started.") } @Override void projectsEvaluated(Gradle gradle) { println(TAG + "project evaluated.") } @Override void projectsLoaded(Gradle gradle) { println(TAG + "project loaded.") } @Override void settingsEvaluated(Settings settings) { println(TAG + "setting evaluated.") } }
调用方式:
public class PluginImpl implements Plugin<Project>{
void apply(Project project){
project.gradle.addListener(new TaskListener())
}
}
这个类是做啥用的呢,此类可以用来监控每一个Task任务的执行情况,比如我们在打apk包的过程中,其实就是调用了一连串的Task任务。下面是我们在未使用插件的情况下打一个release包过程中Gradle Console输出的日志:
Executing tasks: [:app:assembleRelease] Configuration on demand is an incubating feature. Incremental java compilation is an incubating feature. :app:preBuild UP-TO-DATE :app:preReleaseBuild UP-TO-DATE :app:checkReleaseManifest :app:prepareReleaseDependencies :app:compileReleaseAidl UP-TO-DATE :app:compileReleaseRenderscript UP-TO-DATE :app:generateReleaseBuildConfig UP-TO-DATE :app:mergeReleaseShaders UP-TO-DATE :app:compileReleaseShaders UP-TO-DATE :app:generateReleaseAssets UP-TO-DATE :app:mergeReleaseAssets UP-TO-DATE :app:generateReleaseResValues UP-TO-DATE :app:generateReleaseResources UP-TO-DATE :app:mergeReleaseResources UP-TO-DATE :app:processReleaseManifest UP-TO-DATE :app:processReleaseResources UP-TO-DATE :app:generateReleaseSources UP-TO-DATE :app:incrementalReleaseJavaCompilationSafeguard UP-TO-DATE :app:compileReleaseJavaWithJavac UP-TO-DATE :app:compileReleaseNdk UP-TO-DATE :app:compileReleaseSources UP-TO-DATE :app:lintVitalRelease :app:prePackageMarkerForRelease :app:transformClassesWithDexForRelease To run dex in process, the Gradle daemon needs a larger heap. It currently has approximately 1365 MB. For faster builds, increase the maximum heap size for the Gradle daemon to more than 2048 MB. To do this set org.gradle.jvmargs=-Xmx2048M in the project gradle.properties. For more information see https://docs.gradle.org/current/userguide/build_environment.html :app:mergeReleaseJniLibFolders UP-TO-DATE :app:transformNative_libsWithMergeJniLibsForRelease UP-TO-DATE :app:processReleaseJavaRes UP-TO-DATE :app:transformResourcesWithMergeJavaResForRelease UP-TO-DATE :app:validateExternalOverrideSigning :app:packageRelease UP-TO-DATE :app:zipalignRelease UP-TO-DATE :app:assembleRelease BUILD SUCCESSFUL Total time: 5.557 secs
那么我们使用了该插件之后输出的日志是怎样的那,如下:
Executing tasks: [:app:assembleRelease] Configuration on demand is an incubating feature. Incremental java compilation is an incubating feature. [DAVIS] project evaluated. :app:preBuild [DAVIS] task before : preBuild :app:preBuild UP-TO-DATE [DAVIS] task after : preBuild :app:preReleaseBuild [DAVIS] task before : preReleaseBuild :app:preReleaseBuild UP-TO-DATE [DAVIS] task after : preReleaseBuild :app:checkReleaseManifest [DAVIS] task before : checkReleaseManifest [DAVIS] task after : checkReleaseManifest :app:prepareReleaseDependencies [DAVIS] task before : prepareReleaseDependencies [DAVIS] task after : prepareReleaseDependencies :app:compileReleaseAidl [DAVIS] task before : compileReleaseAidl :app:compileReleaseAidl UP-TO-DATE [DAVIS] task after : compileReleaseAidl :app:compileReleaseRenderscript [DAVIS] task before : compileReleaseRenderscript :app:compileReleaseRenderscript UP-TO-DATE [DAVIS] task after : compileReleaseRenderscript :app:generateReleaseBuildConfig [DAVIS] task before : generateReleaseBuildConfig :app:generateReleaseBuildConfig UP-TO-DATE [DAVIS] task after : generateReleaseBuildConfig :app:mergeReleaseShaders [DAVIS] task before : mergeReleaseShaders :app:mergeReleaseShaders UP-TO-DATE [DAVIS] task after : mergeReleaseShaders :app:compileReleaseShaders [DAVIS] task before : compileReleaseShaders :app:compileReleaseShaders UP-TO-DATE [DAVIS] task after : compileReleaseShaders :app:generateReleaseAssets [DAVIS] task before : generateReleaseAssets :app:generateReleaseAssets UP-TO-DATE [DAVIS] task after : generateReleaseAssets :app:mergeReleaseAssets [DAVIS] task before : mergeReleaseAssets :app:mergeReleaseAssets UP-TO-DATE [DAVIS] task after : mergeReleaseAssets :app:generateReleaseResValues [DAVIS] task before : generateReleaseResValues :app:generateReleaseResValues UP-TO-DATE [DAVIS] task after : generateReleaseResValues :app:generateReleaseResources [DAVIS] task before : generateReleaseResources :app:generateReleaseResources UP-TO-DATE [DAVIS] task after : generateReleaseResources :app:mergeReleaseResources [DAVIS] task before : mergeReleaseResources :app:mergeReleaseResources UP-TO-DATE [DAVIS] task after : mergeReleaseResources :app:processReleaseManifest [DAVIS] task before : processReleaseManifest :app:processReleaseManifest UP-TO-DATE [DAVIS] task after : processReleaseManifest :app:processReleaseResources [DAVIS] task before : processReleaseResources :app:processReleaseResources UP-TO-DATE [DAVIS] task after : processReleaseResources :app:generateReleaseSources [DAVIS] task before : generateReleaseSources :app:generateReleaseSources UP-TO-DATE [DAVIS] task after : generateReleaseSources :app:incrementalReleaseJavaCompilationSafeguard [DAVIS] task before : incrementalReleaseJavaCompilationSafeguard :app:incrementalReleaseJavaCompilationSafeguard UP-TO-DATE [DAVIS] task after : incrementalReleaseJavaCompilationSafeguard :app:compileReleaseJavaWithJavac [DAVIS] task before : compileReleaseJavaWithJavac :app:compileReleaseJavaWithJavac UP-TO-DATE [DAVIS] task after : compileReleaseJavaWithJavac :app:compileReleaseNdk [DAVIS] task before : compileReleaseNdk :app:compileReleaseNdk UP-TO-DATE [DAVIS] task after : compileReleaseNdk :app:compileReleaseSources [DAVIS] task before : compileReleaseSources :app:compileReleaseSources UP-TO-DATE [DAVIS] task after : compileReleaseSources :app:lintVitalRelease [DAVIS] task before : lintVitalRelease [DAVIS] task after : lintVitalRelease :app:prePackageMarkerForRelease [DAVIS] task before : prePackageMarkerForRelease [DAVIS] task after : prePackageMarkerForRelease :app:transformClassesWithDavisPluginForRelease [DAVIS] task before : transformClassesWithDavisPluginForRelease :app:transformClassesWithDavisPluginForRelease UP-TO-DATE [DAVIS] task after : transformClassesWithDavisPluginForRelease :app:transformClassesWithDexForRelease [DAVIS] task before : transformClassesWithDexForRelease To run dex in process, the Gradle daemon needs a larger heap. It currently has approximately 1365 MB. For faster builds, increase the maximum heap size for the Gradle daemon to more than 2048 MB. To do this set org.gradle.jvmargs=-Xmx2048M in the project gradle.properties. For more information see https://docs.gradle.org/current/userguide/build_environment.html [DAVIS] task after : transformClassesWithDexForRelease :app:mergeReleaseJniLibFolders [DAVIS] task before : mergeReleaseJniLibFolders :app:mergeReleaseJniLibFolders UP-TO-DATE [DAVIS] task after : mergeReleaseJniLibFolders :app:transformNative_libsWithMergeJniLibsForRelease [DAVIS] task before : transformNative_libsWithMergeJniLibsForRelease :app:transformNative_libsWithMergeJniLibsForRelease UP-TO-DATE [DAVIS] task after : transformNative_libsWithMergeJniLibsForRelease :app:processReleaseJavaRes [DAVIS] task before : processReleaseJavaRes :app:processReleaseJavaRes UP-TO-DATE [DAVIS] task after : processReleaseJavaRes :app:transformResourcesWithMergeJavaResForRelease [DAVIS] task before : transformResourcesWithMergeJavaResForRelease :app:transformResourcesWithMergeJavaResForRelease UP-TO-DATE [DAVIS] task after : transformResourcesWithMergeJavaResForRelease :app:validateExternalOverrideSigning [DAVIS] task before : validateExternalOverrideSigning [DAVIS] task after : validateExternalOverrideSigning :app:packageRelease [DAVIS] task before : packageRelease :app:packageRelease UP-TO-DATE [DAVIS] task after : packageRelease :app:zipalignRelease [DAVIS] task before : zipalignRelease :app:zipalignRelease UP-TO-DATE [DAVIS] task after : zipalignRelease :app:assembleRelease [DAVIS] task before : assembleRelease [DAVIS] task after : assembleRelease BUILD SUCCESSFUL Total time: 3.6 secs [DAVIS] build finished.
从上面的日志我们可以看出,我们可以在项目打包前、某个Task任务执行前或执行后以及整个项目打包完成后来做自己想做的事了。
GitHub源码地址:https://github.com/881205wzs/GradlePluginDemo
源码下载地址:https://download.csdn.net/download/wangzhongshun/11010210
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。