在如今这个时代,仅仅会简单的页面开发已经很难找到好工作了。
所以在实习的业余时间,我开始学习Android的Gradle构建流程,并且想将此心路历程整理为博客,方便加深理解。
在本文,我将给大家简单介绍以下几点:
Gradle Plugin
知识(不包含Groovy
语法)AGP
(Android Gradle Plugin
的缩写)源码AGP
大致的工作流程在之后的系列里面,我将会针对一些细节进行讲解,仅仅代表自己的看法。
PS:本文基于Gradle 3.2.1
版本
在讲解 Gradle Plugin
之前,我们首先需要明白 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
点实现自己的插件。
每一个 build.gradle
就对应着一个 Project
,这就意味着基本上每个Module
都相当于一个 Project
,这些 Project
会在初始化阶段,由 settings.gradle
加载进去。
Project
是一个范型接口,里面定义着许多构建可以用到的方法。
如果有兴趣可以查看 Gradle 用户指南 和 Gradle javadoc,记得选择合适的版本文档进行阅读。·
往往一个 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
参与到模块化构建脚本中,将基础功能抽离出来成为一个插件,方便在各种项目中使用。
我们往往通过apply
来引用定义好的 Plugin
。Plugin
有三种定义方式:
buildSrc
定义
由远程仓库获取
自己写的.gradle
文件。
如果想学习如何自定义 Plugin
,可以自行搜索文章,这类文章已经非常丰富了。
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 源码:
AOSP
上下载源码,解压出来阅读AOSP
上下载构建工具的仓库,导入IDE中阅读implementation
来观看源码 因为前两种方法比较繁琐,想简单了解的话,我这里推荐第三种,直接导入依赖,点进源码中观看。
只需要修改如下两个地方:
implementation 'com.android.tools.build:gradle:3.2.1' 复制代码
classpath 'com.android.tools.build:gradle:3.2.1' 复制代码
这样依赖,通过搜索 AGP
源码,定位位置,我们就可以看到整个源码了。除了有些地方仍然无法跳转以外,基本能够应付简单的源码阅读。
AGP 3.1.2设计图
针对不同的平台,例如Java
、Android
,官方编译团队都需要定制自己的流程,这些流程是仍然是通过自定义 plugin 实现的。
Android 编译团队就在Gradle Plugin
的基础上,扩展了自己的 AppPlugin,这个插件是通过下文这样引入的,相信大家不陌生吧?
apply plugin: 'com.android.application' 复制代码
于是,我们的切入点就在AppPlugin
里面。然而AppPlugin
只是一个简单的实现类,其具体逻辑实现,基本都在AbstractAppPlugin
与 BasePlugin
中。
AppPlugin继承关系
通过查看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
方法的实现。
@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
方法是干啥的,相信你们也想知道。
// 利用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; } 复制代码
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操作!
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
方法
/** 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
中被调用的
@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。
如果有问题欢迎指出