【灵魂七问】深度探索 Gradle 自动化构建技术(五、Gradle 插件架构实现原理剖析 — 上)

插件魔法:深度解析Gradle插件系统的运作机制 定义插件类:创建一个实现了Plugin接口的类,其中T是目标项目类型。@Override// 应用插件逻辑注册插件:在构建脚本中注册自定义插件。plugins {实现插件逻辑:在apply方法中实现插件的具体逻辑。@Override打包和分发插件:将插件打包为JAR文件,并发布到插件仓库或本地文件系统。 阅读详情

前言

成为一名优秀的Android开发,需要一份完备的知识体系,在这里,让我们一起成长为自己所想的那样~。

目前,Gradle 自动化技术越来越重要,也有许多同学已经能够制作出自己的 Gradle 插件,但是一直有一些 “梗” 遗留在我们脑海中,无时无刻不提醒着我们,你真的掌握了吗?例如,“梗1”:Gradle 插件的整体实现架构?我:…,“梗2”:Android Gradle 插件更新历史有哪些重要优化或者改进?我:…, “梗3”:Gradle 构建的核心流程是怎样的?我:…,“梗4”:Gradle 中依赖实现的原理?我:…, “梗5”:AppPlugin 构建流程?我:…, “梗6”:assembleDebug 打包流程?我:…, “梗7”:一些很重要 Task 实现原理能否说说?我:… 。是否有很多点并没有真正地去了解过呢?

思维导图

目录

  • 一、Gradle 插件实现架构概述
  • 二、了解 Android Gradle Plugin 的更新历史
    • 1、Android Gradle Plugin V3.5.0(2019 年 8 月)
    • 2、Android Gradle Plugin V3.4.0(2019 年 4 月)
    • 3、Android Gradle Plugin V3.3.0(2019 年 1 月)
    • 4、Android Gradle Plugin V3.2.0(2018 年 9 月)
    • 5、Android Gradle Plugin V3.1.0(2018 年 3 月)
    • 6、Android Gradle Plugin V3.0.0(2017 年 10 月)
    • 7、Android Gradle Plugin V2.3.0(2017 年 2 月)
  • 三、Gradle 构建核心流程解析
    • 1、LoadSettings
    • 2、Configure
    • 3、TaskGraph
    • 4、RunTasks
    • 5、Finished
  • 四、关于 Gradle 中依赖实现的原理
    • 1、通过 MethodMissing 机制,间接地调用 DefaultDependencyHandler 的 add 方法去添加依赖。
    • 2、不同的依赖声明,其实是由不同的转换器进行转换的。
    • 3、Project 依赖的本质是 Artifacts 依赖,也即 产物依赖。
    • 4、什么是 Gradle 中的 Configuration?
    • 5、Task 是如何发布自己 Artifacts 的?
  • 五、AppPlugin 构建流程
    • 1、准备工作
    • 2、configureProject 配置项目
    • 3、configureExtension 配置 Extension
    • 4、TaskManager#createTasksBeforeEvaluate 创建不依赖 flavor 的 task
    • 5、BasePlugin#createAndroidTasks 创建构建 task
  • 六、assembleDebug 打包流程浅析
    • 1、Android 打包流程回顾
    • 2、assmableDebug 打包流程浅析
  • 七、重要 Task 实现源码分析
    • 1、资源处理相关 Task
    • 2、将 Class 文件打包成 Dex 文件的过程
  • 八、总结
    • 最后的最后

一、Gradle 插件实现架构概述

Android Gradle plugin 团队在 Android Gradle V3.2.0 之前一直是都是用 Java 编写的 Gradle 插件,在 V3.2.0 便采用了 Kotlin 进行大面积的重写。尽管 Groovy 语法简洁,且其闭包的写法非常灵活,但是 Android Studio 对 Groovy 的支持非常不友好,因此,目前写自定义的 Gradle 插件时我们还是尽量使用 Kotlin,这样能尽量避免编写插件时提示不够造成的坑。

下面,我们就来看看 Gradle 插件的整体实现架构,如下图所示:

在最下层的是底层 Gradle 框架,它主要提供一些基础的服务,如 task 的依赖,有向无环图的构建等等。

上面的则是 Google 编译工具团队的 Android Gradle plugin 框架,它主要是 在 Gradle 框架的基础上,创建了很多与 Android 项目打包有关的 task 及 artifacts(每一个 task 执行完成之后通常都会输出产物)。

最上面的则是开发者自定义的 Plugin,关于自定义 Plugin 通常有两种使用套路,如下所示:

  • 1)、在 Android Gradle plugin 提供的 task 的基础上,插入一些自定义的 task。
  • 2)、增加 Transform 进行编译时代码注入。

二、了解 Android Gradle Plugin 的更新历史

下表列出了各个 Android Gradle 插件版本所需的 Gradle 版本。我们应该使用 Android Gradle Plugin 与 Gradle 这两者的最新版本以获得最佳的性能。

插件版本所需的 Gradle 版本
1.0.0 - 1.1.32.2.1 - 2.3
1.2.0 - 1.3.12.2.1 - 2.9
1.5.02.2.1 - 2.13
2.0.0 - 2.1.22.10 - 2.13
2.1.3 - 2.2.32.14.1+
2.3.0+3.3+
3.0.0+4.1+
3.1.0+4.4+
3.2.0 - 3.2.14.6+
3.3.0 - 3.3.24.10.1+
3.4.0 - 3.4.15.1.1+
3.5.0+5.4.1-5.6.4

目前最新的 Android Gradle Plugin 版本为 V3.6.2,Gradle 版本为 V5.6.4。下面,我们了解下 Android Gradle Plugin 更新历史中比较重要的更新变化。

1、Android Gradle Plugin V3.5.0(2019 年 8 月)

本次更新的重中之重是 提高项目的构建速度。

2、Android Gradle Plugin V3.4.0(2019 年 4 月)

如果使用的是 Gradle 5.0 及更高版本,默认的 Gradle 守护进程内存堆大小会从 1 GB 降到 512 MB。这可能会导致构建性能降低。如果要替换此默认设置,请在项目的 gradle.properties 文件中指定 Gradle 守护进程堆大小。

1)、新的 Lint 检查依赖项配置

增加了新的依赖项配置 lintPublish,并更改了原有 lintChecks 的行为,它们的作用分别如下所示:

  • lintChecks:仅用于在本地构建项目时运行的 Lint 检查。
  • lintPublish:在已发布的 AAR 中启用 Lint 检查,这样使用此 AAR 的项目也会应用那些 Lint 检查。

其示例代码如下所示:

dependencies {
      // Executes lint checks from the ':lint' project at build time.
      lintChecks project(':lint')
      // Packages lint checks from the ':lintpublish' in the published AAR.
      lintPublish project(':lintpublish')
} 

2)、Android 免安装应用功能插件弃用警告

在之前的版本可以使用 com.android.feature 插件构建免安装应用,现在建议使用动态功能插件,这样便可以通过单个 Android App Bundle 发布安装版应用和免安装应用。

3)、R8 默认处于启用状态

R8 将 desugar(脱糖:将 .class 字节码转换为 .dex 字节码的过程)、压缩、混淆、优化和 dex 处理整合到了一个步骤中,从而显著提升了构建性能。R8 是在 Android Gradle Plugin V3.2.0 中引入的,对于使用插件 V3.4.0 及更高版本的应用和 Android 库项目来说,R8 已经默认处于启用状态。

R8 引入之前的编译流程

R8 引入之后的编译流程

可以看到,R8 组合了 Proguard、D8 的功能。如果遇到因 R8 导致的编译失败的问题,可以配置以下代码停用 R8:

# Disables R8 for Android Library modules only.
android.enableR8.libraries = false
# Disables R8 for all modules.
android.enableR8 = false 

3)、弃用 ndkCompile

此时使用 ndkBuild 编译原生库会收到构建错误。我们应该 使用 CMake 或 ndk-build 将 C 和 C++ 代码添加到项目中。

3、Android Gradle Plugin V3.3.0(2019 年 1 月)

1)、为库项目更快地生成 R 类

在老版本中,Android Gradle 插件会为项目的每个依赖项生成一个 R.java 文件,然后将这些 R 类和应用的其他类一起编译。现在,插件会直接生成包含应用的已编译 R 类的 JAR,而不会先编译中间的 R.java 类。这不仅可以显著提升包含多个库子项目和依赖项的项目的编译性能,还可以加快在 Android Studio 中索引文件的速度。

4、Android Gradle Plugin V3.2.0(2018 年 9 月)

新的代码压缩器 R8

R8 是一种执行代码压缩和混淆的新工具,替代了 ProGuard。我们只需将以下代码添加到项目的 gradle.properties 文件中,即可开始使用 R8 的预览版本:

android.enableR8 = true 

使用 D8 进行 desugar 的功能现已默认处于启用状态

5、Android Gradle Plugin V3.1.0(2018 年 3 月)

新的 DEX 编译器 (D8)

默认情况下,Android Studio 此时会使用名为 D8 的新 DEX 编译器。DEX 编译是指针对 ART (对于较早版本的 Android,则针对 Dalvik)将 .class 字节码转换为 .dex 字节码的过程。与之前的编译器(DX)相比,D8 的编译速度更快,输出的 DEX 文件更小,同时却能保持相同甚至更出色的应用运行时性能。

如果在使用 D8 的过程中出现了问题,可以在 gradle.properties 配置以下代码暂时停用 D8 并使用 DX:

android.enableD8=false 

6、Android Gradle Plugin V3.0.0(2017 年 10 月)

1)、通过精细控制的任务图提升了多模块项目的并行性。

更改依赖项时,Gradle 通过不重新编译那些无法被访问的依赖项 API 模块来加快编译速度。此时可以利用 Gradle 的新依赖项配置(implementation、api、compileOnly 和 runtimeOnly)限制哪些依赖项会将其 API 泄露给其他模块。

2)、借助每个类的 dex 处理,可加快增量编译速度。

每个类现在都会编译成单独的 DEX 文件,并且只会对修改过的类重新进行 dex 处理。

3)、启用 Gradle 编译缓存优化某些任务来使用缓存的输出

启用 Gradle 编译缓存能够优化某些任务来使用缓存的输出,从而加快编译速度。

4)、AAPT2 默认已启用并改进了增量资源处理

如果在使用 AAPT2 时遇到了问题,我们可以停用 AAPT2,在 gradle.properties 文件中设置如下代码:

android.enableAapt2=false 

然后,通过在命令行中运行 ./gradlew --stop 来重启 Gradle 守护进程。

7、Android Gradle Plugin V2.3.0(2017 年 2 月)

1)、增加编译缓存

存储编译项目时 Android 插件生成的特定输出。使用缓存时,编译插件的速度会明显加快,因为编译系统在进行后续编译时可以直接重用这些缓存文件,而不必重新创建。此外,我们也可以 使用 cleanBuildCache Task 去清除编译缓存。

更老版本的的更新细节请查阅 gradle-plugin。

三、Gradle 构建核心流程解析

当我们输入 ./gradlew/gradle … 命令之后,一个 Gradle 构建就开始了。它包含如下 三个步骤:

  • 1)、首先,初始化 Gradle 构建框架自身。
  • 2)、然后,把命令行参数包装好送给 DefaultGradleLauncher。
  • 3)、最后,触发 DefaultGradleLauncher 中 Gradle 构建的生命周期,并开始执行标准的构建流程。

在开始深入源码之前,我们可以先自顶向下了解下 Gradle 构建的核心流程图,以便对 Gradle 的构建流程建立一个整体的感知。

当我们执行一个 gralde 命令时,便会调用 gradle/wrapper/gradle-wrapper.jar 里面 org.gradle.wrapper.GradleWrapperMain 类的 main 方法,它就是 gradle 的一个入口方法。该方法的核心源码如下所示:

public static void main(String[] args) throws Exception {
            
        ...
        
        // 1、索引到 gradle-wrapper.properties 文件中配置的 gradle zip 地址,并由此包装成一个 WrapperExecutor 实例。
        WrapperExecutor wrapperExecutor = WrapperExecutor.forWrapperPropertiesFile(propertiesFile);
        // 2、使用 wrapperExecutor 实例的 execute 方法执行 gradle 命令。
        wrapperExecutor.execute(args, new Install(logger, new Download(logger, "gradlew", "0"), new PathAssembler(gradleUserHome)), new BootstrapMainStarter());
} 

然后,我们继续看看 wrapperExecutor 的 execute 方法,源码如下所示:

public void execute(String[] args, Install install, BootstrapMainStarter bootstrapMainStarter) throws Exception {
        // 1、下载 gradle wrapper 需要的依赖与源码。
        File gradleHome = install.createDist(this.config);
        // 2、从这里开始执行 gradle 的构建流程。
        bootstrapMainStarter.start(args, gradleHome);
    } 

首先,在注释1处,会下载 gradle wrapper 需要的依赖与源码。接着,在注释2处,便会调用 bootstrapMainStarter 的 start 方法从这里开始执行 gradle 的构建流程。其内部最终会依次调用 DefaultGradleLauncher 的 getLoadedSettings、getConfiguredBuild、executeTasks 与 finishBuild 方法,而它们对应的状态都定义在 DefaultGradleLauncher 中的 Stage 枚举类中,如下所示:

private static enum Stage {
        LoadSettings,
        Configure,
        TaskGraph,
        RunTasks {
            String getDisplayName() {
                return "Build";
            }
        },
        Finished;

        private Stage() {
        }

        String getDisplayName() {
            return this.name();
        }
} 

下面,我们就对这五个流程来进行详细地分析。

1、LoadSettings

当调用 getLoadedSettings 方法时便开始了加载 Setting.gradle 的流程。其源码如下所示:

public SettingsInternal getLoadedSettings() {
        this.doBuildStages(DefaultGradleLauncher.Stage.LoadSettings);
        return this.gradle.getSettings();
} 

这里又继续调用了 doBuildStages 方法进行处理,内部实现如下所示:

private void doBuildStages(DefaultGradleLauncher.Stage upTo) {
        Preconditions.checkArgument(upTo != DefaultGradleLauncher.Stage.Finished, "Stage.Finished is not supported by doBuildStages.");

        try {
            // 当 Stage 是 RunTask 的时候执行。
            if (upTo == DefaultGradleLauncher.Stage.RunTasks && this.instantExecution.canExecuteInstantaneously()) {
                this.doInstantExecution();
            } else {
               // 当 Stage 不是 RunTask 的时候执行。 this.doClassicBuildStages(upTo);
            }
        } catch (Throwable var3) {
            this.finishBuild(upTo.getDisplayName(), var3);
        }

} 

继续调用 doClassicBuildStages 方法,源码如下所示:

private void doClassicBuildStages(DefaultGradleLauncher.Stage upTo) {
        // 1、当 Stage 为 LoadSettings 时执行 prepareSettings 方法去配置并生成 Setting 实例。
        this.prepareSettings();
        if (upTo != DefaultGradleLauncher.Stage.LoadSettings) {
            // 2、当 Stage 为 Configure 时执行 prepareProjects 方法去配置工程。
            this.prepareProjects();
            if (upTo != DefaultGradleLauncher.Stage.Configure) {
                // 3、当 Stage 为 TaskGraph 时执行 prepareTaskExecution 方法去构建 TaskGraph。
                this.prepareTaskExecution();
                if (upTo != DefaultGradleLauncher.Stage.TaskGraph) {
                   
                   // 4、当 Stage 为 RunTasks 时执行 saveTaskGraph 方法 与 runWork 方法保存 TaskGraph 并执行相应的 Tasks。 this.instantExecution.saveTaskGraph();
                   this.runWork();
                }
            }
        }
} 

可以看到,doClassicBuildStages 方法是个很重要的方法,它对所有的 Stage 任务进行了分发,这里小结一下:

  • 1)、当 Stage 为 LoadSettings 时执行 prepareSettings 方法去配置并生成 Setting 实例。
  • 2)、当 Stage 为 Configure 时执行 prepareProjects 方法去配置工程。
  • 3)、当 Stage 为 TaskGraph 时执行 prepareTaskExecution 方法去构建 TaskGraph。
  • 4)、当 Stage 为 RunTasks 时执行 saveTaskGraph 方法 与 runWork 方法保存 TaskGraph 并执行相应的 Tasks。

然后,我们接着继续看看 prepareSettings 方法,其源码如下所示:

private void prepareSettings() {
        if (this.stage == null) {
            // 1、回调 BuildListener.buildStarted() 回调接口。
            this.buildListener.buildStarted(this.gradle);
            // 2、调用 settingPreparer 接口的实现类 DefaultSettingsPreparer 的 prepareSettings 方法。
            this.settingsPreparer.prepareSettings(this.gradle);
            this.stage = DefaultGradleLauncher.Stage.LoadSettings;
        }
} 

在 prepareSettings 方法做了两件事:

  • 1)、回调 BuildListener.buildStarted 接口。
  • 2)、调用 settingPreparer 接口的实现类 DefaultSettingsPreparer 的 prepareSettings 方法。

我们继续看到 DefaultSettingsPreparer 的 prepareSettings 方法,如下所示:

public void prepareSettings(GradleInternal gradle) {
        // 1、执行 init.gradle,它会在每个项目 build 之前被调用,用于做一些初始化的操作。
        this.initScriptHandler.executeScripts(gradle);
        SettingsLoader settingsLoader = gradle.getParent() != null ? this.settingsLoaderFactory.forNestedBuild() : this.settingsLoaderFactory.forTopLevelBuild();
        // 2、调用 SettingLoader 接口的实现类 DefaultSettingsLoader 的 findAndLoadSettings 找到 Settings.gradle 文件的位置。
        settingsLoader.findAndLoadSettings(gradle);
} 

在 prepareSettings 方法中做了两项处理:

  • 1)、执行 init.gradle,它会在每个项目 build 之前被调用,用于做一些初始化的操作。
  • 2)、调用 SettingLoader 接口的实现类 DefaultSettingsLoader 的 findAndLoadSettings 找到 Settings.gradle 文件的位置。

DefaultSettingLoader 的 findAndLoadSettings 方法关联的实现代码非常多,限于篇幅,我这里直接点出 findAndLoadSettings 方法中的主要处理流程:

  • 1)、首先,查找 settings.gradle 位置。
  • 2)、然后,编译 buildSrc(Android 默认的 Plugin 目录)文件夹下的内容。
  • 3)、接着,解析 gradle.properites 文件:这里会读取 gradle.properties 文件里的配置信息与命令行传入的配置属性并存储。
  • 4)、然后,解析 settings.gradle 文件:这里最后会调用 BuildOperationScriptPlugin.apply 去执行 settings.gradle 文件。
  • 5)、最后,根据 Settings.gradle 文件中获得的信息去创建 project 以及 subproject 实例。

2、Configure

当执行完 LoadSetting 阶段之后,就会执行 Configure 阶段,而配置阶段所作的事情就是 把 gradle 脚本编译成 class 文件并执行。由前可知,此时会执行 prepareProjects 方法,如下所示:

private void prepareProjects() {
        if (this.stage == DefaultGradleLauncher.Stage.LoadSettings) {
            // 1、调用 ProjectsPreparer 接口的实现类  DefaultProjectsPreparer 的 prepareProjects 方法。
            this.projectsPreparer.prepareProjects(this.gradle);
            this.stage = DefaultGradleLauncher.Stage.Configure;
        }
} 

这里会继续调用 ProjectsPreparer 接口的实现类 DefaultProjectsPreparer 的 prepareProjects 方法。其源码如下所示:

public void prepareProjects(GradleInternal gradle) {
        
        ...

        // 1、如果在 gradle.properties 文件中指定了参数 configure-on-demand,则只会配置主项目以及执行 task 所需要的项目。
        if (gradle.getStartParameter().isConfigureOnDemand()) {
            this.projectConfigurer.configure(gradle.getRootProject());
        } else {
            // 2、如果没有指定在 gradle.properties 文件中指定参数 configure-on-demand,则会调用 ProjectConfigurer 接口的实现类 TaskPathProjectEvaluator 的 configureHierarchy 方法去配置所有项目。
            this.projectConfigurer.configureHierarchy(gradle.getRootProject());
            (new ProjectsEvaluatedNotifier(this.buildOperationExecutor)).notify(gradle);
        }

        this.modelConfigurationListener.onConfigure(gradle);
} 

在注释1处,如果在 gradle.properties 文件中指定了参数 configure-on-demand,则只会配置主项目以及执行 task 所需要的项目。我们这里只看默认没有指定的情况。否则,在注释2处,则会调用 ProjectConfigurer 接口的实现类 TaskPathProjectEvaluator 的 configureHierarchy 方法去配置所有项目。

我们接着继续看到 configureHierarchy 方法,如下所示:

public void configureHierarchy(ProjectInternal project) {
        this.configure(project);
        Iterator var2 = project.getSubprojects().iterator();

        while(var2.hasNext()) {
            Project sub = (Project)var2.next();
            this.configure((ProjectInternal)sub);
        }
} 

可以看到在 configureHierarchy 方法中使用了 Iterator 遍历并配置了所有 Project。而 configure 方法最终会调用到 EvaluateProject 类的 run 方法,如下所示:

public void run(final BuildOperationContext context) {
            this.project.getMutationState().withMutableState(new Runnable() {
                public void run() {
                    try {
                        
                        EvaluateProject.this.state.toBeforeEvaluate();
                       
                        // 1、 回调 ProjectEvaluationListener 的 beforeEvaluate 接口。 
                        LifecycleProjectEvaluator.this.buildOperationExecutor.run(new LifecycleProjectEvaluator.NotifyBeforeEvaluate(EvaluateProject.this.project, EvaluateProject.this.state));
                       
                        if (!EvaluateProject.this.state.hasFailure()) {
                            EvaluateProject.this.state.toEvaluate();

                            try {
                               // 2、在 evaluate 方法中会设置默认的 init、wrapper task 和 默认插件,然后便会编译、执行 build.gradle 脚本
                               LifecycleProjectEvaluator.this.delegate.evaluate(EvaluateProject.this.project, EvaluateProject.this.state);
                            } catch (Exception var10) {
                                LifecycleProjectEvaluator.addConfigurationFailure(EvaluateProject.this.project, EvaluateProject.this.state, var10, context);
                            } finally {
                                EvaluateProject.this.state.toAfterEvaluate();
                                // 3、回调  ProjectEvaluationListener.afterEvaluate 接口。 
                                LifecycleProjectEvaluator.this.buildOperationExecutor.run(new LifecycleProjectEvaluator.NotifyAfterEvaluate(EvaluateProject.this.project, EvaluateProject.this.state));
                            }
                        }

                        if (EvaluateProject.this.state.hasFailure()) {
                            EvaluateProject.this.state.rethrowFailure();
                        } else {
                            context.setResult(ConfigureProjectBuildOperationType.RESULT);
                        }
                    } finally {
                        EvaluateProject.this.state.configured();
                    }

                }
            });
} 

在 EvaluateProject 的 run 方法中有如下 三个重要的处理:

  • 1)、回调 ProjectEvaluationListener 的 beforeEvaluate 接口。
  • 2)、在 evaluate 方法中会设置默认的 init、wrapper task 和 默认插件,然后便会编译并执行 build.gradle 脚本。
  • 3)、回调 ProjectEvaluationListener.afterEvaluate 接口。

3、TaskGraph

执行完 初始化阶段 与 配置阶段 之后,就会 调用到 DefaultGradleLauncher 的 prepareTaskExecution 方法去创建一个由 Tasks 组成的一个有向无环图。该方法如下所示:

private void prepareTaskExecution() {
        if (this.stage == DefaultGradleLauncher.Stage.Configure) {
            // 1、调用 TaskExecutionPreparer 接口的实现类 BuildOperatingFiringTaskExecutionPreparer 的 prepareForTaskExecution 方法。
            this.taskExecutionPreparer.prepareForTaskExecution(this.gradle);
            this.stage = DefaultGradleLauncher.Stage.TaskGraph;
        }
} 

这里继续调用了 TaskExecutionPreparer 接口的实现类 BuildOperatingFiringTaskExecutionPreparer 的 prepareForTaskExecution 方法,如下所示:

 public void prepareForTaskExecution(GradleInternal gradle) {
        this.buildOperationExecutor.run(new BuildOperatingFiringTaskExecutionPreparer.CalculateTaskGraph(gradle));
} 

可以看到,这里使用 buildOperationExecutor 实例执行了 CalculateTaskGraph 这个构建操作,我们看到它的 run 方法,如下所示:

public void run(BuildOperationContext buildOperationContext) {
            // 1、填充任务图
            final TaskExecutionGraphInternal taskGraph = this.populateTaskGraph();
            buildOperationContext.setResult(new Result() {
                 getRequestedTaskPaths() {
                    return this.toTaskPaths(taskGraph.getRequestedTasks());
                }

                
                public List<String> getExcludedTaskPaths() {
                    return this.toTaskPaths(taskGraph.getFilteredTasks());
                }

                private List<String> toTaskPaths(Set<Task> tasks) {
                    return ImmutableSortedSet.copyOf(Collections2.transform(tasks, new Function<Task, String>() {
                        public String apply(Task task) {
                            return task.getPath();
                        }
                    })).asList();
                }
            });
} 

在注释1处,直接调用了 populateTaskGraph 填充了 Tasks 有向无环图。源码如下所示:

TaskExecutionGraphInternal populateTaskGraph() {           
            // 1、这里又调用了 TaskExecutionPreparer 接口的另一个实现类 DefaultTaskExecutionPreparer 的 prepareForTaskExecution 方法。
            BuildOperatingFiringTaskExecutionPreparer.this.delegate.prepareForTaskExecution(this.gradle);
            return this.gradle.getTaskGraph();
} 

可以看到,在注释1处,又调用了 TaskExecutionPreparer 接口的另一个实现类 DefaultTaskExecutionPreparer 的 prepareForTaskExecution 方法。该方法如下所示:

public void prepareForTaskExecution(GradleInternal gradle) {
        // 1
        this.buildConfigurationActionExecuter.select(gradle);
        TaskExecutionGraphInternal taskGraph = gradle.getTaskGraph();
        // 2、根据 Tasks 与 Tasks 间的依赖信息填充 taskGraph 实例。
        taskGraph.populate();
        this.includedBuildControllers.populateTaskGraphs();
        if (gradle.getStartParameter().isConfigureOnDemand()) {
            (new ProjectsEvaluatedNotifier(this.buildOperationExecutor)).notify(gradle);
        }
} 

在注释1处,调用了 buildConfigurationActionExecuter 接口的 select 方法,这里采用了 策略 + 接口隔离 设计模式,后续会依次调用 ExcludedTaskFilteringBuildConfigurationAction 的 configure 方法、 DefaultTasksBuildExecutionAction 的 configure 方法、TaskNameResolvingBuildConfigurationAction 的 configure 方法。

下面,我们依次来分析下其中的处理。

1)、ExcludedTaskFilteringBuildConfigurationAction#configure:处理需要排除的 task

public void configure(BuildExecutionContext context) {
        // 1、获取被排除的 Tasks 名称,并依次把它们放入 filters 中。
        GradleInternal gradle = context.getGradle();
        Set<String> excludedTaskNames = gradle.getStartParameter().getExcludedTaskNames();
        if (!excludedTaskNames.isEmpty()) {
            Set<Spec<Task>> filters = new HashSet();
            Iterator var5 = excludedTaskNames.iterator();

            while(var5.hasNext()) {
                String taskName = (String)var5.next();
                filters.add(this.taskSelector.getFilter(taskName));
            }

            // 2、给 TaskGraph 实例添加要 filter 的 Tasks
            gradle.getTaskGraph().useFilter(Specs.intersect(filters));
        }

        context.proceed();
} 

在注释1处,先获取了被排除的 Tasks 名称,并依次把它们放入 filters 中,接着在注释2处给 TaskGraph 实例添加了要 filter 的 Tasks。

可以看到,这里给 TaskGraph 设置了 filter 去处理需要排除的 task,以便在后面计算依赖的时候排除相应的 task。

2)、DefaultTasksBuildExecutionAction#configure:添加默认的 task

public void configure(BuildExecutionContext context) {
        StartParameter startParameter = context.getGradle().getStartParameter();
        Iterator var3 = startParameter.getTaskRequests().iterator();

        TaskExecutionRequest request;
        // 1、如果指定了要执行的 Task,则什么也不做。
        do {
            if (!var3.hasNext()) {
                ProjectInternal project = context.getGradle().getDefaultProject();
                this.projectConfigurer.configure(project);
                List<String> defaultTasks = project.getDefaultTasks();
                // 2、如果没有指定 DefaultTasks,则输出 gradle help 信息。
                if (defaultTasks.size() == 0) {
                    defaultTasks = Collections.singletonList("help");
                    LOGGER.info("No tasks specified. Using default task {}", GUtil.toString(defaultTasks));
                } else {
                    LOGGER.info("No tasks specified. Using project default tasks {}", GUtil.toString(defaultTasks));
                }

                // 3、否则,添加默认的 Task。
                startParameter.setTaskNames(defaultTasks);
                context.proceed();
                return;
            }

            request = (TaskExecutionRequest)var3.next();
        } while(request.getArgs().isEmpty());

        context.proceed();
} 

可以看到,DefaultTasksBuildExecutionAction 类的 configure 方法的处理分为如下三步:

  • 1、如果指定了要执行的 Task,则什么也不做。
  • 2、如果没有指定 defaultTasks,则输出 gradle help 信息。
  • 3、否则,添加默认的 defaultTasks。

3)、TaskNameResolvingBuildConfigurationAction#configure:计算 task 依赖图

public void configure(BuildExecutionContext context) {
        GradleInternal gradle = context.getGradle();
        TaskExecutionGraphInternal taskGraph = gradle.getTaskGraph();
        List<TaskExecutionRequest> taskParameters = gradle.getStartParameter().getTaskRequests();
        Iterator var5 = taskParameters.iterator();

        while(var5.hasNext()) {
            TaskExecutionRequest taskParameter = (TaskExecutionRequest)var5.next();
            // 1、解析 Tasks。
            List<TaskSelection> taskSelections = this.commandLineTaskParser.parseTasks(taskParameter);
            Iterator var8 = taskSelections.iterator();

            // 2、遍历并添加所有已选择的 Tasks 至 taskGraph 实例之中。
            while(var8.hasNext()) {
                TaskSelection taskSelection = (TaskSelection)var8.next();
                LOGGER.info("Selected primary task '{}' from project {}", taskSelection.getTaskName(), taskSelection.getProjectPath());
                taskGraph.addEntryTasks(taskSelection.getTasks());
            }
        }

        context.proceed();
} 

这里主要做了 两个处理:

  • 1)、解析 Tasks:分析命令行中的 Task 的参数前面是否指定了 Project,例如 ‘:project:assembleRelease’,如果没有指定则选中 Project 下所有符合该 taskName 的 Task。
  • 2)、遍历并添加所有已选择的 Tasks 至 taskGraph 实例之中: 在内部会处理 dependson finalizedby mustrunafter shouldrunafter 等 Tasks 间的依赖关系,并会把信息保存在 TaskInfo 之中。

4)、填充 task 依赖图

最后,我们再回到 DefaultTaskExecutionPreparer 的 prepareForTaskExecution 方法,在注释2处,我们就可以 调用 taskGraph 的 populate 方法去根据这些 Tasks 与 Tasks 之间的依赖信息去填充 taskGraph 实例了。

4、RunTasks

填充完 taskGraph 之后,我们就可以开始来执行这些 Tasks 了,我们看到 DefaultGradleLauncher 实例的 executeTasks 方法,如下所示:

public GradleInternal executeTasks() {
        this.doBuildStages(DefaultGradleLauncher.Stage.RunTasks);
        return this.gradle;
} 

在 doBuildStages 方法中又会调用 doClassicBuildStages 方法,源码如下所示:

 private void doClassicBuildStages(DefaultGradleLauncher.Stage upTo) {
        this.prepareSettings();
        if (upTo != DefaultGradleLauncher.Stage.LoadSettings) {
            this.prepareProjects();
            if (upTo != DefaultGradleLauncher.Stage.Configure) {
                this.prepareTaskExecution();
                if (upTo != DefaultGradleLauncher.Stage.TaskGraph) {
                    this.instantExecution.saveTaskGraph();
                    // 1
                    this.runWork();
                }
            }
        }
} 

在注释1处,继续调用了 runWork 方法,如下所示:

private void runWork() {
        if (this.stage != DefaultGradleLauncher.Stage.TaskGraph) {
            throw new IllegalStateException("Cannot execute tasks: current stage = " + this.stage);
        } else {
            List<Throwable> taskFailures = new ArrayList();
            // 1
            this.buildExecuter.execute(this.gradle, taskFailures);
            if (!taskFailures.isEmpty()) {
                throw new MultipleBuildFailures(taskFailures);
            } else {
                this.stage = DefaultGradleLauncher.Stage.RunTasks;
            }
        }
} 

这里又继续 调用了 buildExecuter 接口的实现类 DefaultBuildWorkExecutor 的 execute 方法,其实现如下所示:

public void execute(GradleInternal gradle, Collection<? super Throwable> failures) {
        this.execute(gradle, 0, failures);
}

private void execute(final GradleInternal gradle, final int index, final Collection<? super Throwable> taskFailures) {
        if (index < this.executionActions.size()) {
            // 1
            ((BuildExecutionAction)this.executionActions.get(index)).execute(new BuildExecutionContext() {
                public GradleInternal getGradle() {
                    return gradle;
                }

                public void proceed() {
                  // 2、执行完 executionActions 列表中的第一项后,便开始执行下一项。
                    DefaultBuildWorkExecutor.this.execute(gradle, index + 1, taskFailures);
                }
            }, taskFailures);
        }
} 

1)、执行 DryRunBuildExecutionAction:处理 DryRun

可以看到,这里又调用了 BuildExecutionAction 接口的实现类 DryRunBuildExecutionAction 的 execute 方法,如下所示:

 public void execute(BuildExecutionContext context, Collection<? super Throwable> taskFailures) {
        GradleInternal gradle = context.getGradle();
        // 1、如果命令行里包含 --dry-run 参数,则会跳过该 Task 的执行,并输出 Task 的名称与执行的先后关系。
        if (gradle.getStartParameter().isDryRun()) {
            Iterator var4 = gradle.getTaskGraph().getAllTasks().iterator();

            while(var4.hasNext()) {
                Task task = (Task)var4.next();
                this.textOutputFactory.create(DryRunBuildExecutionAction.class).append(((TaskInternal)task).getIdentityPath().getPath()).append(" ").style(Style.ProgressStatus).append("SKIPPED").println();
            }
        } else {
            context.proceed();
        }
} 

在注释1处,如果命令行里包含了 --dry-run 参数,则会跳过该 Task 的执行,并输出 Task 的名称与执行的先后关系。

2)、执行:SelectedTaskExecutionAction:使用线程执行被选择的 Tasks

执行完 DryRunBuildExecutionAction 后,我们再回到 DefaultBuildWorkExecutor 类的 execute 方法,在注释2处,会执行 executionActions 列表中的下一项,即第二项:SelectedTaskExecutionAction,我们看看它的 execute 方法,如下所示:

public void execute(BuildExecutionContext context, Collection<? super Throwable> taskFailures) {
        
        ...

        taskGraph.addTaskExecutionGraphListener(new SelectedTaskExecutionAction.BindAllReferencesOfProjectsToExecuteListener());
        
        // 1、
        taskGraph.execute(taskFailures);
} 

可以看到,这里直接调用了 TaskExecutionGraphInternal 接口的实现类 DefaultTaskExecutionGraph 的 execute 方法去执行 Tasks,其关键源码如下所示:

public void execute(Collection<? super Throwable> failures) {
        ProjectExecutionServiceRegistry projectExecutionServices = new ProjectExecutionServiceRegistry();

        try {
            // 使用线程池执行 Task
            this.executeWithServices(projectExecutionServices, failures);
        } finally {
            projectExecutionServices.close();
        }
} 

这里又继续调用了 DefaultTaskExecutionGraph 的 executeWithServices 方法使用线程池并行执行 Task,其核心源码如下所示:

private void executeWithServices(ProjectExecutionServiceRegistry projectExecutionServices, Collection<? super Throwable> failures) {
        
        ...
        
        this.ensurePopulated();
        
        ...

        try {
            // 1
            this.planExecutor.process(this.executionPlan, failures, new DefaultTaskExecutionGraph.BuildOperationAwareExecutionAction(this.buildOperationExecutor.getCurrentOperation(), new DefaultTaskExecutionGraph.InvokeNodeExecutorsAction(this.nodeExecutors, projectExecutionServices)));
            LOGGER.debug("Timing: Executing the DAG took " + clock.getElapsed());
        } finally {
            ...
        }
} 

最终,这里是 调用了 PlanExecutor 接口的实现类 DefaultPlanExecutor 的 process 方法,如下所示:

public void process(ExecutionPlan executionPlan, Collection<? super Throwable> failures, Action<Node> nodeExecutor) {
        ManagedExecutor executor = this.executorFactory.create("Execution worker for '" + executionPlan.getDisplayName() + "'");

        try {
            WorkerLease parentWorkerLease = this.workerLeaseService.getCurrentWorkerLease();
            // 1、开始使用线程池异步执行 tasks
            this.startAdditionalWorkers(executionPlan, nodeExecutor, executor, parentWorkerLease);
            (new DefaultPlanExecutor.ExecutorWorker(executionPlan, nodeExecutor, parentWorkerLease, this.cancellationToken, this.coordinationService)).run();
            this.awaitCompletion(executionPlan, failures);
        } finally {
            executor.stop();
        }
} 

在注释1处,使用了线程池去异步执行 tasks,如下所示:

private void startAdditionalWorkers(ExecutionPlan executionPlan, Action<? super Node> nodeExecutor, Executor executor, WorkerLease parentWorkerLease) {
        LOGGER.debug("Using {} parallel executor threads", this.executorCount);

        for(int i = 1; i < this.executorCount; ++i) {
            executor.execute(new DefaultPlanExecutor.ExecutorWorker(executionPlan, nodeExecutor, parentWorkerLease, this.cancellationToken, this.coordinationService));
        }
} 

需要了解的是,用于执行 Task 的线程默认是 8 个线程。这里使用线程池执行了 DefaultPlanExecutor 的 ExecutorWorker,在它的 run 方法中最终会调用到 DefaultBuildOperationExecutor 的 run 方法去执行 Task。但在 Task 执行前还需要做一些预处理。

3)、Task 执行前的一些预处理

在 DefaultBuildOperationExecutor 的 run 方法中只做了两件事,这里我们只需大致了解下即可,如下所示:

  • 1、首先,回调 TaskExecutionListener#beforeExecute。
  • 2、然后,链式执行一系列对 Task 的通用处理,其具体的处理如下所示:
    • 1)、CatchExceptionTaskExecuter#execute:增加 try catch,避免执行过程中发生异常。
    • 2)、ExecuteAtMostOnceTaskExecuter#execute:判断 task 是否执行过。
    • 3)、SkipOnlyIfTaskExecuter#execute:判断 task 的 onlyif 条件是否满足执行。
    • 4)、SkipTaskWithNoActionsExecuter#execute:跳过没有 action 的 task,如果没有 action 说明 task 不需要执行。
    • 5)、ResolveTaskArtifactStateTaskExecuter#execute:设置 artifact 的状态。
    • 6)、SkipEmptySourceFilesTaskExecuter#execut:跳过设置了 source file 且 source file 为空的 task,如果 source file 为空则说明 task 没有需要处理的资源。
    • 7)、ValidatingTaskExecuter#execute:确认 task 是否可以执行。
    • 8)、ResolveTaskOutputCachingStateExecuter#execute:处理 task 输出缓存。
    • 9)、SkipUpToDateTaskExecuter#execute:跳过 update-to-date 的 task。
    • 10)、ExecuteActionsTaskExecuter#execute:用来真正执行 Task 的 executer。

可以看到,在真正执行 Task 前需要经历一些通用的 task 预处理,最后才会调用 ExecuteActionsTaskExecuter 的 execute 方法去真正执行 Task。

4)、真正执行 Task

public TaskExecuterResult execute(final TaskInternal task, final TaskStateInternal state, final TaskExecutionContext context) {
        final ExecuteActionsTaskExecuter.TaskExecution work = new ExecuteActionsTaskExecuter.TaskExecution(task, context, this.executionHistoryStore, this.fingerprinterRegistry, this.classLoaderHierarchyHasher);
        // 使用 workExecutor 对象去真正执行 Task。
        final CachingResult result = (CachingResult)this.workExecutor.execute(new AfterPreviousExecutionContext() {
            public UnitOfWork getWork() {
                return work;
            }

            public Optional<String> getRebuildReason() {
                return context.getTaskExecutionMode().getRebuildReason();
            }

            public Optional<AfterPreviousExecutionState> getAfterPreviousExecutionState() {
                return Optional.ofNullable(context.getAfterPreviousExecution());
            }
        });
        ...
} 

可以看到,这里使用了 workExecutor 对象去真正执行 Task,在执行时便会回调 ExecuteActionsTaskExecuter.TaskExecution 内部类的 execute 方法,其实现源码如下所示:

 public WorkResult execute(@Nullable InputChangesInternal inputChanges) {
            this.task.getState().setExecuting(true);

            WorkResult var2;
            try {
                ExecuteActionsTaskExecuter.LOGGER.debug("Executing actions for {}.", this.task);
               
                // 1、回调 TaskActionListener 的 beforeActions 接口。
                ExecuteActionsTaskExecuter.this.actionListener.beforeActions(this.task);
               
                // 2、内部会遍历执行所有的 Action。 
                ExecuteActionsTaskExecuter.this.executeActions(this.task, inputChanges);
                var2 = this.task.getState().getDidWork() ? WorkResult.DID_WORK : WorkResult.DID_NO_WORK;
            } finally {
                this.task.getState().setExecuting(false);
               
               // 3、回调 TaskActionListener.afterActions。 
               ExecuteActionsTaskExecuter.this.actionListener.afterActions(this.task);
            }

            return var2;
} 

在 ExecuteActionsTaskExecuter.TaskExecution 内部类的 execute 方法中做了三件事,如下所示:

  • 1)、回调 TaskActionListener 的 beforeActions。
  • 2)、内部会遍历执行 Task 所有的 Action。 => 执行 Task 就是执行其中包含的所有 Action,这便是 Task 的本质。
  • 3)、回调 TaskActionListener.afterActions。

当执行完 Task 的所有 Action 之后,便会最终在 DefaultBuildOperationExecutor 的 run 方法中回调 TaskExecutionListener.afterExecute 来标识 Task 最终执行完成。

5、Finished

在 Finished 阶段仅仅只干了一件比较重要的事情,就是 回调 buildListener 的 buildFinished 接口,如下所示:

private void finishBuild(String action, @Nullable Throwable stageFailure) {
        if (this.stage != DefaultGradleLauncher.Stage.Finished) {
            ...

            try {
               
                 this.buildListener.buildFinished(buildResult);
            } catch (Throwable var7) {
                failures.add(var7);
            }

            this.stage = DefaultGradleLauncher.Stage.Finished;
            
            ...
        }
} 

6、小结

从对 Gradle 执行 Task 的分析中可以看到 Task 的本质,其实就是一系列的 Actions。深入了解了 Gradle 的构建流程之后,我们再重新回归头来看看开始的那一张 Gradle 的构建流程图,如下所示:

此外,zhangyi54 做的 Gradle原理动画讲解 将枯燥的原理流程视觉化了,值得推荐。需要注意区分的是,动画讲解的 Gradle 版本比较老,但是主要的原理还是一致的,可以放心观看。

注意:build.gradle 脚本的 buildscript 会在脚本的其它内容之前执行。

四、关于 Gradle 中依赖实现的原理

1、通过 MethodMissing 机制,间接地调用 DefaultDependencyHandler 的 add 方法去添加依赖

我们都知道,DependencyHandler 是用来进行依赖声明的一个类,但是在 DependencyHandler 并没有发现 implementation(), api(), compile() 这些方法,这是怎么回事呢?

其实这是 通过 MethodMissing 机制,间接地调用 DependencyHandler 的实现 DefaultDependencyHandler 的 add() 方法将依赖添加进去的。

那么,methodMissing 又是什么?

它是 Groovy 语言的一个重要特性,这个特性允许在运行时 catch 对于未定义方法的调用。

而 Gradle 对这个特性进行了封装,一个类要想使用这个特性,只要实现 MixIn 接口即可。代码如下所示:

public interface MethodMixIn {
    MethodAccess getAdditionalMethods();
} 

2、不同的依赖声明,其实是由不同的转换器进行转换的

例如如下两个依赖:

  • 1)、DependencyStringNotationConverter: 将类似于 ‘androidx.appcompat:appcompat:1.1.0’ 的常规依赖声明转换为依赖。
  • 2)、DependencyProjectNotationConverter: 将类似于 ‘project(":mylib")’ 的依赖声明转换为依赖。

此外,除了 project 依赖,其它的依赖最终都会转换为 SelfResolvingDependency, 而这个依赖可以实现自我解析。

3、Project 依赖的本质是 Artifacts 依赖,也即 产物依赖。

4、什么是 Gradle 中的 Configuration?

implementation 与 api。但是,承担 moudle 或者 aar 依赖仅仅是 Configuration 表面的任务,依赖的实质就是获取被依赖项构建过程中产生的 Artifacts。

而对于大部分的 Task 来说,执行完之后都会有产物输出。而在各个 moudle 之间 Artifacts 的产生与消费则构成了一个完整的生产者-消费者模型。

5、Task 是如何发布自己 Artifacts 的?

每一个 task 都会调用 VariantScopeImpl 中的 publishIntermediateArtifact 方法将自己的产物进行发布,最后会调用到 DefaultVariant 的 artifact 方法对产物进行发布。

参考链接:


1、Android Gradle Plugin V3.6.2 源码

2、Gradle V5.6.4 源码

3、Android Plugin DSL Reference

4、Gradle DSL Reference

5、designing-gradle-plugins

6、android-training => gradle

7、连载 | 深入理解Gradle框架之一:Plugin, Extension, buildSrc

8、连载 | 深入理解gradle框架之二:依赖实现分析

9、连载 | 深入理解gradle框架之三:artifacts的发布

10、Android Gradle Plugin 源码解析(上)

11、Android Gradle Plugin 源码解析(下)

12、Gradle 庖丁解牛(构建源头源码浅析)

13、Gradle 庖丁解牛(构建生命周期核心委托对象创建源码浅析)

很感谢您阅读这篇文章,希望您能将它分享给您的朋友或技术群,这对我意义重大。

希望我们能成为朋友,在 Github、掘金上一起分享知识。

最后

按照国际惯例,给大家分享一套十分好用的Android进阶资料:《全网最全Android开发笔记》。

整个笔记一共8大模块、729个知识点,3382页,66万字,可以说覆盖了当下Android开发最前沿的技术点,和阿里、腾讯、字节等等大厂面试看重的技术。

图片

图片

因为所包含的内容足够多,所以,这份笔记不仅仅可以用来当学习资料,还可以当工具书用。

如果你需要了解某个知识点,不管是Shift+F 搜索,还是按目录进行检索,都能用最快的速度找到你要的内容。

相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照整个知识体系编排的。

(一)架构师必备Java基础

1、深入理解Java泛型

2、注解深入浅出

3、并发编程

4、数据传输与序列化

5、Java虚拟机原理

6、高效IO

……

图片

(二)设计思想解读开源框架

1、热修复设计

2、插件化框架设计

3、组件化框架设计

4、图片加载框架

5、网络访问框架设计

6、RXJava响应式编程框架设计

……

图片

(三)360°全方位性能优化

1、设计思想与代码质量优化

2、程序性能优化

  • 启动速度与执行效率优化
  • 布局检测与优化
  • 内存优化
  • 耗电优化
  • 网络传输与数据储存优化
  • APK大小优化

3、开发效率优化

  • 分布式版本控制系统Git
  • 自动化构建系统Gradle

……

图片

(四)Android框架体系架构

1、高级UI晋升

2、Android内核组件

3、大型项目必备IPC

4、数据持久与序列化

5、Framework内核解析

……

图片

(五)NDK模块开发

1、NDK开发之C/C++入门

2、JNI模块开发

3、Linux编程

4、底层图片处理

5、音视频开发

6、机器学习

……

图片

(六)Flutter学习进阶

1、Flutter跨平台开发概述

2、Windows中Flutter开发环境搭建

3、编写你的第一个Flutter APP

4、Flutter Dart语言系统入门

……

图片

(七)微信小程序开发

1、小程序概述及入门

2、小程序UI开发

3、API操作

4、购物商场项目实战

……

图片

(八)kotlin从入门到精通

1、准备开始

2、基础

3、类和对象

4、函数和lambda表达式

5、其他

……

图片

好啦,这份资料就给大家介绍到这了,有需要详细文档的小伙伴,可以微信扫下方二维码领取哈~

img

Cocos Creator自定义构建模板:原生工程配置自动化实战指南 在跨平台游戏与应用开发中,原生工程配置是连接引擎与操作系统的重要桥梁。其核心原理在于构建系统通过模板化机制,将通用配置与项目特定需求分离,实现自动化文件生成与变量替换。这一技术的核心价值在于解决了手动修改原生工程文件易被覆盖的痛点,确保了配置的一致性与可维护性。在实际应用场景中,开发者常需集成第三方SDK、配置应用签名、管理依赖库或添加平台特定权限。通过自定义构建模板,可以将这些**AndroidManifest.xml**和**build.gradle**等关键文件的修改固化下来,实现构建流程的标准化与自 阅读详情

相关推荐

自动化测试框架设计:从技术选型到企业级实践,构建面试与工程壁垒

自动化测试是软件工程中保障质量、提升交付效率的核心实践。其原理在于通过脚本或框架模拟用户操作,自动执行测试用例并进行结果验证,从而替代重复的人工测试。这项技术的核心价值在于实现快速反馈、提高测试覆盖率并支持持续集成与持续交付(CI/CD)。在微服务、数据驱动等现代应用场景中,一个设计良好的自动化测试框架能够有效管理测试用例、数据、执行与报告,是构建稳定交付流水线的关键。本文聚焦于如何基于主流开源技术栈,如RestAssured、TestNG与Allure,进行框架选型与整合,并深入探讨数据驱动、报告定制等工

weixin_34241036的博客 367

深入学习-Gradle-自动化构建技术(五)Gradle-插件架构实现原理剖析-—-上

我这里整理了一份完整的学习思维以及Android开发知识大全PDF。当然实践出真知,即使有了学习线路也要注重实践,学习过的内容只有结合实操才算是真正的掌握。《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取![外链图片转存中…(img-mSOIc2aX-1713678439100)][外链图片转存中…(img-9FHVAp8l-1713678439101)][外链图片转存中…(img-hMazEUmE-1713678439102)]

2401_84149814的博客 1191

Unity手游《最后一战》源码深度剖析:从架构设计到性能优化的实战指南

在Unity游戏开发中,架构设计与性能优化是构建高质量商业项目的核心。架构层面,通过模块化设计、管理器模式(如UIManager、PoolManager)和事件驱动机制,实现系统间的高内聚低耦合,这是保障代码可维护性和可扩展性的基础原理。其技术价值在于提升团队协作效率,降低后期维护成本。在移动端射击类等资源密集型游戏中,性能优化直接决定用户体验与项目成败,涉及资源管理、渲染优化和CPU效率等多个关键场景。具体到资源管理,成熟项目会采用AssetBundle或Addressables系统进行动态加载与生命周期

weixin_34026276的博客 321

深度探索 Gradle 自动化构建技术(一、Gradle 核心配置篇)

Gradle 核心配置

QG 1281

高效开发Gradle架构设计图解/掌握项目工程自动化技巧(精通篇一)

Gradle是一个强大的自动化构建工具,它通过灵活的构建脚本和丰富的插件生态系统,为Java和其他语言的项目提供了现代化的构建解决方案。无论您是构建简单的应用还是复杂的多模块项目,Gradle都能以其高效的性能和卓越的可扩展性满足您的需求。本文将引导您深入了解Gradle的核心概念、最佳实践和高级技巧,帮助您充分利用这一强大的工具。

肖哥弹架构博客 1315

游戏大厂技术全景(42):程序化内容生成——地形、关卡、任务与“可控的随机“

第零至第七部分 · 主线 01~30(全景地图 → 引擎地基 → 客户端 → 渲染与表现 → 网络与服务器 → 生产与效能 → 质量与安全 → 运营与前沿)第八部分 · 进阶专题 31 物理与碰撞 / 32 技能与战斗框架 / 33 游戏 AI / 34 着色器编译与 PSO 卡顿 / 35 网络传输层与弱网对抗 / 36 匹配与排位算法 / 37 相机、输入与手感 / 38 音频技术 / 39 存档与数据演进 / 40 主机平台开发 / 41 UGC 与模组 /← 当前 / 43 动画进阶。

本博客聚焦游戏开发技巧、创业实战心得及大学热门课程干货。这里既有技术深度,也有思维广度,无论你是开发者、创业者还是高校学子,都能在这里找到适合自己的成长之路! 880

【灵魂七问】深度探索 Gradle 自动化构建技术(五、Gradle 插件架构实现原理剖析 — 下)...

前言 成为一名优秀的Android开发,需要一份完备的知识体系,在这里,让我们一起成长为自己所想的那样~。 五、AppPlugin 构建流程 为了能够查看 Android Gradle Plugin 与 Gradle 的源码,我们需要在项目中添加 android gradle plugin 依赖,如下所示: compile'com.android.tools.build:gradle:3.6.2...

IT元老的博客 924

Gradle 6.9.1二进制发行版完整实战指南

Gradle 的强大之处,不仅在于它能帮你更快地构建项目,更在于它倡导了一种模块化、可复用、可审计的现代工程实践。它用 Groovy DSL 实现了极高的表达效率;用 DAG 和缓存机制实现了智能调度与性能飞跃;用 configuration 和 locking 实现了依赖治理的标准化;用 Daemon 和 Worker API 实现了资源的最大化利用。掌握这些机制,你不仅能写出更好的构建脚本,更能设计出更健壮、更高效的工程体系。所以,下次当你敲下。

weixin_42315569的博客 433

【灵魂七问】深度探索 Gradle 自动化构建技术(五、Gradle 插件架构实现原理剖析 — 上)...

前言 成为一名优秀的Android开发,需要一份完备的知识体系,在这里,让我们一起成长为自己所想的那样~。 目前,Gradle 自动化技术越来越重要,也有许多同学已经能够制作出自己的 Gradle 插件,但是一直有一些 “梗” 遗留在我们脑海中,无时无刻不提醒着我们,你真的掌握了吗?例如,“梗1”:Gradle 插件的整体实现架构?我:...,“梗2”:Android Gradle 插件更新历史有...

IT元老的博客 506

RK806S PMIC电源管理芯片调试

当点按PWRON按键时,rk806上电开始启动bootloader和uboot,在执行到此处时检测到是由于按键启动的,所以0x5d的值是0x80,0x74的值是0x80,在一般情况下,mode都默认是none的,所以mode值为11,因此就进入了我们的if判断,若是由于按键启动的,则直接调用rk8xx_shutdown函数关掉,进入这个函数会看到实际是通过i2c给0x72寄存器的BIT(0)写1,使其关机。当SoC在12V电源已经接入,且处于关机状态时,使用PWRON按键开机,0x74的值则为0x80。

Y1anoohh的博客 2546

LSM6DSOW陀螺仪轮询驱动:从I2C/SPI配置到数据读取全解析

惯性测量单元(IMU)是现代嵌入式系统中实现姿态感知与运动跟踪的核心传感器,其通过加速度计和陀螺仪测量线性加速度与角速度。在嵌入式开发中,轮询(Polling)作为一种基础且可靠的通信方式,通过主循环主动查询传感器状态,简化了初始调试与验证流程。对于I2C/SPI接口的数字传感器,掌握寄存器配置、数据就绪标志检查及原始数据转换是通用的驱动方法,能够快速验证传感器工作状态并理解数据流。LSM6DSOW作为一款高性能6轴IMU,支持可配置的量程与输出数据速率,适用于无人机飞控、手持云台等高动态场景。本文以LSM

weixin_28728265的博客 560

【灵魂七问】深度探索 Gradle 自动化构建技术,想跳槽涨薪的必看

1)、新的 Lint 检查依赖项配置 增加了新的依赖项配置 lintPublish,并更改了原有 lintChecks 的行为,它们的作用分别如下所示: lintChecks:仅用于在本地构建项目时运行的 Lint 检查。 lintPublish:在已发布的 AAR 中启用 Lint 检查,这样使用此 AAR 的项目也会应用那些 Lint 检查。 其示例代码如下所示: dependencies { // Executes lint checks from the ‘:lint’ project at b

flutter001的博客 626

中科蓝讯蓝牙耳机SDK实战:从目录解析到消息处理框架

本文深入解析中科蓝讯蓝牙耳机SDK的目录结构与消息处理框架,提供从硬件驱动到蓝牙功能定制的实战指南。重点剖析SDK核心模块如消息队列机制、按键事件处理及电量报告优化,并分享二次开发中的调试技巧与避坑经验,助力开发者高效完成TWS耳机功能定制。

weixin_30335575的博客 389

获取Gradle Task耗时

获取task耗时

b1tb1t的博客 318

matplotlib三维画图之观察博弈论混合策略下纳什均衡点

参考链接: 博弈论入门20讲之第16讲-猜谜游戏-散户如何赢庄家 游戏背景: 我的期望是: 你的期望是: 使用Python的matplotlib画图: import numpy as np import matplotlib.pyplot as plt from mpl_toolkits.mplot3d import Axes3D fig = plt.figure() ax = Axes3D(fig) X = np.linspace(0, 1, 100) Y = np.linspace(0, 1,

敲代码的小风 1260

Docker “docker login“ 失败? 登录阿里云镜像仓库仓库 unauthorized: authentication required

文章目录听说你 login fail?来,让我们解决一下!登录,撒花 听说你 login fail? 美滋滋的在服务器上安装完成Docker应用,开始配置阿里云镜像加速?然后被阿里云官方文档蹂躏?别急别急,让我来拯救你。 我们先来看看 阿里云官方文档 Docker镜像基本操作: 好家伙,emmm… 上来直接就Login Succeeded,这不简直so easy?我们也来照着操作操作: docker@default-online:~$ docker login registry.cn-hangzhou.a

zhuzicc的博客 1万+

Linux 内核 DVB USB 驱动 GL861 设备支持清单详解:从 USB ID 到驱动源码

本篇文章围绕 Linux 内核文档 [dvb-usb-gl861-cardlist.rst](https://link.gitcode.com/i/c11dbe543049b9f5d64f43413333f939) 展开,它是内核媒体子系统(Media subsystem)中 GL861 USB2.0 DVB 设备驱动所支持的"硬件卡片清单"。文章将逐条解读这张 USB ID 表,并结合 [dri

gitblog_00629的博客 474

广东省道路工程检测练习试题(判断题)真题

按下式计算小于某粒径颗粒质量百分数:X=(A/B)x100( )49.、挖坑灌砂法适用于在现场测定基层(或底基层)、砂石路面及路基土的各种材料压实层的密度和压实度,但不适用于填石路堤等有大孔洞或大孔隙材料的压实度检测。95.挖坑灌砂法适用于在现场测定基层(或底基层)、砂石路面及路基土的各种材料压实层的密度和压实度,但不适用于填石路堤等有大孔洞或大孔隙材料的压实度检测( )107.以干密度 pd 为纵坐标,含水率w为横坐标,绘 pd-w 关系曲线,曲线上峰值点的纵、横坐标分别为最大干密度和最佳含水率( )

ZShy001238的博客 685

TreeInfotip:为你的项目目录树注入灵魂

TreeInfotip:为你的项目目录树注入灵魂 在软件开发的世界里,项目的目录结构往往是开发者们最先接触的部分。一个清晰、易于理解的目录结构不仅能提升开发效率,还能减少团队内部的沟通成本。然而,面对复杂的项目结构,尤其是那些历史悠久、命名不规范的“火葬场项目”,开发者们常常感到头疼。为了解决这一痛点,TreeInfotip应运而生。 项目介绍 TreeInfotip是一款基于IntelliJ平台...

gitblog_00689的博客 527

手把手教你学Simulink——基于Simulink的CLLC谐振变换器双向对称控制

本文介绍了一种基于Simulink的CLLC谐振变换器双向对称控制方法。针对CLLC拓扑的多阶谐振特性,采用固定频率、50%占空比、仅调节相位差的对称控制策略,实现了宽电压范围调节和全负载ZVS软开关。详细阐述了Simulink建模步骤,包括功率级搭建、相位差生成逻辑和双向切换实现,并通过仿真验证了稳态性能和双向无缝切换能力。该方法解决了传统PFM控制的不足,具有控制简单、效率高、易于工程实现的优势,适用于车载充电和储能系统等双向功率应用场景。

MHD0815的博客 302

6、业务影响分析(BIA)的数据收集与关键指标评估

本文全面解析了业务影响分析(BIA)的核心内容,包括数据收集、业务流程分析、信息系统与资产清单、人员与供应商依赖、影响声明、关键指标评估(如MTD、RTO、RPO)、威胁建模与风险分析等。通过详细的介绍和图表展示,帮助组织系统性地制定灾难恢复计划,以应对各种潜在风险,确保关键业务流程在灾难发生后能够快速恢复并持续运营。

moss5的博客 376

Maven Gradle SBT Mill Ivy Grape Leiningen Buildr构建工具

如果你是标准的Java/Android开发者新项目或追求现代体验,首选Gradle。大型保守型企业项目,强调规范和稳定,Maven依然是不二之选。如果你在JVM的某个特定语言生态中SBT(主流) 或Mill(如果你追求极简)。Leiningen。Gradle(本身就是Groovy写的) 或Grape(用于临时脚本)。如果你在维护老项目Ant项目:引入Ivy管理依赖。如果你追求极致的语言特性想在Java中用Ruby写构建脚本?试试Buildr。想在Scala中用纯Scala写构建脚本?

lanwp5302的博客 458

迈瑞BS系列全自动生化分析仪LIS协议接口手册(内含HL7和ASTM)

迈瑞BS系列全自动生化分析仪LIS协议接口手册,内含HL7和ASTM)

OBS Studio插件系统深度解析:从新手到高手的进阶指南

你是否曾经遇到过这样的困扰:直播画面平淡无奇,音频效果差强人意,场景切换生硬刻板?作为一款强大的开源直播软件,OBS Studio的真正威力往往隐藏在其丰富的插件生态中。本文将带你深入了解OBS插件系统的核心机制,通过"问题导向-解决方案-实践指南"的递进式结构,帮助你从基础用户成长为插件应用高手。 OBS Studio的插件系统是其功能扩展的灵魂所在,几乎所有自定义功能都通过插件模块实现。无论

gitblog_00942的博客 363

霍尼韦尔SoftMaster使用说明

霍尼韦尔SoftMaster使用说明

Devexpress16.2.6破解工具

Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具

AI时代Java程序员如何构建核心竞争力:从原理到实战的转型指南

在软件开发领域,自动化工具和人工智能辅助编程正逐渐改变开发范式。其核心原理在于通过模式识别和代码生成,提升基础编码环节的效率。这一技术进步带来的核心价值是解放开发者,使其能更专注于复杂系统设计、性能优化和架构决策等高阶任务。对于Java开发者而言,这意味着应用场景从传统的CRUD实现,转向对JVM原理、高并发处理、数据库深度优化及分布式系统等底层技术的驾驭。掌握这些硬核知识,结合AI工具进行高效学习和问题排查,成为构建不可替代技术护城河的关键。本文聚焦于如何利用AI应对面试与深化Java核心技术栈,帮助开发

chupu2979的博客 376

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

荣耀Magic视界技术深度解析:从SLAM原理到AR应用开发实战

增强现实(AR)技术通过将虚拟信息叠加到真实世界,正在重塑人机交互体验。其核心原理依赖于即时定位与地图构建(SLAM)技术,通过多传感器融合实时感知环境三维结构,并结合空间锚定技术实现虚拟内容的稳定放置。这项技术的工程价值在于打通了数字世界与物理世界的感知通道,为导航、教育、电商等垂直领域提供了全新的信息呈现与交互范式。在实际开发中,开发者需要掌握AR Engine SDK集成、平面检测、手势交互等关键技术,并应对SLAM跟踪稳定性、多设备适配和功耗优化等工程挑战。本文以荣耀Magic视界为具体案例,深入探

weixin_34060741的博客 1万+
上一篇: 【背上Jetpack之Fragment】你真的会用Fragment吗?Fragment常见问题以及androidx下Fragment的使用新姿势
下一篇: 3 分钟理解完全二叉树、平衡二叉树、二叉查找树
CV大师啊
博客等级 码龄5年 5984粉丝 15原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值