前言
Gradle 是將軟件編譯碉纺、測(cè)試、部署等步驟聯(lián)系在一起自動(dòng)化構(gòu)建工具础浮。
對(duì)于Android開發(fā)人員已經(jīng)了解build.gradle 的 android{} 和 dependencies{} 恋拍,但是他的編譯過程是什么樣的行楞?這個(gè)過程中可以干些什么事了解嗎?
此文是學(xué)習(xí)Gradle時(shí)的學(xué)習(xí)筆記海雪,讓你重新認(rèn)識(shí)Gradle锦爵,讓Gradle加快并提效構(gòu)建你的項(xiàng)目。此時(shí)分享給大家奥裸,與大家共勉
此筆記主要內(nèi)容如下
Gradle 最基礎(chǔ)的一個(gè)項(xiàng)目配置
Groovy 基礎(chǔ)語法 并解釋 apply plugin: 'xxxx'和dependencies{}
Gradle Project/Task 并自定義Task和Plugin
自定義一個(gè)重命名APP名的插件 流程
APT 技術(shù)- Java AbstractProcessor
Android 字節(jié)碼增強(qiáng)技術(shù) - Transform (Android 中使用字節(jié)碼增強(qiáng)技術(shù))
文章內(nèi)容略長(zhǎng)险掀,如果你已經(jīng)掌握Gradle基礎(chǔ)知識(shí),可以直接通過目錄查看你想看的內(nèi)容湾宙,回顧或者學(xué)習(xí)都還不錯(cuò)樟氢。
初識(shí)Gradle 項(xiàng)目構(gòu)建配置
gralde項(xiàng)目結(jié)構(gòu)
如圖所示,是一個(gè)比較小的gradle配置,這里主要說兩部分
- 綠色部分: gralde版本配置及gralde所需要的腳本侠鳄,其中g(shù)radlew為linux/mac下的腳本埠啃,gradle.bat為windows下所需的腳本
- 紅色部分:settings.gradle 為根項(xiàng)目的項(xiàng)目配置,外層的build.gradle為根項(xiàng)目的配置伟恶,內(nèi)層的build.gradle為子項(xiàng)目的配置
gradle 配置順序
gralde的項(xiàng)目配置是先識(shí)別 settings.gradle碴开,然后在配置各個(gè)build.gradle.
為了說明構(gòu)建執(zhí)行順序,在上述最基礎(chǔ)的gradle項(xiàng)目結(jié)構(gòu)里面設(shè)置了對(duì)應(yīng)的代碼
// 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博秫,執(zhí)行./gradlew 得到如下結(jié)果:
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 語法
下面講一些關(guān)于groovy的語法潦牛,可以打開Android Studio Tools-> Groovy Console練習(xí)Groovy 語法 ,如下
可選的類型定義,可以省略語句結(jié)束符分號(hào)(;)
int vs = 1
def version = 'version1'
println(vs)
println(version)
括號(hào)也是可選的
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()
輸出結(jié)果
[ant, maven, gradle, test]
1000
class java.util.LinkedHashMap
閉包
groovy語法中支持閉包語法挡育,閉包簡(jiǎn)單的說就是代碼塊,如下:
def v = {
v -> println v
}
static def testMethod(Closure closure){
closure('閉包 test')
}
testMethod v
其中定義的v就為閉包巴碗,testMethod 為一個(gè)方法分俯,傳入?yún)?shù)為閉包寓调,然后調(diào)用閉包.
解釋 apply plugin: 'xxxx'和 dependencies{}
準(zhǔn)備工作,看gradle的源碼
我們先把子項(xiàng)目的build.gradle改為如下形式
apply plugin: 'java-library'
repositories {
mavenLocal()
}
dependencies {
compile gradleApi()
}
這樣搏色,我們就可以直接看gradle的源碼了,在External Libraries里如下
解釋
進(jìn)入build.gradle 點(diǎn)擊apply 會(huì)進(jìn)入到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其實(shí)就是一個(gè)方法,后面?zhèn)鬟f的就是一個(gè)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是一個(gè)方法 后面?zhèn)鬟f的是一個(gè)閉包的參數(shù).
問題:思考那么android {}也是一樣的實(shí)現(xiàn)嗎? 后面講解
Gradle Project/Task
在前面章節(jié)中提到gralde初始化配置痊银,是先解析并執(zhí)行setting.gradle抵蚊,然后在解析執(zhí)行build.gradle,那么其實(shí)這些build.gradle 就是Project溯革,外層的build.gradle是根Project,內(nèi)層的為子project贞绳,根project只能有一個(gè),子project可以有多個(gè).
我們知道了最基礎(chǔ)的gradle配置致稀,那么怎么來使用Gradle里面的一些東西來為我們服務(wù)呢冈闭?
Plugin
前面提到apply plugin:'xxxx',這些plugin都是按照gradle規(guī)范來實(shí)現(xiàn)的抖单,有java的有Android的萎攒,那么我們來實(shí)現(xiàn)一個(gè)自己的plugin.
把build.gradle 改為如下代碼
//app build.gradle
class LibPlugin implements Plugin<Project>{
@Override
void apply(Project target) {
println 'this is lib plugin'
}
}
apply plugin:LibPlugin
運(yùn)行./gradlew 結(jié)果如下
> Configure project :app
this is lib plugin
Plugin 之Extension
我們?cè)谧远x的Plugin中要獲取Project的配置,可以通過Project去獲取一些基本配置信息矛绘,那我們要自定義的一些屬性怎么去配置獲取呢耍休,這時(shí)就需要?jiǎng)?chuàng)建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'
//創(chuàng)建 Extension
target.extensions.create('libConfig',LibExtension)
//創(chuàng)建一個(gè)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'
}
配置完成后羊精,執(zhí)行./gradlew libTask
得到如下結(jié)果
> Configure project :app
this is lib plugin
> Task :lib:libTask
1.0
lib message
看完上述代碼,我們就知道android {} 其實(shí)他就是一個(gè)Extension囚玫, 他是由plugin 'com.android.application'或者'com.android.library' 創(chuàng)建喧锦。
Task
上述代碼中,創(chuàng)建了一個(gè)名字為libTask的task,gradle中創(chuàng)建task的方式由很多中, 具體的創(chuàng)建接口在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不可以執(zhí)行跑起來劫灶,那么我們就要定義一些task來完成我們的編譯裸违,運(yùn)行,打包等本昏。com.android.application插件 為我們定義了打包task如assemble供汛,我們剛才定義的插件為我們添加了一個(gè)libTask用于輸出。
Task API
我們看到創(chuàng)建的task里面可以直接調(diào)用doLast API涌穆,那是因?yàn)門ask類中有doLast API,可以查看對(duì)應(yīng)的代碼看到對(duì)應(yīng)的API
Gradle的一些Task
gradle 為我們定義了一些常見的task怔昨,如clean,copy等宿稀,這些task可以直接使用name創(chuàng)建趁舀,如下:
task clean(type: Delete) {
delete rootProject.buildDir
}
依賴task
我們知道Android打包時(shí),會(huì)使用assemble相關(guān)的task祝沸,但是僅僅他是不能直接打包的矮烹,他會(huì)依賴其他的一些task.
那么怎么創(chuàng)建一個(gè)依賴的Task呢?代碼如下
task A{
println "A task"
}
task B({
println 'B task'
},dependsOn: A)
執(zhí)行./graldew B 輸出
A task
B task
自定義一個(gè)重命名APP名字的插件
通過上述的一些入門講解越庇,大概知道了gradle是怎么構(gòu)建的,那現(xiàn)在來自定義一個(gè)安卓打包過程中奉狈,重命名APP名字的一個(gè)插件卤唉。
上述在build.gradle直接編寫Plugin是OK的,那么為了復(fù)用性更高一些仁期,那我們?cè)趺窗堰@個(gè)抽出去呢桑驱?
如下
其中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{ //當(dāng)前項(xiàng)目可以發(fā)布到本地文件夾中
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"
}
}
}
}
}
定義完成后,執(zhí)行./gradlew uploadArchives 會(huì)在本目錄生成對(duì)應(yīng)對(duì)應(yīng)的插件
應(yīng)用插件
在根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 設(shè)置
apply plugin: 'apkname'
nameConfig{
prefixName = 'demo'
notConfig = 'debug'
}
Gradle doc 官網(wǎng)
Gradle的基礎(chǔ)API差不多就介紹完了跛蛋。
官網(wǎng)地址:https://docs.gradle.org/current/userguide/userguide.html
可以去查看對(duì)應(yīng)的API,也可以直接通過源碼的方式查看
但是筆記還沒完熬的,學(xué)習(xí)了Gradle的基礎(chǔ),我們要讓其為我們服務(wù)赊级。下面介紹幾個(gè)實(shí)際應(yīng)用.
APT 技術(shù)
http://www.reibang.com/p/94aee6b02b2b
https://blog.csdn.net/kaifa1321/article/details/79683246
APT 全稱Annotation Processing Tool押框,編譯期解析注解,生成代碼的一種技術(shù)此衅。常用一些IOC框架的實(shí)現(xiàn)原理都是它强戴,著名的ButterKnife,Dagger2就是用此技術(shù)實(shí)現(xiàn)的挡鞍,SpringBoot中一些注入也是使用他進(jìn)行注入的.
在介紹APT之前,先介紹一下SPI (Service Provider Interface)它通過在ClassPath路徑下的META-INF/**文件夾查找文件预烙,自動(dòng)加載文件里所定義的類墨微。
上面自定義的ApkNamePlugin 就是使用這種機(jī)制實(shí)現(xiàn)的,如下.
SPI 技術(shù)也有人用在了組件化的過程中進(jìn)行解耦合扁掸。
要實(shí)現(xiàn)一個(gè)APT也是需要這種技術(shù)實(shí)現(xiàn)翘县,但是谷歌已經(jīng)把這個(gè)使用APT技術(shù)重新定義了一個(gè),定義了一個(gè)auto-service谴分,可以簡(jiǎn)化實(shí)現(xiàn),下面就實(shí)現(xiàn)一個(gè)簡(jiǎn)單Utils的文檔生成工具锈麸。
Utils文檔生成插件
我們知道,項(xiàng)目中的Utils可能會(huì)很多牺蹄,每當(dāng)新人入職或者老員工也不能完成知道都有那些Utils了忘伞,可能會(huì)重復(fù)加入一些Utils,比如獲取屏幕的密度沙兰,框高有很多Utils.我們通過一個(gè)小插件來生成一個(gè)文檔氓奈,當(dāng)用Utils可以看一眼文檔就很一目了然了.
新建一個(gè)名為DocAnnotation的Java Libary
定義一個(gè)注解
@Retention(RetentionPolicy.CLASS)
public @interface GDoc {
String name() default "";
String author() default "";
String time() default "";
}
新建一個(gè)名為DocComplie 的 Java Libary先
然后引入谷歌的 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')
}
定義一個(gè)Entity類
public class Entity {
public String author;
public String time;
public String name;
}
定義注解處理器
@AutoService(Processor.class) //其中這個(gè)注解就是 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;
}
}
項(xiàng)目中引用
dependencies {
implementation project(':DocAnnotation')
annotationProcessor project(':DocComplie')
}
應(yīng)用一個(gè)Utils
@GDoc(name = "顏色工具類",time = "2019年09月18日19:58:07",author = "dingxx")
public final class ColorUtils {
}
最后生成的文檔如下:
名稱 功能 作者
ColorUtils 顏色工具類 dingxx
當(dāng)然最后生成的文檔可以由自己決定鼎天,也可以直接是html等.
Android Transform
在說Android Transform之前舀奶,先介紹Android的打包流程,在執(zhí)行task assemble時(shí)
在.class /jar/resources編譯的過程中斋射,apply plugin: 'com.android.application' 這個(gè)插件支持定義一個(gè)回調(diào) (com.android.tools.build:gradle:2.xx 以上)育勺,類似攔截器但荤,可以進(jìn)行你自己的一些定義處理,這個(gè)被成為Android的Transform
那么這個(gè)時(shí)候涧至,可以動(dòng)態(tài)的修改這些class腹躁,完成我們自己想干的一些事,比如修復(fù)第三方庫的bug化借,自動(dòng)埋點(diǎn)潜慎,給第三方庫添加函數(shù)執(zhí)行耗時(shí),完成動(dòng)態(tài)的AOP等等.
我們所知道的 ARoute就使用了這種技術(shù). 當(dāng)然他是先使用了APT先生成路由文件蓖康,然后通過Transform加載.
以下內(nèi)容引用自ARoute ReadMe
使用 Gradle 插件實(shí)現(xiàn)路由表的自動(dòng)加載 (可選)
apply plugin: 'com.alibaba.arouter'
buildscript {
repositories {
jcenter()
}
dependencies {
classpath "com.alibaba:arouter-register:?"
}
}
可選使用铐炫,通過 ARouter 提供的注冊(cè)插件進(jìn)行路由表的自動(dòng)加載(power by AutoRegister), 默認(rèn)通過掃描 dex 的方式 進(jìn)行加載通過 gradle 插件進(jìn)行自動(dòng)注冊(cè)可以縮短初始化時(shí)間解決應(yīng)用加固導(dǎo)致無法直接訪問 dex 文件蒜焊,初始化失敗的問題倒信,需要注意的是,該插件必須搭配 api 1.3.0 以上版本使用泳梆!
看ARoute的LogisticsCenter 可以知道鳖悠,init時(shí),如果沒有使用trasnform的plugin,那么他將在注冊(cè)時(shí)优妙,遍歷所有dex乘综,查找ARoute引用的相關(guān)類,如下
//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>()));
}
}
....
}
Android Transform的實(shí)現(xiàn)簡(jiǎn)介
通過以上套硼,我們知道卡辰,回調(diào)的是.class文件或者jar文件,那么要處理.class 文件或者jar文件就需要字節(jié)碼處理的相關(guān)工具邪意,常用字節(jié)碼處理的相關(guān)工具都有
- ASM
- Javassist
- AspectJ
具體的詳細(xì)九妈,可以查看美團(tuán)的推文 Java字節(jié)碼增強(qiáng)探秘
怎么定義一個(gè)Trasnfrom內(nèi),回顧上面的gradle plugin實(shí)現(xiàn)雾鬼,看以下代碼
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;
}
}
}
結(jié)合字節(jié)碼增加技術(shù)萌朱,就可以實(shí)現(xiàn)動(dòng)態(tài)的一些AOP,由于篇幅原因策菜,這里就不在詳細(xì)把筆記拿出來了晶疼,如果想進(jìn)一步學(xué)習(xí),推薦ARoute作者的一個(gè)哥們寫的AutoRegister做入,可以看看源碼
總結(jié)
到這里Gradle的學(xué)習(xí)筆記基本整理完成了冒晰,由于作者水平有限,如果文中存在錯(cuò)誤竟块,還請(qǐng)指正壶运,感謝.
也推薦閱讀我的另一篇文章,Android 修圖(換證件照背景浪秘,污點(diǎn)修復(fù))