大家常说的agp其实就是Android-Gradle-Plugin的缩写,在项目中的配置就是在根目录下的build.gradle文件中的dependencies中配置的classpath “com.android.tools.build:gradle:xxx”,在根目录build.gradle的dependencies中配置好agp后就能在任意module的build.gradle文件中apply Android提供的各种plugin,常见的就有com.android.application和com.android.library,一般来说build.gradle中apply了com.android.application的module,使用assemblexxx执行构建命令打出来的产物就是一个apk,而apply了com.android.library的module的产物则是一个aar,当然,还可以直接apply java,这样的module表示是个纯java的模块,打出的产物是个jar。
现在的Android工程应该都是用Android-Studio来开发的了,也就是说基本都是基于gradle来构建的,应该也只有一些外包公司还会使用Eclipse + ADT来开发Android了。
任何基于gradle来构建的项目的基本结构都是根目录下有个settings.gradle,用来配置项目中的子模块的,使用include来引入子模块,如果子模块不在项目目录内,则可以在include之后使用project(":xxx").projectDir = new File("…")来指定外部module的路径,这一般是gradle项目多仓开发的配置方式。然后就是项目根目录下有个build.gradle,这个文件是用来配置整个项目的基本依赖,比如插件依赖,aar依赖的maven仓库地址等。一般配置结构如下:
// projectDir/build.gradle
apply plugin "..."
apply from "..."
buildscript {
repositories {
maven { url "xxxx" }
....
}
dependencies {
classpath "xxx"
...
}
}
subprojects {
repositories {
maven { url "xxx" }
...
}
}
allprojects {
...
}
这是groovy语言的写法,其实这个build.gradle文件会被编译成字节码去执行,而这些闭包则会编译成函数的实现。而闭包里面的闭包,则相当了函数调用了。
其中buildscript就是用来设置构建脚本所需依赖的仓库地址和引入插件的依赖,subprojects中用来配置每个module中的依赖的仓库地址,allprojects中则是配置config阶段的回调,可以在这个闭包中设置一些状态监听与module属性的配置。
还有一点特别重要的是项目根目录和每个module中都会有一个gradle.properties文件,这个文件中是配置一些key-value的,而构建脚本文件,就是所有的gradle文件,都是可以直接使用gradle.properties中的key-value配置,例如在gradle.properties中配置了DEV_MODE=true,则可在gradle文件中直接使用if (“true” == DEV_MODE) {xxx},但这里有个限制,gradle.properties中配置的value,在使用的时候,都会被当作字符串类型的,就算写的是数字或者true/false这样的bool值,也全会被当作是字符串。同时配置的key-value也全会被当作是常量,不可修改,因此配置KEY的名字时,一般用全大写。
由于根目录和每个module中都会有这样一个gradle.properties文件,因此会有读范围问题和覆盖问题,规则是子module可以读取本目录和父级目录下的gradle.properties中的key-value,而外部目录不能直接读取子目录下的,子目录下的gradle.properites文件中若有和父级目录文件中相同的key值,则子目录中的value值覆盖父级目录文件中的value值。在一个源码/aar切换的Andrid gradle项目中,可以用这种特性在根目录的gradle.properties中配置一些全局的参数,例如构建用的Andrid SDK版本号,构建工具版本号,最低支持的系统版本号等,而每个module的gradle.properties则可以为每个module配置其aar所需要的maven参数,例如groupId+artifactId+version。
一般来说只有壳module的build.gradle中会apply application插件,由此来打出一个apk供用户安装,由于前些年国内插件化盛行,而插件包一般也使用apk压缩格式,因此基本上插件的壳module也是apply的applicaiton插件。只要apply了application插件,就可配置很多构建apk相关的参数,一般配置如下 :
// apk壳module的build.gradle
apply plugin "com.android.application"
android {
compileSdkVersion xxx
buildToolsVersion xxx
signingConfigs {
xxx
}
defaultConfig {
applicationId xxx
ndk { xxx }
}
buildType {
debug {
xxx
}
release {
xxx
}
...
}
}
dependencies {
xxx
}
开发过gradle插件的都知道,一般来说插件工程都是一个java工程,并且插件名一般都是在工程的resources的META-INF.gradle-plugins中用一个以插件名为名的properties文件配置的,文件中只需要一行implementation-class=xxx,其中xxx表示插件的实现类的全类名。因此我们来到Android gradle plugin的源码工程,在tools/base/build-system/gradle-core下找到一个叫main的目录,main目录的resources中的META-INF.gradle-plugins目录下就有非常多的插件properties文件,这每个文件都是一个插件配置文件,也就是说每个文件名都表示一个插件名,其中就有com.android.application和com.android.library,也就是前面说到的apk壳module的依赖的插件和aar module依赖的插件。com.android.application.gradle中配置的implementation-class是com.android.build.gradle.AppPlugin,因此application插件的实现类就是AppPlugin。
AppPlugin继承自AbstractAppPlugin,而AbstractAppPlugin继承自BasePlugin,其实现了Plugin接口,其对apply方法的实现是调用basePluginApply方法。basePluginApply方法中的重点调用是
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 {
...
}
这里的if是判断是否使用新的dsl和api,一般来说新的gradle这是都是返回false从而走到if分支。在if里使用ThreadRecorder创建了三个线程任务。先来看看ThreadRecorder#record的实现
public void record(
@NonNull ExecutionType executionType,
@NonNull String projectPath,
@Nullable String variant,
@NonNull VoidBlock block) {
ProfileRecordWriter profileRecordWriter = ProcessProfileWriter.get();
GradleBuildProfileSpan.Builder currentRecord =
create(profileRecordWriter, executionType, null);
try {
block.call();
} catch (IOException e) {
throw new UncheckedIOException(e);
} finally {
write(profileRecordWriter, currentRecord, projectPath, variant);
}
}
首先是调用了create方法,设置各种id,以例以后能够方便找回线程的一些变量,接着调用block.call方法,block是方法传进来的一个类似闭包的方法,最后调用write方法,write方法中调用了ProcessProfileWRiter的writeRecord方法,是将executionRecord调用build创建出来的GradleBuildProfileSpan添加到span队列中,而这个队列会在ProcessProfileWriter的finish方法中将相关信息全部写到日志文件中去。
再回头看BasePlugin中的逻辑,threadRecorder.record的逻辑也很清楚了,就是要调用最后一个lambda参数,这里这三个分别是configureProject、configureExtension和createTaskts。
首先是configureProject,主要逻辑是创建了一个AndroidBuilder对象和初始化了DataBindingBuilder成员变量,AndroidBuilder对象最后用于构建GlobalScope对象。在configureProject方法中还调用了project.getPlugins().apply(JavaBasePlugin.class),这是为了让这个project可以使用java的插件,JavaBasePlugin也是实现了Plugin接口,并且在其apply方法中又apply了BasePlugin插件,BasePlugin的apply方法中主要就是给project配置上了打包上传task和assemble task。JavaBasePlugin中还给project配置了一些java工程应该有的能力,
public void apply(ProjectInternal project) {
project.getPluginManager().apply(BasePlugin.class);
project.getPluginManager().apply(ReportingBasePlugin.class);
JavaPluginConvention javaConvention = this.addExtensions(project);
this.configureSourceSetDefaults(javaConvention);
this.configureCompileDefaults(project, javaConvention);
this.configureJavaDoc(project, javaConvention);
this.configureTest(project, javaConvention);
this.configureBuildNeeded(project);
this.configureBuildDependents(project);
this.configureSchema(project);
this.bridgeToSoftwareModelIfNecessary(project);
this.configureVariantDerivationStrategy(project);
}
看这个方法的实现就大概知道java工程要有哪些能力了,首先肯定是source,也就是src目录了,configureSourceSetDefaults中配置了依赖方式,例如compile、implementation、runtime、compileOnly等等,设置这些依赖方式的可见性,其实就是是否可以使用这些依赖方式。还配置了src下的main/java为类文件目录,src下的main/resources为资源目录,调用configureOutputDirectoryForSourceSet方法配置编译的输出路径,调用createProcessResourcesTask创建资源处理task,调用createCompileJavaTask创建java编译task,调用createClassesTask设置这些编译task的依赖关系。JavaBasePlugin的apply中的configureJavaDoc和configureTest就很简单了,就是为了配置javadoc和test的,configureBuildNeeded方法只是让buildNeeded task依赖build task。下面的configureBuildDependents就很重要了,注册了buildDependents task,其action中是让buildDependents依赖于build,也就是build之后才能执行buildDependents。
再回到BasePlugin中来,configureProject的最后使用gradle.addBuildListener注册了构建监听器,在buildFinished回调中去关闭所有的进程。
第二个线程任务是执行configureExtension,这里创建了buildTypeContainer、productFlavorContainer、signingConfigContainer和buildOutputs四个NamedDomainObjectContainer容器,分别表示构建类型、变体、签名以及构建输出,用这四个容器构建了一个extension对象,并用这个extention创建了variantFactory,也就是变体工厂,Android项目是可以配置变体的,比如应用于打渠道包。又用variantFactory和extension创建了taskManager对象,最后再加上taskManager创建了variantManager对象,这里调用了createExtension来创建extension对象,调用createVariantFactory创建variantFactory对象,调用createTaskManager创建taskManager对象,这三个方法都是在AbstractAppPlugin中实现的。
最后一个线程任务是createTasks方法,主要分为两部分,分别是createTasksBeforeEvaluate和createAndroidTasks,前者是构建前的一些准备,后者才是真正的构建task。createAndroidTasks方法中只需要关注taskManager的相关调用,configureCustomLintChecks用于配置自定义的lint检查,addDataBindingDependenciesIfNecessary主要是适配databinding,configureGlobalLintTask也是lint相关,createAnchorAssembleTasks就是为我们配置assembleXXX的相关task,也就是各种构建命令,这里会分变体构建,也是多渠道的核心。真正创建task的是BasePlugin#createAndroidTasks方法中的variantManager.createAndroidTasks,其中为每个变体调用了createTasksForVariantData方法,这个createTasksForVariantData方法判断了变体类型,如果不是测试类型,就调用taskManager.createTasksForVariantScope(variantScope),这里又回到了taskManager,ApplicationTaskManager的实现是为变体VariantScope对象创建了非常多的task。
在循环遍历前首先判断变体集合是否为空,为空就先调用populateVariantDataList计算出所有的变体。查看variantScopes的使用情况,发现只有在populateVariantDataList中有add,而populateVariantDataList只有在createAndroidTasks中有调用,因此第一次肯定为空,所以第一次肯定要调populateVariantDataList,在其中调用了configureDependencies,有一行
if (globalScope.getProjectOptions().get(BooleanOption.ENABLE_JETIFIER)) {
AndroidXDepedencySubstitution.replaceOldSupportLibraries(project);
}
这个是判断是否开启了androidx的jetifier插件,如果是要将所有的support依赖替换成Androidx的依赖。configureDependencies中使用dependencies注册了非常多的Transform,基本上为每种依赖类型都配置了一种Tranform来处理这种依赖。