前言

protobufGoogle推出的序列化协议,比json所占的字节更小,序列化更快等特点。本文简单介绍协议特别点然后介绍如何编写一个Gradle插件实现自动编译。

protobuf Github地址
protobuf 语法教程

一个直观的例子:

  val protoBufPerson = AddressBookProtos.Person.newBuilder().setEmail("xxx@qq.com").setId(23).setName("王五").build()val jsonPerson = """{"email":"xxx@qq.com","id":23,"name":"王五"}"""Log.e("MainActivity", "protoBufPerson :${protoBufPerson.serializedSize}  jsonPerson :${jsonPerson.toByteArray().size}")

输出:

 protoBufPerson :22  jsonPerson :46

可见protobuf序列化后是json的数据体的一半左右。具体性能相关可以参考官网。

Tip:移动端可以考虑用lite版本减少生成的类体积

protobuf 使用流程

由于官网有详细的介绍,这里只做简单描述。

1 编辑proto文件

syntax = "proto2";package tutorial;option java_package = "com.example.tutorial";
option java_outer_classname = "AddressBookProtos";message Person {optional string name = 1;optional int32 id = 2;optional string email = 3;}

proto语法是跨语言的,所以我们需要需要对应平台的编译器工具,编译成java文件,比如笔者在MacOs下,需要下载这个平台的编译工具。

2 执行编译工具,将中间语法文件编译成java文件:

protoc --java_out=输出目录  编译文件   -I=编译文件所在的文件夹

3 拷贝生成java文件到工程

为了提高开发效率,大神们早就推出了gradle插件帮我们完成自动编译后自动加入工程目录。

如下将proto文件放入目录即可 直接使用Person.proto编译后产物

开始编写内嵌插件

gradle如果仅仅为本工程使用,可以在当前目录创建一个特殊目录buildSrc,然后在buildSrc目录放置build.gradle文件即可,如果你插件需要额外的配置可以自行在build.gradle添加依赖。Developing Custom Gradle Plugins.

本例在一个Android工程目录下做演示:

├── AndroidProtpPlugin.iml
├── app
│   ├── build.gradle
│   ├── libs
│   ├── proguard-rules.pro
│   └── src
├── build.gradle
├── buildSrc
│   └── build.gradle
├── gradle
│   └── wrapper
├── gradle.properties
├── gradlew
├── gradlew.bat
├── local.properties
└── settings.gradle

基础类编写

首先编辑一个插件类,实现Plugin接口即可

//MyProtoPlugin.java
public class MyProtoPlugin implements Plugin<Project> {//gradle 日志输出工具。参数表示是否输出日志,false表示输出Logger logger = new Logger(false);//插件被应用的时候回调@Overridepublic void apply(Project project) {logger.log("MyProtoPlugin 插件被激活");}
}

然后在app模块下的build.gradle下应用

//build.gradle
apply plugin:MyProtoPlugin

运行./gradlew :app:help

输出:

./gradlew -q :app:help
MyProtoPlugin 插件被激活

获取用户配置proto文件目录

我们插件需要用户告诉需要编译的proto文件位置,所以我们暴露一个DSL给开发者自定义。

public class MyProtoPlugin implements Plugin<Project> {//gradle 日志输出工具。参数表示是否输出日志,false表示输出Logger logger = new Logger(false);@Overridepublic void apply(Project project) {logger.log("MyProtoPlugin 插件被激活");/*** 创建一个插件DSL扩展,第一个参数为DSL名字,第二个为配置类*  比如:*     protoConfig{*             // protoDirPath为MyProtoConfigExtension内部属性*             protoDirPath = "张三"**     }*/project.getExtensions().create("protoConfig", MyProtoConfigExtension.class);//等project配置阶段完毕输出 用户配置project.afterEvaluate(project1 -> {MyProtoConfigExtension configExtension = project1.getExtensions().getByType(MyProtoConfigExtension.class);logger.log("用户配置的目录" + configExtension.protoDirPath);});}
}
public class MyProtoConfigExtension {//要编译的proto文件目录String protoDirPath;public String getProtoDirPath() {return protoDirPath;}public void setProtoDirPath(String protoDirPath) {this.protoDirPath = protoDirPath;}
}

插件应用处修改代码

//build.gradle
apply plugin: MyProtoPlugin
//配置要编译protobuf文件位置
protoConfig {protoDirPath = "src/main/proto"
}

获取用户当前系统对应proto编译器

有的开发者使用Mac,或者linux,我们需要根据此选择一个对应版本的编译器去编译proto文件。
Google发布了一个artifact: com.google.protobuf:protoc
我们看工件(artifact)目录:

我们可以看到这个工件中有很多的编译器版本。但是假设我们只想拿mac的编译器怎么办?我们可以看下protoc-xxx.pom文件
我们可以看到pom提供类分类器(classifier)供我们选择.(classifier是maven的基础知识,不清楚的同学可以看下网上轮子,可以简单理解一个artifact具有 组织名,工件名称,版本号,可选分类器,可选文件后缀ext等)

protoc文件目录

比如我们需要mac下的编译器,依赖写法如下

dependencies {implementation group: 'com.google.protobuf', name: 'protoc', version: '3.14.0',classifier:'osx-x86_64',ext:'exe'
}

上述我们知道了如何从artifact取出对应平台的编译器,那么我们如何判断当前gradle执行环境的操作系统呢?我们可以使用google提供的插件帮助我们

osdetector-gradle-plugin

我们给插件目录buildSrc下的build.gradle添加依赖

repositories {maven {url 'https://mirrors.huaweicloud.com/repository/maven/'}google()jcenter()
}
dependencies {implementation 'com.google.gradle:osdetector-gradle-plugin:1.6.2'
}

继续编辑插件


public class MyProtoPlugin implements Plugin<Project> {//gradle 日志输出工具。参数表示是否输出日志,false表示输出Logger logger = new Logger(false);@Overridepublic void apply(Project project) {//...//利用osdetector得到对应系统分类器OsDetector osDetector = new OsDetector();logger.log("当前操作系统的分类器 " +osDetector.getClassifier());//...}
}

输出

当前操作系统的分类器 osx-x86_64

我们最后获取protoc编译器maven即可

OsDetector osDetector = new OsDetector();logger.log("当前操作系统的分类器 " + osDetector.getClassifier());//这个名字用于创建一个configuration,configuration可以简单理解为管理一组依赖的管理器//implementation 和testImplementation就是其中一个管理器String MycName = "customPluginConfiguration";//创建一个管理器名字为customPluginConfigurationConfiguration configuration = project.getConfigurations().create(MycName);//这个是构造proto的artifact的信息,这些信息可以查看pom文件得知HashMap<String, String> protocArtifactMap = new HashMap<>();protocArtifactMap.put("group", "com.google.protobuf");protocArtifactMap.put("name", "protoc");protocArtifactMap.put("version", "3.14.0");protocArtifactMap.put("classifier", osDetector.getClassifier());protocArtifactMap.put("ext", "exe");//添加依赖到MycName这个管理器中Dependency protoDependency = project.getDependencies().add(MycName, protocArtifactMap);//用管理器返回这个依赖的所有的文件FileCollection files = configuration.fileCollection(protoDependency);//因为这个依赖只会存在一个文件也就是编译器File protoExe = files.getSingleFile();logger.log("获得的平台编译器 " + protoExe.getAbsolutePath());

执行编译protoc文件

上面我们得到信息:

  1. 本地平台的protoc编译器,如果mac版本编译器
  2. 工程proto文件路径

我们编写一个专门编译和输出结果的Task

//CompilerProtoTask.java
public class CompilerProtoTask extends DefaultTask {Logger logger = Logging.getLogger(CompilerProtoTask.class);@InputString protoDir;//输出编译后的文件夹@OutputDirectoryString outGeneratedDir;{outGeneratedDir = getProject().getBuildDir() + "/generated/source/protos/";}@TaskActionvoid action() {OsDetector osDetector = new OsDetector();//这个名字用于创建一个configuration,configuration可以简单理解为管理一组依赖的管理器String MycName = "customPluginConfiguration";//创建一个管理器名字为customPluginConfigurationConfiguration configuration = getProject().getConfigurations().create(MycName);//这个是构造proto的artifact的信息,这些信息可以查看pom文件得知HashMap<String, String> protocArtifactMap = new HashMap<>();protocArtifactMap.put("group", "com.google.protobuf");protocArtifactMap.put("name", "protoc");protocArtifactMap.put("version", "3.14.0");protocArtifactMap.put("classifier", osDetector.getClassifier());protocArtifactMap.put("ext", "exe");//添加依赖到MycName这个管理器中Dependency protoDependency = getProject().getDependencies().add(MycName, protocArtifactMap);//用管理器返回这个依赖的所有的文件FileCollection files = configuration.fileCollection(protoDependency);//因为这个依赖只会存在一个文件也就是编译器File protoExe = files.getFiles().stream().findFirst().get();try {//获得扩展类对象实例,主要用于获取用户配置的proto文件路径String protoDirPath = protoDir;File file1 = new File(getProject().getProjectDir(), protoDirPath);//得到用户配置proto文件目录下的所有后缀为proto的文件String[] extensionFilter = {"proto"};Collection<File> protoDifFile = FileUtils.listFiles(new File(getProject().getProjectDir(), protoDirPath), extensionFilter, false);//拼接命令行字符串StringBuilder cmd = new StringBuilder(protoExe.getAbsolutePath() + " ");File outFile = new File(outGeneratedDir);if (!outFile.exists()) {outFile.mkdirs();}cmd.append("--java_out=" + outGeneratedDir);for (File file : protoDifFile) {String replaceFilePath = " " + file.getPath().replaceFirst(file1.getAbsolutePath() + "/", "") + " ";cmd.append(replaceFilePath);}cmd.append(" -I" + protoDirPath + " ");logger.info("运行编译命令 " + cmd);//防止编译器无权限运行if (!protoExe.canExecute() && !protoExe.setExecutable(true)) {throw new GradleException("protoc编译器无法执行");}//执行命令行Process exec = null;try {String[] strings = new String[0];exec = Runtime.getRuntime().exec(cmd.toString(), strings, getProject().getProjectDir());int resultCode = exec.waitFor();//执行成功if (resultCode == 0) {} else {throw new GradleException("编译proto文件错误" + IOUtils.toString(exec.getErrorStream()));}} finally {if (exec != null) {exec.destroy();}}} catch (Exception e) {e.printStackTrace();}}
}

简单来说上面的Task就是得到编译器然后执行shell命令编译出源文件.

我们将上面的Task 整合到插件中

public class MyProtoPlugin implements Plugin<Project> {//gradle 日志输出工具。参数表示是否输出日志,false表示输出Logger logger = new Logger(false);@Overridepublic void apply(Project project) {logger.log("MyProtoPlugin 插件被激活");/*** 创建一个插件DSL扩展,第一个参数为DSL名字,第二个为配置类*  比如:*     protoConfig{*             // protoDirPath为MyProtoConfigExtension内部属性*             protoDirPath = "张三"**     }*/project.getExtensions().create("protoConfig", MyProtoConfigExtension.class);//在evaluate之后添加task到gradle中project.afterEvaluate(new Action<Project>() {@Overridepublic void execute(Project project) {/*** 创建任务并设置输出目录*/CompilerProtoTask compilerProto = project.getTasks().create("compilerProto", CompilerProtoTask.class);
//                compilerProto.onlyIf(new );compilerProto.setGroup("proto");MyProtoConfigExtension myProtoConfigExtension = project.getExtensions().getByType(MyProtoConfigExtension.class);compilerProto.protoDir = myProtoConfigExtension.protoDirPath;}});}
}

执行如下命令会在build/granerated/source/protos生成java文件

./gradlew compilerProto


虽然插件生成源文件,但是java编译器不知道这个文件要被打包到工程中。比如生成了A.java,我们想要gradle自动帮我编译这个文件并最后打包到jar或者apk中.

关联插件生成类到编译路径中

我们上面生成了类文件,但是还没有关联到编译路径中,也就是我们生成的类不会打包到Jar中或者Android的apk中.

我们需要知道当前是java工程还是Android工程.

Android工程处理源码关联

对于Android的工程来说会引用AGP无非两种:

  1. apply plugin: 'com.android.application'
  2. apply plugin: 'com.android.library'

前者是Apk应用,后者是Android 类库.

我们在插件buildSrc下的build.gradle添加Android插件依赖:

dependencies {//...implementation  'com.android.tools.build:gradle:4.2.0-alpha16'//..
}

于是乎我们判断应用插件的工程是否为Android的逻辑如下:

 //是Android工程boolean isAndroidProject(Project project) {return project.getPlugins().hasPlugin(com.android.build.gradle.AppPlugin.class) || project.getPlugins().hasPlugin(com.android.build.gradle.LibraryPlugin.class);}

apply plugin: 'com.android.application'应用的是AppPlugin
apply plugin: 'com.android.library'应用的是LibraryPlugin

关于为什么一个字符串可以关联到某个插件类,我们这里简单讲下,插件开发者长传的时候会配置如下内容到gradle中

gradlePlugin {plugins {create("simplePlugin") {id = "org.samples.greeting"implementationClass = "org.gradle.GreetingPlugin"}}
}

这个插件配置会在发布的时候生成一个数据位于
src / main / resources / META-INF / gradle-plugins / org.samples.greeting.properties
内容如下

implementation-class=org.gradle.GreetingPlugin

上面我们知道如何判断Android工程和Java工程(不是Android工程那就是Java工程),但是还没告诉Android插件将我们的生成proto目录加入编译路径中,但是AGP提供了对应的函数方便我们操作。

class BaseVariant{void registerJavaGeneratingTask(@NonNull Task task, @NonNull File... sourceFolders);
}

本文为了防止读者读Android Gradle插件不熟悉这里解释说明下构建变体和风味:

如下案例:

android{//构建类型buildTypes {release {}debug {}}//风味维度flavorDimensions "version", "hha"//产品风味productFlavors {demo {dimension "version"applicationIdSuffix ".demo"versionNameSuffix "-demo"}full {dimension "version"applicationIdSuffix ".full"versionNameSuffix "-full"}nnimei {dimension "hha"applicationIdSuffix ".full"versionNameSuffix "-full"}}
}

上面会将不同维度的产品风味组合在一起,最后组合到构建类型。

如上案例:两个维度version, hha组合产品风味,就有两种结果分别为:

1. fullnnimei
2. demonnimei

结合构建类型

1.fullnnimeiDebug
2.demonnimeiDebug
3.fullnnimeiRelease
4.demonnimeiRelease

这样便生成了四种最终变体。

但是还有一种类型变体叫做测试环境变体,也就是在单元测试的时候使用的变体,而测试分两种一种Android 的测试和一种Junit的本地测试。

Android 的测试默认情况只会生成Debug构建类型的变体

 1. demoNnimeiDebugAndroidTest2. fullNnimeiDebugAndroidTest

如果你想添加/输出/管理 测试依赖 可以用testVariants

android{testVariants.all { variant->//可在此配置变体的信息println "变体 ${variant.name}"}
}

输出:

变体 demoNnimeiDebugAndroidTest
变体 fullNnimeiDebugAndroidTest

Junit则会生成全部的构建类型变体,你可以用unitTestVariants管理/添加/输出:

unitTestVariants.all{variant->//可在此配置变体的信息println "unitTestVariants 变体 ${variant.name}"}

输出:

unitTestVariants 变体 demoNnimeiDebugUnitTest
unitTestVariants 变体 fullNnimeiDebugUnitTest
unitTestVariants 变体 demoNnimeiReleaseUnitTest
unitTestVariants 变体 fullNnimeiReleaseUnitTest

综上我们知道变体有三种:

  1. 开发变体
  2. Android测试变体
  3. Junit测试变体

Android Gradle变体提供的所有功能读者可自行参阅文档。我们只需要知道现在他提供了添加registerJavaGeneratingTask帮助我们完成关联类路径。

回过头来我们看下这个函数

class BaseVariant{void registerJavaGeneratingTask(@NonNull Task task, @NonNull File... sourceFolders);
}

BaseVariant表示某个变体如demoNnimeiDebug等。

registerJavaGeneratingTask关联一个task,编译时会自动运行这个任务,sourceFolders文件下的所有java/kotlin文件会自动加入编译路径

         //如果当前是android工程链接生成的源码路径到编译路径if (isAndroidProject(project)) {linkAndroidProject(project);} else {//是一个java工程}void linkAndroidProject(Project project) {if (project.getPlugins().hasPlugin(com.android.build.gradle.AppPlugin.class)) {//当前是Android 应用工程//Android 插件提供了扩展// 也就是我们经常的写法////  android{////  }//AppExtension extension = (AppExtension) (project.getExtensions().getByName("android"));extension.getApplicationVariants().all(configurationAndroidVariant(project));extension.getTestVariants().all(configurationAndroidVariant(project));extension.getUnitTestVariants().all(configurationAndroidVariant(project));extension.getApplicationVariants().all(new Action<ApplicationVariant>() {@Overridepublic void execute(ApplicationVariant applicationVariant) {System.out.println("Android 正式环境变体  "+applicationVariant.getName() );}});extension.getTestVariants().all(new Action<TestVariant>() {@Overridepublic void execute(TestVariant testVariant) {System.out.println("Android 测试环境变体 "+testVariant.getName() );}});} else {//当前是Android lib工程LibraryExtension extension = (LibraryExtension) (project.getExtensions().getByName("android"));extension.getLibraryVariants().all(configurationAndroidVariant(project));extension.getLibraryVariants().all(configurationAndroidVariant(project));extension.getUnitTestVariants().all(configurationAndroidVariant(project));}}
private Action<BaseVariant> configurationAndroidVariant(Project project) {return new Action<BaseVariant>() {@Overridepublic void execute(BaseVariant libraryVariant) {CompilerProtoTask compilerProto = (CompilerProtoTask) project.getTasks().getByName("compilerProto");//applicationVariant.addJavaSourceFoldersToModel();libraryVariant.registerJavaGeneratingTask(compilerProto, new File(compilerProto.outGeneratedDir));}};}

Java工程处理源码关联

相对Android工程,Java工程概念就要少很多。
主要是SourceSet的概念。
SourceSet可以简单理解为Java工程目录管理器,比如如下配置:

sourceSets {//自定义的一个源集合mySource {java.srcDir("src/myjava")resources.srcDir("my/res")}//系统默认提供main {}
}

系统默认定义了默认的SourceSet叫做main.默认的java文件目录为src/main/java,资源目录为src/main/resouce.每个SourceSet之间源码不可以相互访问

下面的代码就会抛出找不到符号错误

public class MainJava {public static void main(String[] args) {//不能访问另一个SourceSet代码MyJavaClass myJavaClass = new MyJavaClass();}
}

java插件在SourceSet中提供了大量的Task帮助我编译某个SourceSet代码。

当然在默认情况我们运行Jar命令只会打包main的java文件,而不会打包其他SourceSet的代码。
比如下面运行
./gradlew jar
得到一个jar压缩包,内容如下:

没有mysourceJar的任何资源。但是如果你希望打包非main资源请自定义如下任务。

tasks.create("mysourceJar", Jar) {from(sourceSets.mySource.output)
}

每个SourceSet都提供非常多的属性帮助我们使用,如上output表示这个SourceSet编译后输出的class文件和资源文件目录。然后我们创建一个已有的Jar任务扩展即可.关于自定任务可以参阅官网。

JavaGradle插件说明

我们现在了解了Java插件的基础知识后,回到我们的主题,如何把protoc输出目录加入编译路径上?
我们只需要将我们的编译输出proto文件的输出目录加入sourceSet即可.

 void linkJavaProject(Project project) {SourceSetContainer container = project.getExtensions().getByType(SourceSetContainer.class);//遍历所有源集合 比如main等for (SourceSet sourceSet : container) {//getCompileTaskName用于获取这个sourceSet提供的编译java文件的task//比如我我有一个SourceSet名为MySource,那么编译任务名称为compileMySourceJava//利用这个函数我们快速拿到这个Task的名称String compileName = sourceSet.getCompileTaskName("java");//获取这个Task实例JavaCompile javaCompile = (JavaCompile) project.getTasks().getByName(compileName);//执行ava编译的时候,先执行编译proto文件任务CompilerProtoTask compilerProto = (CompilerProtoTask) project.getTasks().getByName("compilerProto");javaCompile.dependsOn(compilerProto);//proto任务的输出目录附加到sourceSet中sourceSet.getJava().srcDirs(compilerProto.outGeneratedDir);}}

总结

我们回顾下整个插件开发流程,我们构造了三个类:

CompilerProtoTask 用于负责编译整个proto文件
MyProtoConfigExtension是一个让用户可以gradle中自定配置proto文件位置的类
MyProtoPlugin负责将CompilerProtoTask输出目录加入的工程编译路径中,让其打包的时候可以寻找到。

//CompilerProtoTask.java
public class CompilerProtoTask extends DefaultTask {Logger logger = Logging.getLogger(CompilerProtoTask.class);@InputString protoDir;@OutputDirectoryString outGeneratedDir;{outGeneratedDir = getProject().getBuildDir() + "/generated/source/protos/";}@TaskActionvoid action() {OsDetector osDetector = new OsDetector();//这个名字用于创建一个configuration,configuration可以简单理解为管理一组依赖的管理器String MycName = "customPluginConfiguration";//创建一个管理器名字为customPluginConfigurationConfiguration configuration = getProject().getConfigurations().create(MycName);//这个是构造proto的artifact的信息,这些信息可以查看pom文件得知HashMap<String, String> protocArtifactMap = new HashMap<>();protocArtifactMap.put("group", "com.google.protobuf");protocArtifactMap.put("name", "protoc");protocArtifactMap.put("version", "3.14.0");protocArtifactMap.put("classifier", osDetector.getClassifier());protocArtifactMap.put("ext", "exe");//添加依赖到MycName这个管理器中Dependency protoDependency = getProject().getDependencies().add(MycName, protocArtifactMap);//用管理器返回这个依赖的所有的文件FileCollection files = configuration.fileCollection(protoDependency);//因为这个依赖只会存在一个文件也就是编译器File protoExe = files.getFiles().stream().findFirst().get();try {//获得扩展类对象实例,主要用于获取用户配置的proto文件路径String protoDirPath = protoDir;File file1 = new File(getProject().getProjectDir(), protoDirPath);//得到用户配置proto文件目录下的所有后缀为proto的文件String[] extensionFilter = {"proto"};Collection<File> protoDifFile = FileUtils.listFiles(new File(getProject().getProjectDir(), protoDirPath), extensionFilter, false);//拼接命令行字符串StringBuilder cmd = new StringBuilder(protoExe.getAbsolutePath() + " ");File outFile = new File(outGeneratedDir);if (!outFile.exists()) {outFile.mkdirs();}cmd.append("--java_out=" + outGeneratedDir);for (File file : protoDifFile) {String replaceFilePath = " " + file.getPath().replaceFirst(file1.getAbsolutePath() + "/", "") + " ";cmd.append(replaceFilePath);}cmd.append(" -I" + protoDirPath + " ");logger.info("运行编译命令 " + cmd);//防止编译器无权限运行if (!protoExe.canExecute() && !protoExe.setExecutable(true)) {throw new GradleException("protoc编译器无法执行");}//执行命令行Process exec = null;try {String[] strings = new String[0];exec = Runtime.getRuntime().exec(cmd.toString(), strings, getProject().getProjectDir());int resultCode = exec.waitFor();//执行成功if (resultCode == 0) {} else {throw new GradleException("编译proto文件错误" + IOUtils.toString(exec.getErrorStream()));}} finally {if (exec != null) {exec.destroy();}}} catch (Exception e) {e.printStackTrace();}}}
//MyProtoConfigExtension.java
public class MyProtoConfigExtension {//要编译的proto文件目录String protoDirPath;public String getProtoDirPath() {return protoDirPath;}public void setProtoDirPath(String protoDirPath) {this.protoDirPath = protoDirPath;}
}
//MyProtoPlugin.java
public class MyProtoPlugin implements Plugin<Project> {//gradle 日志输出工具。参数表示是否输出日志,false表示输出Logger logger = new Logger(false);@Overridepublic void apply(Project project) {logger.log("MyProtoPlugin 插件被激活");/*** 创建一个插件DSL扩展,第一个参数为DSL名字,第二个为配置类*  比如:*     protoConfig{*             // protoDirPath为MyProtoConfigExtension内部属性*             protoDirPath = "张三"**     }*/project.getExtensions().create("protoConfig", MyProtoConfigExtension.class);project.afterEvaluate(new Action<Project>() {@Overridepublic void execute(Project project) {/*** 创建任务并设置输出目录*/CompilerProtoTask compilerProto = project.getTasks().create("compilerProto", CompilerProtoTask.class);
//                compilerProto.onlyIf(new );compilerProto.setGroup("proto");MyProtoConfigExtension myProtoConfigExtension = project.getExtensions().getByType(MyProtoConfigExtension.class);compilerProto.protoDir = myProtoConfigExtension.protoDirPath;//如果当前是android工程链接生成的源码路径到编译路径if (isAndroidProject(project)) {linkAndroidProject(project);} else {linkJavaProject(project);}}});}void linkAndroidProject(Project project) {if (project.getPlugins().hasPlugin(com.android.build.gradle.AppPlugin.class)) {//当前是Android 应用工程//Android 插件提供了扩展// 也就是我们经常的写法////  android{////  }//AppExtension extension = (AppExtension) (project.getExtensions().getByName("android"));extension.getApplicationVariants().all(configurationAndroidVariant(project));extension.getTestVariants().all(configurationAndroidVariant(project));extension.getUnitTestVariants().all(configurationAndroidVariant(project));extension.getApplicationVariants().all(new Action<ApplicationVariant>() {@Overridepublic void execute(ApplicationVariant applicationVariant) {System.out.println("Android 正式环境变体  "+applicationVariant.getName() );}});extension.getTestVariants().all(new Action<TestVariant>() {@Overridepublic void execute(TestVariant testVariant) {System.out.println("Android 测试环境变体 "+testVariant.getName() );}});} else {//当前是Android lib工程LibraryExtension extension = (LibraryExtension) (project.getExtensions().getByName("android"));extension.getLibraryVariants().all(configurationAndroidVariant(project));extension.getLibraryVariants().all(configurationAndroidVariant(project));extension.getUnitTestVariants().all(configurationAndroidVariant(project));}}private Action<BaseVariant> configurationAndroidVariant(Project project) {return new Action<BaseVariant>() {@Overridepublic void execute(BaseVariant libraryVariant) {CompilerProtoTask compilerProto = (CompilerProtoTask) project.getTasks().getByName("compilerProto");//applicationVariant.addJavaSourceFoldersToModel();libraryVariant.registerJavaGeneratingTask(compilerProto, new File(compilerProto.outGeneratedDir));}};}void linkJavaProject(Project project) {SourceSetContainer container = project.getExtensions().getByType(SourceSetContainer.class);//遍历所有源集合 比如main等for (SourceSet sourceSet : container) {//getCompileTaskName用于获取这个sourceSet提供的编译java文件的task//比如我我有一个SourceSet名为MySource,那么编译任务名称为compileMySourceJava//利用这个函数我们快速拿到这个Task的名称String compileName = sourceSet.getCompileTaskName("java");//获取这个Task实例JavaCompile javaCompile = (JavaCompile) project.getTasks().getByName(compileName);//执行ava编译的时候,先执行编译proto文件任务CompilerProtoTask compilerProto = (CompilerProtoTask) project.getTasks().getByName("compilerProto");javaCompile.dependsOn(compilerProto);//proto任务的输出目录附加到sourceSet中sourceSet.getJava().srcDirs(compilerProto.outGeneratedDir);}}//是Android工程boolean isAndroidProject(Project project) {return project.getPlugins().hasPlugin(com.android.build.gradle.AppPlugin.class) || project.getPlugins().hasPlugin(com.android.build.gradle.LibraryPlugin.class);}
}

源码地址:

本案例源码地址

参考

Android测试相关文档

Android变体说明文档

AGP源码和文档

Java插件文档

Gradle configuration相关文档

JavaGradle插件说明

Gradle插件 protobuf自动编译相关推荐

  1. 【Android Protobuf 序列化】Protobuf 使用 ( protobuf-gradle-plugin 插件简介 | Android Studio 中配置插件 | AS 中编译源文件 )

    文章目录 一.protobuf-gradle-plugin 插件简介 二.Android Studio 中配置 protobuf-gradle-plugin 插件 三.Android Studio 中 ...

  2. Android Gradle 插件版本说明

    Android Studio 构建系统以 Gradle 为基础,并且 Android Gradle 插件添加了几项专用于构建 Android 应用的功能.虽然 Android 插件通常会与 Andro ...

  3. 通过自定义Gradle插件修改编译后的class文件

    我的简书同步发布:通过自定义Gradle插件修改编译后的class文件 转载请注明出处:[huachao1001的专栏:http://blog.csdn.net/huachao1001] 或许你会觉得 ...

  4. SpringBoot------添加保存时自动编译插件

    1.右键Java项目2.选择"Spring Tools" 3.选择"Add Boot DevTools" 4.每次使用Ctrl + S键时就会自动编译了 实际上 ...

  5. Android Apk瘦身方案2——gradle插件将png自动压缩为webp

    实现思路 在 mergeRes 和 processRes 任务之间插入 WebP 压缩任务,如下图所示: 使用开源框架Cwebp,使用命令行对所有的图片进行遍历处理,然后将结果输出 Google 官方 ...

  6. 小记Android Gradle插件 Iven 记于 20190522 药厂

    2016.04年 Android Gradle插件2.x 通过支持字节码注入,将代码和资源更新推送到模拟器或物理设备上正在运行的应用程序,启用Instant Run. 添加了对增量构建的支持,即使应用 ...

  7. [Android]上传到多个Maven仓库的Gradle插件RapidMavenPushPlugin

    博客搬迁至https://blog.wangjiegulu.com RSS订阅:https://blog.wangjiegulu.com/feed.xml RapidMavenPushPlugin 用 ...

  8. replugin源码解析之replugin-host-gradle(宿主的gradle插件)

    前言 replugin-host-gradle 是 RePlugin 插件框架中的宿主gradle插件,主要用于在宿主应用的编译期常规构建任务流中,插入一些定制化的构建任务,以便实现自动化编译期修改宿 ...

  9. Android如何自定义Gradle插件

    Android-如何自定义gradle插件 自定义gradle插件可以实现定制自己的构建流程,以达到复用目的: ##1. 自定义插件方式 自定义插件有三种方式 添加脚步 在你的app项目的build. ...

  10. Eclipse 安装Gradle插件

    http://www.cnblogs.com/simoncook/archive/2013/02/28/2937939.html 一.介绍 1. Gradle 是什么 Gradle 官方对其描述是:能 ...

最新文章

  1. 【jquery】jquery选择器
  2. linux下如何查看程序写入内存数据_linux到底如何正确关机
  3. 比特币黄金首遭“51%攻击”,可能动摇数字货币世界的根基
  4. 什么是尾递归?测试python尾递归
  5. c++函数模板(c++细节篇十)
  6. php html url编码,html中url编码是什么?有什么用?
  7. 多项式乘法:练习总结
  8. 不同.net版本实现单点登录
  9. java服务端集成极光消息推送--详细开发步骤
  10. 把文化全交给HR,是管理者最大的过失
  11. Bilateral Filters(双边滤波算法)原理及实现
  12. mysql的时间函数_MySQL常用时间函数
  13. STEP 7新建梯形图程序,S7-plcsim使用
  14. 八 Spring Security Oauth2 单点登录 第三方授权(QQ、微信登录)
  15. 怎么学好html5和css3,如何提高你的CSS水平
  16. Oracle 安装步骤
  17. 多项式除以多项式例题讲解_多项式乘以多项式训练题.doc
  18. 在本地安装使用-LTP
  19. 知乎热议:替代 Matlab 的国产软件出现,开发商称半年内实现 Matlab 功能的70%
  20. python中show函数_Pycharm(Python)下imshow函数显示问题的解决方法

热门文章

  1. android 连接电视,手机连接电视方法大全
  2. caj怎么转word文档
  3. staruml 试用_浅析几款主流的UML建模工具
  4. Java软件开发基础入门之工作流
  5. 三十年中国GIS基础软件市场回顾与发展展望
  6. 进华为你必须了解的——华为精神
  7. 360度全方位超详尽iPhone5s新手入门宝典(上)
  8. win7怎么把计算机放到桌面6,手机投屏到电脑win7最简单具体操作步骤
  9. 基于WinForm开发的Ribbon界面案例的扩展
  10. 域控-笔记四(综合应用)