在如今这个时代,仅仅会简单的页面开发已经很难找到好工作了。
所以在实习的业余时间,我开始学习Android的Gradle构建流程,并且想将此心路历程整理为博客,方便加深理解。
简述
在本文,我将给大家简单介绍以下几点:
- 简单的
Gradle Plugin
知识(不包含Groovy
语法) - 如何阅读
AGP
(Android Gradle Plugin
的缩写)源码 AGP
大致的工作流程
在之后的系列里面,我将会针对一些细节进行讲解,仅仅代表自己的看法。
PS:本文基于Gradle 3.2.1
版本
Gradle Plugin
在讲解 Gradle Plugin
之前,我们首先需要明白 Gradle
是什么?
Gradle 是什么?
简单来说,Gradle
在 Android
里是一个构建工具,它帮助我们将源码和资源文件通过一些处理、编译,打包成一个安装包。在 Android Studio
中,好心的 IDE 开发者已经为我们集成了一套默认的 Gradle 构建流程
,这就是为什么我们新建一个 Project
时,一行代码也不需要改就可以打包出一个 Apk
。
如果此时我们需要根据自己的项目定制流程,就得需要基本的 Gradle
知识了。当然,现在的 Gradle 已经支持 kotlin-based DSL了,不需要再写不习惯的 Groovy
了。
生命周期
Gradle一共有三个生命周期:初始化阶段,配置阶段,执行阶段
初始化阶段:
在这个阶段中,Gradle
会解析整个工程的 Project
,构建 Project
对象。其中 settings.gradle
文件就是在这里执行的,例如下文
include ':app',':module_1',':module_2',':module_3'
这里会将这四个module
解析为 Project
,同时也会将根目录下的build.gradle
解析为 RootProject
。
配置阶段:
解析所有 Project
对象中的 Task
,根据他们的依赖关系,产生一份有向无环图。在这个阶段,也会将我们apply
的 Plugin
从远程下载下来,也会从 buildSrc
中获取定义的 Plugin
。
执行阶段:
按照之前产生的有向无环图开始执行 Task
,每个 Task
所依赖的其他 Task
, 都会保证在这个 Task
之前执行。
回调:
借用网络上的一幅图,下面展示了在 Gradle
生命周期的各个回调。可以充分利用这些 hook
点实现自己的插件。
Project
每一个 build.gradle
就对应着一个 Project
,这就意味着基本上每个Module
都相当于一个 Project
,这些 Project
会在初始化阶段,由 settings.gradle
加载进去。
Project
是一个范型接口,里面定义着许多构建可以用到的方法。
如果有兴趣可以查看 Gradle 用户指南 和 Gradle javadoc,记得选择合适的版本文档进行阅读。·
Task
往往一个 Project
包含多个 Task
,每一个Task
就是一个操作,例如合并资源、复制文件等。所有的 Task
都由 TaskContainer
进行存放和管理,而这些Task
之间也有相应的依赖关系,我们可以通过 dependsOn
将自己定义的 Task
放在另一个的前面或者后面。同时可以利用doLast
、doFirst
这些回调满足自己的需求。
例如我们定义了一个自己的Task
val task = project.task(PLUGIN_NAME)
task.doLast {
//do..
}
val preBuild = project.tasks.findByName("preBuild")
preBuild?.dependsOn(task)
这样就可以将自己定义的 task
放在preBuild
这个Task
之前,起到了hook preBuild
的作用。
在AGP源码中,官方也为我们定制了许许多多的 Task,每个 Task 的作用大不相同。
Plugin
在开发中,我们常常会利用Plugin
参与到模块化构建脚本中,将基础功能抽离出来成为一个插件,方便在各种项目中使用。
我们往往通过apply
来引用定义好的 Plugin
。Plugin
有三种定义方式:
-
buildSrc
定义 -
由远程仓库获取
-
自己写的
.gradle
文件。如果想学习如何自定义
Plugin
,可以自行搜索文章,这类文章已经非常丰富了。
Extension
Extension
意为扩展,在 Gradle
中相当于 Plugin
的 扩展,我们可以通过 Extension
获取用户的自定义配置。所有的 Extension
都由 ExtensionContainer
来创建与管理。
最常见的 Extension
莫过 android extension
, 这个 extension
是在 AbstractAppPlugin
中被创建的,主要负责获取打包基础配置的。
android {
compileSdkVersion 29
buildToolsVersion "29.0.2"
defaultConfig {
applicationId "com.fxy.agpstudy"
minSdkVersion 16
targetSdkVersion 29
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
我们可以通过 ExtensionContainer
创建自己的 Extension
,如下文
open class MyExtension {
var ignore: Boolean = true
}
class MyPlugin : Plugin<Project> {
companion object {
const val PLUGIN = "MyPlugin"
const val EXTENSION = "MyExtension"
}
override fun apply(project: Project) {
project.extensions.create(EXTENSION, MyPlugin::class.java)
//..
}
如何阅读 AGP 源码
有三种方式可以阅读 AGP 源码:
AOSP
上下载源码,解压出来阅读AOSP
上下载构建工具的仓库,导入IDE中阅读- 直接新建项目,在项目中
implementation
来观看源码
因为前两种方法比较繁琐,想简单了解的话,我这里推荐第三种,直接导入依赖,点进源码中观看。
只需要修改如下两个地方:
implementation 'com.android.tools.build:gradle:3.2.1'
classpath 'com.android.tools.build:gradle:3.2.1'
这样依赖,通过搜索 AGP
源码,定位位置,我们就可以看到整个源码了。除了有些地方仍然无法跳转以外,基本能够应付简单的源码阅读。
AGP 大致工作流程
AGP 3.1.2设计图
针对不同的平台,例如Java
、Android
,官方编译团队都需要定制自己的流程,这些流程是仍然是通过自定义 plugin 实现的。
Android 编译团队就在Gradle Plugin
的基础上,扩展了自己的 AppPlugin,这个插件是通过下文这样引入的,相信大家不陌生吧?
apply plugin: 'com.android.application'
于是,我们的切入点就在AppPlugin
里面。然而AppPlugin
只是一个简单的实现类,其具体逻辑实现,基本都在AbstractAppPlugin
与 BasePlugin
中。
AppPlugin继承关系
Android Extension在哪里创建的?
通过查看AbstractAppPlugin
的源码,我们能够轻松地找到 android
初始化的地方。
@NonNull
@Override
protected BaseExtension createExtension(
@NonNull Project project,
@NonNull ProjectOptions projectOptions,
@NonNull GlobalScope globalScope,
@NonNull SdkHandler sdkHandler,
@NonNull NamedDomainObjectContainer<BuildType> buildTypeContainer,
@NonNull NamedDomainObjectContainer<ProductFlavor> productFlavorContainer,
@NonNull NamedDomainObjectContainer<SigningConfig> signingConfigContainer,
@NonNull NamedDomainObjectContainer<BaseVariantOutput> buildOutputs,
@NonNull SourceSetManager sourceSetManager,
@NonNull ExtraModelInfo extraModelInfo) {
return project.getExtensions()
.create(
"android",
getExtensionClass(),
project,
projectOptions,
globalScope,
sdkHandler,
buildTypeContainer,
productFlavorContainer,
signingConfigContainer,
buildOutputs,
sourceSetManager,
extraModelInfo,
isBaseApplication);
}
可以发现,所有的Extension
就像我们之前所说的一样,都由ExtensionContainer
创建、管理。
执行流程
一个 Plugin 中最重要的方法是啥?—— 当然是 apply
了
从AppPlugin
向上寻找,在BasePlugin
中发现了 apply
方法的实现。
apply
@Override
public void apply(@NonNull Project project) {
//... 一系列初始化操作
threadRecorder = ThreadRecorder.get();
ProfilerInitializer.init(project, projectOptions);
//... 一系列初始化操作
//如果是最新的dsl实现
if (!projectOptions.get(BooleanOption.ENABLE_NEW_DSL_AND_API)) {
threadRecorder.record(
ExecutionType.BASE_PLUGIN_PROJECT_CONFIGURE,
project.getPath(),
null,
this::configureProject);
threadRecorder.record(
ExecutionType.BASE_PLUGIN_PROJECT_BASE_EXTENSION_CREATION,
project.getPath(),
null,
this::configureExtension);
threadRecorder.record(
ExecutionType.BASE_PLUGIN_PROJECT_TASKS_CREATION,
project.getPath(),
null,
this::createTasks);
} else {
//...都是以前的实现,我们就不管了
}
}
所以说,我们可以看见threadRecorder
调用了三次record
方法。然后每个方法里面使用了Java8
的lambda
语法,生成了匿名内部类。然后执行了以下三个方法:configureProject
、configureExtension
、createTasks
。后面
在分析这三个方法之前,我其实还是蛮想知道record
方法是干啥的,相信你们也想知道。
Record方法
threadRecorder.record
// 利用get方法获取Recorder
public static Recorder get() {
return ProcessProfileWriterFactory.getFactory().isInitialized() ? RECORDER : NO_OP_RECORDER;
}
//两个单例,一般来说是获取的下面那个
private static final Recorder NO_OP_RECORDER = new NoOpRecorder();
private static final Recorder RECORDER = new ThreadRecorder();
所以说,我们可以查看 ThreadRecorder
中 record
方法干了啥?
@Nullable
@Override
public <T> T record(
@NonNull ExecutionType executionType,
@Nullable GradleTransformExecution transform,
@NonNull String projectPath,
@Nullable String variant,
@NonNull Block<T> block) {
// 获取刚刚初始化过的单例,这个单例是在ProfilerInitializer.init中初始化的
// 这个get方法里面,最终实现是一个加了synchronized的单例
ProfileRecordWriter profileRecordWriter = ProcessProfileWriter.get();
//创建GradleBuildProfileSpan的Builder
GradleBuildProfileSpan.Builder currentRecord =
create(profileRecordWriter, executionType, transform);
try {
//回调到之前的lambda表达式那里,执行我们看见的那三个方法
return block.call();
} catch (Exception e) {
block.handleException(e);
} finally {
write(profileRecordWriter, currentRecord, projectPath, variant);
}
// we always return null when an exception occurred and was not rethrown.
return null;
}
threadRecorder.create
private GradleBuildProfileSpan.Builder create(
@NonNull ProfileRecordWriter profileRecordWriter,
@NonNull ExecutionType executionType,
@Nullable GradleTransformExecution transform) {
long thisRecordId = profileRecordWriter.allocateRecordId();
// am I a child ?
@Nullable
Long parentId = recordStacks.get().peek();
long startTimeInMs = System.currentTimeMillis();
final GradleBuildProfileSpan.Builder currentRecord =
GradleBuildProfileSpan.newBuilder()
.setId(thisRecordId)
.setType(executionType)
.setStartTimeInMs(startTimeInMs);
if (transform != null) {
currentRecord.setTransform(transform);
}
if (parentId != null) {
currentRecord.setParentId(parentId);
}
currentRecord.setThreadId(threadId.get());
recordStacks.get().push(thisRecordId);
return currentRecord;
}
protected final ThreadLocal<Deque<Long>> recordStacks =
ThreadLocal.withInitial(ArrayDeque::new);
这里可以看到,大概就是为GradleBuildProfileSpan.Builder
设置threadId
等各种id,放进ThreadLocal
中的Deque。
既然是一个双向队列,那么肯定有消费他的时候。我们定眼一看,发现write这个方法中有pop操作!
threadRecorder.write
private void write(
@NonNull ProfileRecordWriter profileRecordWriter,
@NonNull GradleBuildProfileSpan.Builder currentRecord,
@NonNull String projectPath,
@Nullable String variant) {
// pop this record from the stack.
if (recordStacks.get().pop() != currentRecord.getId()) {
Logger.getLogger(ThreadRecorder.class.getName())
.log(Level.SEVERE, "Profiler stack corrupted");
}
currentRecord.setDurationInMs(
System.currentTimeMillis() - currentRecord.getStartTimeInMs());
//关键处
profileRecordWriter.writeRecord(projectPath, variant, currentRecord);
}
ProfileRecordWriter
是一个接口,其实现类为ProcessProfileWriter
。于是,我们需要追踪到这个类中,查看其writeRecord
方法
ProcessProfileWriter.writeRecord
/** Append a span record to the build profile. Thread safe. */
@Override
public void writeRecord(
@NonNull String project,
@Nullable String variant,
@NonNull final GradleBuildProfileSpan.Builder executionRecord) {
executionRecord.setProject(mNameAnonymizer.anonymizeProjectPath(project));
executionRecord.setVariant(mNameAnonymizer.anonymizeVariant(project, variant));
spans.add(executionRecord.build());
}
这里使用builder
模式创建了GradleBuildProfileSpan
,保存在spans里面。
private final ConcurrentLinkedQueue<GradleBuildProfileSpan> spans;
在finishAndMaybeWrite
方法中,GradleBuildProfile.Builder
中会添加spans
进去。
synchronized void finishAndMaybeWrite(@Nullable Path outputFile) {
checkState(!finished, "Already finished");
finished = true;
//添加spans
mBuild.addAllSpan(spans);
//...省略一些对mBuild的配置
// Write benchmark file into build directory, if set.
if (outputFile != null) {
try {
Files.createDirectories(outputFile.getParent());
try (BufferedOutputStream outputStream =
new BufferedOutputStream(
Files.newOutputStream(outputFile, StandardOpenOption.CREATE_NEW))) {
//写入基准文件到build目录下
mBuild.build().writeTo(outputStream);
}
if (mEnableChromeTracingOutput) {
ChromeTracingProfileConverter.toJson(outputFile);
}
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
//...省略
}
outputFile
在注释里面说道,是基准文件,于是我向上一步步寻找这个文件的定义在哪,并且这个方法是如何被调用的
ProfilerInitializer.ProfileShutdownListener
最终我们找到,这个方法是在ProfilerInitializer.ProfileShutdownListener
中被调用的
@Override
public void completed() {
synchronized (lock) {
if (recordingBuildListener != null) {
gradle.removeListener(Objects.requireNonNull(recordingBuildListener));
recordingBuildListener = null;
@Nullable
Path profileFile =
profileDir == null
? null
: profileDir.resolve(
PROFILE_FILE_NAME.format(LocalDateTime.now()));
// This is deliberately asynchronous, so the build can complete before the analytics are submitted.
ProcessProfileWriterFactory.shutdownAndMaybeWrite(profileFile);
}
}
}
而这个Listener
是这么添加的
project.getGradle()
.addListener(
new ProfileShutdownListener(
project.getGradle(),
projectOptions.get(StringOption.PROFILE_OUTPUT_DIR),
projectOptions.get(BooleanOption.ENABLE_PROFILE_JSON)));
所以说,Listener
的completed
相当于Gradle
构建完成的回调,于是这个record
这个点的流程差不多就通了。
我们总结一下:
1、创建了GradleBuildProfileSpan.Builder
2、回调configureProject
、configureExtension
、createTasks
这三个方法
3、写入GradleBuildProfileSpan
并保存到spans
中
4、等Gradle构建完成时,通过Listener
的complete
回调,将spans
转化为GradleBuildProfile
然后写入到基准文件中。
后续
通过这么分析下来,这个点对我们理解AGP流程帮助不大,但是谁又能在分析源码之前知道这个点重不重要呢?探索源码的精神还是要有的!
之后的文章,我将分析configureProject、configureExtension、createTasks这三个方法的逻辑,以及里面较为重要的Task。
如果有问题欢迎指出