Android Gradle Plugin —— 初窥门径 (一)

1,532 阅读9分钟

在如今这个时代,仅仅会简单的页面开发已经很难找到好工作了。

所以在实习的业余时间,我开始学习Android的Gradle构建流程,并且想将此心路历程整理为博客,方便加深理解。

简述

在本文,我将给大家简单介绍以下几点:

  1. 简单的 Gradle Plugin 知识(不包含Groovy语法)
  2. 如何阅读 AGPAndroid Gradle Plugin的缩写)源码
  3. AGP 大致的工作流程

在之后的系列里面,我将会针对一些细节进行讲解,仅仅代表自己的看法。

PS:本文基于Gradle 3.2.1版本

Gradle Plugin

​ 在讲解 Gradle Plugin 之前,我们首先需要明白 Gradle 是什么?

Gradle 是什么?

​ 简单来说,GradleAndroid 里是一个构建工具,它帮助我们将源码和资源文件通过一些处理、编译,打包成一个安装包。在 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,根据他们的依赖关系,产生一份有向无环图。在这个阶段,也会将我们applyPlugin 从远程下载下来,也会从 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 放在另一个的前面或者后面。同时可以利用doLastdoFirst 这些回调满足自己的需求。

​ 例如我们定义了一个自己的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来引用定义好的 PluginPlugin有三种定义方式:

  1. buildSrc 定义

  2. 由远程仓库获取

  3. 自己写的.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 源码:

  1. AOSP上下载源码,解压出来阅读
  2. AOSP上下载构建工具的仓库,导入IDE中阅读
  3. 直接新建项目,在项目中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设计图

​ 针对不同的平台,例如JavaAndroid,官方编译团队都需要定制自己的流程,这些流程是仍然是通过自定义 plugin 实现的。

​ Android 编译团队就在Gradle Plugin的基础上,扩展了自己的 AppPlugin,这个插件是通过下文这样引入的,相信大家不陌生吧?

apply plugin: 'com.android.application'

​ 于是,我们的切入点就在AppPlugin里面。然而AppPlugin只是一个简单的实现类,其具体逻辑实现,基本都在AbstractAppPluginBasePlugin中。

​ 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方法。然后每个方法里面使用了Java8lambda语法,生成了匿名内部类。然后执行了以下三个方法:configureProjectconfigureExtensioncreateTasks。后面

​ 在分析这三个方法之前,我其实还是蛮想知道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();

​ 所以说,我们可以查看 ThreadRecorderrecord 方法干了啥?

	  @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)));

所以说,Listenercompleted相当于Gradle构建完成的回调,于是这个record这个点的流程差不多就通了。

我们总结一下:

1、创建了GradleBuildProfileSpan.Builder

2、回调configureProjectconfigureExtensioncreateTasks这三个方法

3、写入GradleBuildProfileSpan并保存到spans

4、等Gradle构建完成时,通过Listenercomplete回调,将spans转化为GradleBuildProfile然后写入到基准文件中。

后续

通过这么分析下来,这个点对我们理解AGP流程帮助不大,但是谁又能在分析源码之前知道这个点重不重要呢?探索源码的精神还是要有的!

之后的文章,我将分析configureProject、configureExtension、createTasks这三个方法的逻辑,以及里面较为重要的Task。

如果有问题欢迎指出