Gradle 是将软件编译、测试、部署等步骤联系在一块儿自动化构建工具。html
对于Android开发人员已经了解build.gradle 的 android{} 和 dependencies{} ,可是他的编译过程是什么样的?这个过程当中能够干些什么事了解吗?java
此文是学习Gradle时的学习笔记,让你从新认识Gradle,让Gradle加快并提效构建你的项目。此时分享给你们,与你们共勉linux
Gradle 最基础的一个项目配置android
Groovy 基础语法 并解释 apply plugin: 'xxxx'和dependencies{}git
Gradle Project/Task 并自定义Task和Plugingithub
自定义一个重命名APP名的插件 流程json
APT 技术- Java AbstractProcessorwindows
Android 字节码加强技术 - Transform (Android 中使用字节码加强技术)api
文章内容略长,若是你已经掌握Gradle基础知识,能够直接经过目录查看你想看的内容,回顾或者学习都还不错。bash
gralde的项目配置是先识别 settings.gradle,而后在配置各个build.gradle.
为了说明构建执行顺序,在上述最基础的gradle项目结构里面设置了对应的代码
// settings.gradle
println "settings.gradle start"
include ':app'
println "settings.gradle end"
复制代码
//root build.gradle
println "project.root start"
buildscript {
repositories {
}
dependencies {
}
}
allprojects {
}
println "project.root end"
复制代码
//app build.gradle
println "project.app start"
project.afterEvaluate {
println "project.app.afterEvaluate print"
}
project.beforeEvaluate {
println "project.app.beforeEvaluate print"
}
println "project.app end"
复制代码
若是是mac/linux,执行./gradlew 获得以下结果:
settings.gradle start
settings.gradle end
> Configure project :
project.root start
project.root end
> Configure project :app
project.app start
project.app end
project.app.afterEvaluate print
复制代码
下面讲一些关于groovy的语法,能够打开Android Studio Tools-> Groovy Console练习Groovy 语法 ,以下
int vs = 1
def version = 'version1'
println(vs)
println(version)
复制代码
println vs
println version
复制代码
def s1 = 'aaa'
def s2 = "version is ${version}"
def s3 = ''' str is many '''
println s1
println s2
println s3
复制代码
def list = ['ant','maven']
list << "gradle"
list.add('test')
println list.size()
println list.toString()
//map
def years = ['key1':1000,"key2":2000]
println years.key1
println years.getClass()
复制代码
输出结果
[ant, maven, gradle, test]
1000
class java.util.LinkedHashMap
复制代码
groovy语法中支持闭包语法,闭包简单的说就是代码块,以下:
def v = {
v -> println v
}
static def testMethod(Closure closure){
closure('闭包 test')
}
testMethod v
复制代码
其中定义的v就为闭包,testMethod 为一个方法,传入参数为闭包,而后调用闭包.
咱们先把子项目的build.gradle改成以下形式
apply plugin: 'java-library'
repositories {
mavenLocal()
}
dependencies {
compile gradleApi()
}
复制代码
这样,咱们就能够直接看gradle的源码了,在External Libraries里以下
进入build.gradle 点击apply 会进入到gradle的源码,能够看到
//PluginAware
/** * Applies a plugin or script, using the given options provided as a map. Does nothing if the plugin has already been applied. * <p> * The given map is applied as a series of method calls to a newly created {@link ObjectConfigurationAction}. * That is, each key in the map is expected to be the name of a method {@link ObjectConfigurationAction} and the value to be compatible arguments to that method. * * <p>The following options are available:</p> * * <ul><li>{@code from}: A script to apply. Accepts any path supported by {@link org.gradle.api.Project#uri(Object)}.</li> * * <li>{@code plugin}: The id or implementation class of the plugin to apply.</li> * * <li>{@code to}: The target delegate object or objects. The default is this plugin aware object. Use this to configure objects other than this object.</li></ul> * * @param options the options to use to configure and {@link ObjectConfigurationAction} before “executing” it */
void apply(Map<String, ?> options);
复制代码
用Groovy 语法很清楚的解释,apply其实就是一个方法,后面传递的就是一个map,其中plugin为key.
那么dependencies{}也是同样
//Project
/** * <p>Configures the dependencies for this project. * * <p>This method executes the given closure against the {@link DependencyHandler} for this project. The {@link * DependencyHandler} is passed to the closure as the closure's delegate. * * <h3>Examples:</h3> * See docs for {@link DependencyHandler} * * @param configureClosure the closure to use to configure the dependencies. */
void dependencies(Closure configureClosure);
复制代码
dependencies是一个方法 后面传递的是一个闭包的参数.
问题:思考那么android {}也是同样的实现吗? 后面讲解
在前面章节中提到gralde初始化配置,是先解析并执行setting.gradle,而后在解析执行build.gradle,那么其实这些build.gradle 就是Project,外层的build.gradle是根Project,内层的为子project,根project只能有一个,子project能够有多个.
咱们知道了最基础的gradle配置,那么怎么来使用Gradle里面的一些东西来为咱们服务呢?
前面提到apply plugin:'xxxx',这些plugin都是按照gradle规范来实现的,有java的有Android的,那么咱们来实现一个本身的plugin.
把build.gradle 改成以下代码
//app build.gradle
class LibPlugin implements Plugin<Project>{
@Override
void apply(Project target) {
println 'this is lib plugin'
}
}
apply plugin:LibPlugin
复制代码
运行./gradlew 结果以下
> Configure project :app
this is lib plugin
复制代码
咱们在自定义的Plugin中要获取Project的配置,能够经过Project去获取一些基本配置信息,那咱们要自定义的一些属性怎么去配置获取呢,这时就须要建立Extension了,把上述代码改成以下形式。
//app build.gradle
class LibExtension{
String version
String message
}
class LibPlugin implements Plugin<Project>{
@Override
void apply(Project target) {
println 'this is lib plugin'
//建立 Extension
target.extensions.create('libConfig',LibExtension)
//建立一个task
target.tasks.create('libTask',{
doLast{
LibExtension config = project.libConfig
println config.version
println config.message
}
})
}
}
apply plugin:LibPlugin
//配置
libConfig {
version = '1.0'
message = 'lib message'
}
复制代码
配置完成后,执行./gradlew libTask 获得以下结果
> Configure project :app
this is lib plugin
> Task :lib:libTask
1.0
lib message
复制代码
看完上述代码,咱们就知道android {} 其实他就是一个Extension, 他是由plugin 'com.android.application'或者'com.android.library' 建立。
上述代码中,建立了一个名字为libTask的task,gradle中建立task的方式由不少中, 具体的建立接口在TaskContainer类中
//TaskContainer
Task create(Map<String, ?> options) throws InvalidUserDataException;
Task create(Map<String, ?> options, Closure configureClosure) throws InvalidUserDataException;
Task create(String name, Closure configureClosure) throws InvalidUserDataException;
Task create(String name) throws InvalidUserDataException;
<T extends Task> T create(String name, Class<T> type) throws InvalidUserDataException;
<T extends Task> T create(String name, Class<T> type, Action<? super T> configuration) throws InvalidUserDataException;
复制代码
Project不能够执行跑起来,那么咱们就要定义一些task来完成咱们的编译,运行,打包等。com.android.application插件 为咱们定义了打包task如assemble,咱们刚才定义的插件为咱们添加了一个libTask用于输出。
咱们看到建立的task里面能够直接调用doLast API,那是由于Task类中有doLast API,能够查看对应的代码看到对应的API
gradle 为咱们定义了一些常见的task,如clean,copy等,这些task能够直接使用name建立,以下:
task clean(type: Delete) {
delete rootProject.buildDir
}
复制代码
咱们知道Android打包时,会使用assemble相关的task,可是仅仅他是不能直接打包的,他会依赖其余的一些task. 那么怎么建立一个依赖的Task呢?代码以下
task A{
println "A task"
}
task B({
println 'B task'
},dependsOn: A)
复制代码
执行./graldew B 输出
A task
B task
复制代码
经过上述的一些入门讲解,大概知道了gradle是怎么构建的,那如今来自定义一个安卓打包过程当中,重命名APP名字的一个插件。
上述在build.gradle直接编写Plugin是OK的,那么为了复用性更高一些,那咱们怎么把这个抽出去呢?
以下
其中build.gradle为
apply plugin: 'groovy'
apply plugin: 'maven'
repositories {
mavenLocal()
jcenter()
}
dependencies {
compile gradleApi()
}
def versionName = "0.0.1"
group "com.ding.demo"
version versionName
uploadArchives{ //当前项目能够发布到本地文件夹中
repositories {
mavenDeployer {
repository(url: uri('../repo')) //定义本地maven仓库的地址
}
}
}
复制代码
apkname.properties为
implementation-class=com.ding.demo.ApkChangeNamePlugin
复制代码
ApkChangeNamePlugin
package com.ding.demo
import org.gradle.api.Project
import org.gradle.api.Plugin
class ApkChangeNamePlugin implements Plugin<Project>{
static class ChangeAppNameConfig{
String prefixName
String notConfig
}
static def buildTime() {
return new Date().format("yyyy_MM_dd_HH_mm_ss", TimeZone.getTimeZone("GMT+8"))
}
@Override
void apply(Project project) {
if(!project.android){
throw new IllegalStateException('Must apply \'com.android.application\' or \'com.android.library\' first!');
}
project.getExtensions().create("nameConfig",ChangeAppNameConifg)
ChangeAppNameConfig config
project.afterEvaluate {
config = project.nameConfig
}
project.android.applicationVariants.all{
variant ->
variant.outputs.all {
output ->
if (output.outputFile != null && output.outputFile.name.endsWith('.apk')
&& !output.outputFile.name.contains(config.notConfig)) {
def appName = config.prefixName
def time = buildTime()
String name = output.baseName
name = name.replaceAll("-", "_")
outputFileName = "${appName}-${variant.versionCode}-${name}-${time}.apk"
}
}
}
}
}
复制代码
定义完成后,执行./gradlew uploadArchives 会在本目录生成对应对应的插件
应用插件 在根build.gralde 配置
buildscript {
repositories {
maven {url uri('./repo/')}
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.4.1'
classpath 'com.ding.demo:apkname:0.0.1'
}
}
复制代码
在app.gralde 设置
apply plugin: 'apkname'
nameConfig{
prefixName = 'demo'
notConfig = 'debug'
}
复制代码
Gradle的基础API差很少就介绍完了。
官网地址:docs.gradle.org/current/use…
能够去查看对应的API,也能够直接经过源码的方式查看
可是笔记还没完,学习了Gradle的基础,咱们要让其为咱们服务。下面介绍几个实际应用.
www.jianshu.com/p/94aee6b02…
blog.csdn.net/kaifa1321/a…
APT 全称Annotation Processing Tool,编译期解析注解,生成代码的一种技术。经常使用一些IOC框架的实现原理都是它,著名的ButterKnife,Dagger2就是用此技术实现的,SpringBoot中一些注入也是使用他进行注入的.
在介绍APT以前,先介绍一下SPI (Service Provider Interface)它经过在ClassPath路径下的META-INF/**文件夹查找文件,自动加载文件里所定义的类。 上面自定义的ApkNamePlugin 就是使用这种机制实现的,以下.
要实现一个APT也是须要这种技术实现,可是谷歌已经把这个使用APT技术从新定义了一个,定义了一个auto-service,能够简化实现,下面就实现一个简单Utils的文档生成工具。
咱们知道,项目中的Utils可能会不少,每当新人入职或者老员工也不能完成知道都有那些Utils了,可能会重复加入一些Utils,好比获取屏幕的密度,框高有不少Utils.咱们经过一个小插件来生成一个文档,当用Utils能够看一眼文档就很一目了然了.
定义一个注解
@Retention(RetentionPolicy.CLASS)
public @interface GDoc {
String name() default "";
String author() default "";
String time() default "";
}
复制代码
而后引入谷歌的 auto-service,引入DocAnnotation
apply plugin: 'java-library'
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.google.auto.service:auto-service:1.0-rc2'
implementation 'com.alibaba:fastjson:1.2.34'
implementation project(':DocAnnotation')
}
复制代码
定义一个Entity类
public class Entity {
public String author;
public String time;
public String name;
}
复制代码
定义注解处理器
@AutoService(Processor.class) //其中这个注解就是 auto-service 提供的SPI功能
public class DocProcessor extends AbstractProcessor{
Writer docWriter;
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
}
@Override
public Set<String> getSupportedAnnotationTypes() {
//可处理的注解的集合
HashSet<String> annotations = new HashSet<>();
String canonicalName = GDoc.class.getCanonicalName();
annotations.add(canonicalName);
return annotations;
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment env) {
Messager messager = processingEnv.getMessager();
Map<String,Entity> map = new HashMap<>();
StringBuilder stringBuilder = new StringBuilder();
for (Element e : env.getElementsAnnotatedWith(GDoc.class)) {
GDoc annotation = e.getAnnotation(GDoc.class);
Entity entity = new Entity();
entity.name = annotation.name();
entity.author = annotation.author();
entity.time = annotation.time();
map.put(e.getSimpleName().toString(),entity);
stringBuilder.append(e.getSimpleName()).append(" ").append(entity.name).append("\n");
}
try {
docWriter = processingEnv.getFiler().createResource(
StandardLocation.SOURCE_OUTPUT,
"",
"DescClassDoc.json"
).openWriter();
//docWriter.append(JSON.toJSONString(map, SerializerFeature.PrettyFormat));
docWriter.append(stringBuilder.toString());
docWriter.flush();
docWriter.close();
} catch (IOException e) {
//e.printStackTrace();
//写入失败
}
return true;
}
}
复制代码
dependencies {
implementation project(':DocAnnotation')
annotationProcessor project(':DocComplie')
}
复制代码
应用一个Utils
@GDoc(name = "颜色工具类",time = "2019年09月18日19:58:07",author = "dingxx")
public final class ColorUtils {
}
复制代码
最后生成的文档以下:
名称 功能 做者
ColorUtils 颜色工具类 dingxx
复制代码
固然最后生成的文档能够由本身决定,也能够直接是html等.
在说Android Transform以前,先介绍Android的打包流程,在执行task assemble时
在.class /jar/resources编译的过程当中,apply plugin: 'com.android.application' 这个插件支持定义一个回调 (com.android.tools.build:gradle:2.xx 以上),相似拦截器,能够进行你本身的一些定义处理,这个被成为Android的Transform
那么这个时候,能够动态的修改这些class,完成咱们本身想干的一些事,好比修复第三方库的bug,自动埋点,给第三方库添加函数执行耗时,完成动态的AOP等等.
咱们所知道的 ARoute就使用了这种技术. 固然他是先使用了APT先生成路由文件,而后经过Transform加载.
如下内容引用自ARoute ReadMe
使用 Gradle 插件实现路由表的自动加载 (可选)
apply plugin: 'com.alibaba.arouter'
buildscript {
repositories {
jcenter()
}
dependencies {
classpath "com.alibaba:arouter-register:?"
}
}
复制代码
可选使用,经过 ARouter 提供的注册插件进行路由表的自动加载(power by AutoRegister), 默认经过扫描 dex 的方式 进行加载经过 gradle 插件进行自动注册能够缩短初始化时间解决应用加固致使没法直接访问 dex 文件,初始化失败的问题,须要注意的是,该插件必须搭配 api 1.3.0 以上版本使用!
看ARoute的LogisticsCenter 能够知道,init时,若是没有使用trasnform的plugin,那么他将在注册时,遍历全部dex,查找ARoute引用的相关类,以下
//LogisticsCenter
public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {
if (registerByPlugin) {
logger.info(TAG, "Load router map by arouter-auto-register plugin.");
} else {
Set<String> routerMap;
// It will rebuild router map every times when debuggable.
if (ARouter.debuggable() || PackageUtils.isNewVersion(context)) {
logger.info(TAG, "Run with debug mode or new install, rebuild router map.");
// These class was generated by arouter-compiler.
routerMap = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);
if (!routerMap.isEmpty()) {
context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).edit().putStringSet(AROUTER_SP_KEY_MAP,routerMap).apply();
}
PackageUtils.updateVersion(context); // Save new version name when router map update finishes.
} else {
logger.info(TAG, "Load router map from cache.");
routerMap = new HashSet<>(context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).getStringSet(AROUTER_SP_KEY_MAP, new HashSet<String>()));
}
}
....
}
复制代码
经过以上,咱们知道,回调的是.class文件或者jar文件,那么要处理.class 文件或者jar文件就须要字节码处理的相关工具,经常使用字节码处理的相关工具都有
具体的详细,能够查看美团的推文 Java字节码加强探秘
怎么定义一个Trasnfrom内,回顾上面的gradle plugin实现,看如下代码
public class TransfromPlugin implements Plugin<Project> {
@Override
public void apply(Project project) {
AppExtension appExtension = (AppExtension) project.getProperties().get("android");
appExtension.registerTransform(new DemoTransform(), Collections.EMPTY_LIST);
}
class DemoTransform extends Transform{
@Override
public String getName() {
return null;
}
@Override
public Set<QualifiedContent.ContentType> getInputTypes() {
return null;
}
@Override
public Set<? super QualifiedContent.Scope> getScopes() {
return null;
}
@Override
public boolean isIncremental() {
return false;
}
}
}
复制代码
结合字节码增长技术,就能够实现动态的一些AOP,因为篇幅缘由,这里就不在详细把笔记拿出来了,若是想进一步学习,推荐ARoute做者的一个哥们写的AutoRegister,能够看看源码
到这里Gradle的学习笔记基本整理完成了,因为做者水平有限,若是文中存在错误,还请指正,感谢. 也推荐阅读个人另外一篇文章,Android 修图(换证件照背景,污点修复)