本文会介绍如何实现一个Android Gradle Plugin
一、创建Plugin项目
1、 新建new module,类型选择为java library
2、 把main下面的java目录重命名成groovy
3、 在main下新建资源目录:resources->META-INF->gradle-plugins
4、 在gradle-plugins下新建文件**.properties,如com.jeremy.demoplugin.properties,其中com.jeremy.demoplugin为后面要用到的plugin的名字,内容为相应的Plugin类
配置插件短名称,新建文件「src/main/resources/META-INF/gradle-plugins/插件短名称.properties」
implementation-class=com.jeremy.plugin.DemoPlugin
5、 建好Plugin类,如:DemoPlugin.groovy
package com.jeremy.plugin
import org.gradle.api.Plugin
import org.gradle.api.Project
class DemoPlugin implements Plugin<Project> {
@Override
void apply(Project project) {
project.task('test_task') << {
println 'hello, world!'
}
}
}
6、 修改build.gradle
apply plugin: 'groovy'
dependencies {
implementation gradleApi()
implementation localGroovy()
implementation fileTree(dir: 'libs', include: ['*.jar'])
}
最后工程的目录结构如下:
二、将 plugin module 传到本地 maven 仓库
可以验证两个流程:把plugin上传到仓库;在其他module中使用仓库中的plugin
1、 添加 gradle.properties
PROJ_NAME=demoplugin
PROJ_ARTIFACTID=demoplugin
PROJ_POM_NAME=Local Repository
LOCAL_REPO_URL=file:///Users/hailiangliao/Develop/Android/repo/
PROJ_GROUP=com.jeremy
PROJ_VERSION=0.0.1
PROJ_VERSION_CODE=1
PROJ_WEBSITEURL=http://kvh.io
PROJ_ISSUETRACKERURL=https://github.com/kevinho/Embrace-Android-Studio-Demo/issues
PROJ_VCSURL=https://github.com/kevinho/Embrace-Android-Studio-Demo.git
PROJ_DESCRIPTION=demo apps for embracing android studio
PROJ_LICENCE_NAME=The Apache Software License, Version 2.0
PROJ_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0.txt
PROJ_LICENCE_DEST=repo
DEVELOPER_ID=jeremy
DEVELOPER_NAME=jeremy
DEVELOPER_EMAIL=190545925@qq.com
2、 在 build.gradle 添加上传功能
关键参数:
- repository
- groupId
- artifactId
- version
apply plugin: 'groovy'
dependencies {
implementation gradleApi()
implementation localGroovy()
implementation fileTree(dir: 'libs', include: ['*.jar'])
}
sourceCompatibility = "1.7"
targetCompatibility = "1.7"
apply plugin: 'maven'
uploadArchives {
repositories.mavenDeployer {
repository(url: LOCAL_REPO_URL)
pom.groupId = PROJ_GROUP
pom.artifactId = PROJ_ARTIFACTID
pom.version = PROJ_VERSION
}
}
3、 上传通过运行命令:
./gradlew -p plugin/ clean build uploadArchives
4、 如果不想使用maven仓库,而想使用子module的lib,方便调试,可以如下修改:
引入repositories:
flatDir {
dirs './probe-compiler/build/libs'
}
引入dependencies 的时候不要加版本号:
classpath 'com.jeremy.probe:probe-compiler'
三、在 app module 中使用插件
1、 在项目的 buildscript 添加插件作为 classpath
buildscript {
repositories {
maven {
url 'file:///Users/hailiangliao/Develop/Android/repo/'
}
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.1.2'
classpath 'com.jeremy:demoplugin:0.0.1'
}
}
2、 在 app module 中使用插件
apply plugin: 'com.jeremy.demoplugin'
四、实例:com.jeremy.probe
1、module划分
- probe-annotations:定义anotation和aspect
- probe-compiler:定义plugin
- 新建这两个module,probe-annotations类型为Android library;probe-compiler类型为java library
2、probe-annotations
probe-annotations主要实现相关的anotation和切面定义
/**
* Aspect representing the cross cutting-concern: Method and Constructor Tracing.
*/
@Aspect
public class TraceExecuteTimeAspect {
private static final String TAG_TRACE_EXE_TIME = "TraceExecuteTime";
@Pointcut("execution(@com.jeremy.probe_annotations.annotation.TraceExecuteTime * *(..))")
public void methodAnnotatedTraceExecuteTime() {
}
@Pointcut("execution(@com.jeremy.probe_annotations.annotation.TraceExecuteTime *.new(..))")
public void constructorAnnotatedTraceExecuteTime() {
}
@Around("methodAnnotatedTraceExecuteTime() || constructorAnnotatedTraceExecuteTime()")
public Object weaveJoinPointTraceExecuteTime(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
SpendTimeWatcher spendTimeWatcher = new SpendTimeWatcher();
spendTimeWatcher.start();
Object result = joinPoint.proceed();
spendTimeWatcher.stop();
Log.d(TAG_TRACE_EXE_TIME, buildMessageTraceExeTime(methodSignature.getDeclaringType().getName(),
methodSignature.getName(),
spendTimeWatcher.getTotalTimeMillis()));
return result;
}
private static String buildMessageTraceExeTime(String className, String methodName, long methodDuration) {
return new StringBuilder()
.append("Spend time: ")
.append(methodDuration)
.append(" ms")
.append("\n --> ")
.append("during execute method: ")
.append(methodName)
.append("()")
.append("\n --> ")
.append("in class: ")
.append(className)
.toString();
}
}
@Aspect
public class TraceReturnAspect {
private static final String TAG_TRACE_RETURN = "TraceReturn";
@Pointcut("execution(@com.jeremy.probe_annotations.annotation.TraceReturn * *(..))")
public void methodAnnotatedTraceReturn() {
}
@Around("methodAnnotatedTraceReturn()")
public Object weaveJoinPointTraceReturn(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Object result = joinPoint.proceed();
Log.d(TAG_TRACE_RETURN, buildMessageTraceReturn(methodSignature.getDeclaringType().getName(),
methodSignature.getName(),
result));
return result;
}
private static String buildMessageTraceReturn(String className, String methodName, Object returnValue) {
return new StringBuilder()
.append("Return value: ")
.append(returnValue)
.append("\n --> ")
.append("by method: ")
.append(methodName)
.append("()")
.append("\n --> ")
.append("in class: ")
.append(className)
.toString();
}
}
build.gradle:
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'org.aspectj:aspectjtools:1.9.1'
}
}
apply plugin: 'com.android.library'
android {
compileSdkVersion 27
defaultConfig {
minSdkVersion 15
targetSdkVersion 27
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:27.1.1'
implementation 'org.aspectj:aspectjrt:1.9.1'
}
apply plugin: 'maven'
uploadArchives {
repositories.mavenDeployer {
repository(url: LOCAL_REPO_URL)
pom.groupId = PROJ_GROUP
pom.artifactId = PROJ_ARTIFACTID
pom.version = PROJ_VERSION
}
}
import com.android.build.gradle.LibraryPlugin
import org.aspectj.bridge.IMessage
import org.aspectj.bridge.MessageHandler
import org.aspectj.tools.ajc.Main
android.libraryVariants.all { variant ->
LibraryPlugin plugin = project.plugins.getPlugin(LibraryPlugin)
JavaCompile javaCompile = variant.javaCompile
javaCompile.doLast {
String[] args = ["-showWeaveInfo",
"-1.7",
"-inpath", javaCompile.destinationDir.toString(),
"-aspectpath", javaCompile.classpath.asPath,
"-d", javaCompile.destinationDir.toString(),
"-classpath", javaCompile.classpath.asPath,
"-bootclasspath", plugin.project.android.bootClasspath.join(
File.pathSeparator)]
println("execute aspectj args")
MessageHandler handler = new MessageHandler(true);
new Main().run(args, handler)
def log = project.logger
for (IMessage message : handler.getMessages(null, true)) {
switch (message.getKind()) {
case IMessage.ABORT:
case IMessage.ERROR:
case IMessage.FAIL:
log.error message.message, message.thrown
break;
case IMessage.WARNING:
case IMessage.INFO:
log.info message.message, message.thrown
break;
case IMessage.DEBUG:
log.debug message.message, message.thrown
break;
}
}
}
}
需要注意的是,由于定义的Aspect也需要被增强,所以需要在build.gradle中通过相应的代码来实现,当时花了很多时间来查这个问题
3、probe-compiler
定义ProbePlugin:
package com.jeremy.probe_compiler
import org.aspectj.bridge.IMessage
import org.aspectj.bridge.MessageHandler
import org.aspectj.tools.ajc.Main
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.tasks.compile.JavaCompile
class ProbePlugin implements Plugin<Project> {
@Override
void apply(Project project) {
project.android.applicationVariants.all { variant ->
println("hello ProbePlugin")
if (!variant.buildType.isDebuggable()) {
project.logger.debug("Skipping non-debuggable build type '${variant.buildType.name}'.")
return;
}
JavaCompile javaCompile = variant.javaCompile
javaCompile.doLast {
def log = project.logger
String[] args = ["-showWeaveInfo",
"-1.7",
"-inpath", javaCompile.destinationDir.toString(),
"-aspectpath", javaCompile.classpath.asPath,
"-d", javaCompile.destinationDir.toString(),
"-classpath", javaCompile.classpath.asPath,
"-bootclasspath", project.android.bootClasspath.join(File.pathSeparator)]
def argsStr = Arrays.toString(args)
println("begin apply ProbePlugin")
println("execute aspectj args: " + argsStr)
log.debug "ajc args: " + argsStr
MessageHandler handler = new MessageHandler(true);
new Main().run(args, handler);
for (IMessage message : handler.getMessages(null, true)) {
switch (message.getKind()) {
case IMessage.ABORT:
case IMessage.ERROR:
case IMessage.FAIL:
log.error message.message, message.thrown
break;
case IMessage.WARNING:
log.warn message.message, message.thrown
break;
case IMessage.INFO:
log.info message.message, message.thrown
break;
case IMessage.DEBUG:
log.debug message.message, message.thrown
break;
}
}
}
}
}
}
build.gradle:
apply plugin: 'groovy'
dependencies {
implementation gradleApi()
implementation localGroovy()
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'org.aspectj:aspectjtools:1.9.1'
implementation 'org.aspectj:aspectjrt:1.9.1'
}
sourceCompatibility = "1.7"
targetCompatibility = "1.7"
apply plugin: 'maven'
uploadArchives {
repositories.mavenDeployer {
repository(url: LOCAL_REPO_URL)
pom.groupId = PROJ_GROUP
pom.artifactId = PROJ_ARTIFACTID
pom.version = PROJ_VERSION
}
}
五、其他
Q:编写groovy插件的时候没有代码提示,怎么办?
A:在Android module的gradle文件中编写是有代码提示的,可以现在这个文件中编写,写好之后拷到groovy文件中,不知道有没有更好的方法
- 后来发现可以通过在dependency中添加compileOnly ‘com.android.tools.build:gradle:3.1.2’解决