在如今这个时代,仅仅会简单的页面开发已经很难找到好工作了。
所以在实习的业余时间,我开始学习Android的Gradle构建流程,并且想将此心路历程整理为博客,方便加深理解。
简述
在本文,我将给大家介绍BasePlugin中configureProject、configureExtension、createTasks这三个回调方法的主要流程
PS:本文基于Gradle 3.2.1版本
BasePlugin.configureProject
private void configureProject() {
final Gradle gradle = project.getGradle();
ObjectFactory objectFactory = project.getObjects();
extraModelInfo = new ExtraModelInfo(project.getPath(), projectOptions, project.getLogger());
sdkHandler = new SdkHandler(project, getLogger());
//如果gradle没有设置的离线,如果需要下载SDK的时候,会在这里下载SDK
if (!gradle.getStartParameter().isOffline()
&& projectOptions.get(BooleanOption.ENABLE_SDK_DOWNLOAD)) {
SdkLibData sdkLibData = SdkLibData.download(getDownloader(), getSettingsController());
sdkHandler.setSdkLibData(sdkLibData);
}
// AndroidBuilder用于存放构建中所需要获取的数据,在构建的步骤中使用他们
AndroidBuilder androidBuilder =
new AndroidBuilder(
project == project.getRootProject() ? project.getName() : project.getPath(),
creator,
new GradleProcessExecutor(project),
new GradleJavaProcessExecutor(project),
extraModelInfo.getSyncIssueHandler(),
extraModelInfo.getMessageReceiver(),
getLogger(),
isVerbose());
//DataBinding的Builder,DataBinding相关的Task就是从这里取出数据
dataBindingBuilder = new DataBindingBuilder();
dataBindingBuilder.setPrintMachineReadableOutput(
SyncOptions.getErrorFormatMode(projectOptions) == ErrorFormatMode.MACHINE_PARSABLE);
//如果有被移除的配置,则report
if (projectOptions.hasRemovedOptions()) {
androidBuilder
.getIssueReporter()
.reportWarning(Type.GENERIC, projectOptions.getRemovedOptionsErrorMessage());
}
//如果有被废弃的配置,则report
if (projectOptions.hasDeprecatedOptions()) {
extraModelInfo
.getDeprecationReporter()
.reportDeprecatedOptions(projectOptions.getDeprecatedOptions());
}
//如果有实验性的配置,则遍历实验性的配置,然后report
if (!projectOptions.getExperimentalOptions().isEmpty()) {
projectOptions
.getExperimentalOptions()
.forEach(extraModelInfo.getDeprecationReporter()::reportExperimentalOption);
}
// Enforce minimum versions of certain plugins
GradlePluginUtils.enforceMinimumVersionsOfPlugins(
project, androidBuilder.getIssueReporter());
// Apply the Java plugin
// JavaBasePlugin很重要,之后会介绍
project.getPlugins().apply(JavaBasePlugin.class);
//...省略一波
// call back on execution. This is called after the whole build is done (not
// after the current project is done).
// This is will be called for each (android) projects though, so this should support
// being called 2+ times.
gradle.addBuildListener(
new BuildListener() {
@Override
public void buildStarted(@NonNull Gradle gradle) {
BuildableArtifactImpl.Companion.disableResolution();
}
@Override
public void settingsEvaluated(@NonNull Settings settings) {}
@Override
public void projectsLoaded(@NonNull Gradle gradle) {}
@Override
public void projectsEvaluated(@NonNull Gradle gradle) {}
@Override
public void buildFinished(@NonNull BuildResult buildResult) {
// Do not run buildFinished for included project in composite build.
if (buildResult.getGradle().getParent() != null) {
return;
}
sdkHandler.unload();
threadRecorder.record(
ExecutionType.BASE_PLUGIN_BUILD_FINISHED,
project.getPath(),
null,
() -> {
WorkerActionServiceRegistry.INSTANCE
.shutdownAllRegisteredServices(
ForkJoinPool.commonPool());
//在finish的回调里面清理PreDexCache的缓存
PreDexCache.getCache()
.clear(
FileUtils.join(
project.getRootProject().getBuildDir(),
FD_INTERMEDIATES,
"dex-cache",
"cache.xml"),
getLogger());
Main.clearInternTables();
});
}
});
gradle.getTaskGraph()
.addTaskExecutionGraphListener(
taskGraph -> {
for (Task task : taskGraph.getAllTasks()) {
if (task instanceof TransformTask) {
Transform transform = ((TransformTask) task).getTransform();
if (transform instanceof DexTransform) {
//将PreDexCache加载进TransformerTask中
PreDexCache.getCache()
.load(
FileUtils.join(
project.getRootProject()
.getBuildDir(),
FD_INTERMEDIATES,
"dex-cache",
"cache.xml"));
break;
}
}
}
});
//添加lint classpath的配置
createLintClasspathConfiguration(project);
}
总结一下:
-
进行各种版本检查
-
创建
AndroidBuilder和DataBindingBuilder用于之后构建获取数据 -
apply
JavaBasePlugin插件 -
getGradle().addBuildListener的finish回调中进行PreDexCache清理 -
getGradle().getTaskGraph().addTaskExecutionGraphListener中,将PreDexCache添加进TransformTask流程都比较好理解,接下来我们分析
JavaBasePlugin。分析一个Plugin,自然要从最重要的apply方法看起
JavaBasePlugin.apply
@Override
public void apply(final ProjectInternal project) {
project.getPluginManager().apply(BasePlugin.class);
project.getPluginManager().apply(ReportingBasePlugin.class);
JavaPluginConvention javaConvention = addExtensions(project);
//这里比较重要
configureSourceSetDefaults(javaConvention);
//获取sourceCompatibility和targetCompatibility
configureCompileDefaults(project, javaConvention);
//其余几个有兴趣大家都可以看看,基本都是注册Task
configureJavaDoc(project, javaConvention);
configureTest(project, javaConvention);
configureBuildNeeded(project);
configureBuildDependents(project);
configureSchema(project);
bridgeToSoftwareModelIfNecessary(project);
configureVariantDerivationStrategy(project);
}
从这里可以看到,都是一些配置方法,用于注册Task。这里我们着重分析configureSourceSetDefaults ,其他方法大家有兴趣可以私下去看
JavaBasePlugin.configureSourceSetDefaults
private void configureSourceSetDefaults(final JavaPluginConvention pluginConvention) {
final Project project = pluginConvention.getProject();
pluginConvention.getSourceSets().all(new Action<SourceSet>() {
@Override
public void execute(final SourceSet sourceSet) {
ConventionMapping outputConventionMapping = ((IConventionAware) sourceSet.getOutput()).getConventionMapping();
ConfigurationContainer configurations = project.getConfigurations();
//这里定义了sourceSet里面各种需要用到的字段,例如runtimeClasspath、annotationProcessor、implementation等
defineConfigurationsForSourceSet(sourceSet, configurations, pluginConvention);
//这里定义了sourceSet获取数据的源文件夹
definePathsForSourceSet(sourceSet, outputConventionMapping, project);
//创建资源处理Task,将源资源文件复制到target目录中
createProcessResourcesTask(sourceSet, sourceSet.getResources(), project);
//创建JavaCompileTask,在JavaCompile中调用Compiler<JavaCompileSpec>进行编译,这里是支持增量的
Provider<JavaCompile> compileTask = createCompileJavaTask(sourceSet, sourceSet.getJava(), project);
//创建classesTask, task的名字由sourceSet返回
createClassesTask(sourceSet, project);
//创建Java编译输出的文件夹
JvmPluginsHelper.configureOutputDirectoryForSourceSet(sourceSet, sourceSet.getJava(), project, compileTask, compileTask.map(new Transformer<CompileOptions, JavaCompile>() {
@Override
public CompileOptions transform(JavaCompile javaCompile) {
return javaCompile.getOptions();
}
}));
}
});
}
definePathsForSourceSet
private void definePathsForSourceSet(final SourceSet sourceSet, ConventionMapping outputConventionMapping, final Project project) {
outputConventionMapping.map("resourcesDir", new Callable<Object>() {
@Override
public Object call() {
String classesDirName = "resources/" + sourceSet.getName();
return new File(project.getBuildDir(), classesDirName);
}
});
sourceSet.getJava().srcDir("src/" + sourceSet.getName() + "/java");
sourceSet.getResources().srcDir("src/" + sourceSet.getName() + "/resources");
}
如此一分析,在哪里配置sourceSet、sourceSet资源文件夹有哪些、哪里创建JavaCompile的Task等等问题就迎刃而解了。
BasePlugin.configureExtension
private void configureExtension() {
// ======================= 1.创建三个容器集合 ===========================
//...
final NamedDomainObjectContainer<BuildType> buildTypeContainer =...;
final NamedDomainObjectContainer<ProductFlavor> productFlavorContainer =...;
final NamedDomainObjectContainer<SigningConfig> signingConfigContainer =...;
final NamedDomainObjectContainer<BaseVariantOutput> buildOutputs =...;
// ================== 2. 创建各种对象 ==============================
project.getExtensions().add("buildOutputs", buildOutputs);
sourceSetManager = createSourceSetManager();
extension =
createExtension(....);
globalScope.setExtension(extension);
variantFactory = createVariantFactory(globalScope, extension);
taskManager =
createTaskManager(...);
variantManager =
new VariantManager(...);
registerModels(registry, globalScope, variantManager, extension, extraModelInfo);
// ========== 3. 将三个容器集合添加到 variantManager 中 ====================
signingConfigContainer.whenObjectAdded(variantManager::addSigningConfig);
buildTypeContainer.whenObjectAdded(
buildType -> {
SigningConfig signingConfig =
signingConfigContainer.findByName(BuilderConstants.DEBUG);
buildType.init(signingConfig);
variantManager.addBuildType(buildType);
});
productFlavorContainer.whenObjectAdded(variantManager::addProductFlavor);
// ....
// create default Objects, signingConfig first as its used by the BuildTypes.
//默认构建配置,创建一些buildType之类的,有兴趣可以去看看
variantFactory.createDefaultComponents(
buildTypeContainer, productFlavorContainer, signingConfigContainer);
}
总结一下,这里主要做了三件事情:
- 创建了三个
NamedDomainObjectContainer,也就是三个容器集合。分别对应着BuildType(构建类型)、ProductFlavor(产品风味)和 SigningConfig(签署设置) - 创建一系列的
extension、TaskManager、VariantManager等等。其中VariantManager是用于管理 variant,variant意思为变体,也就是我们经常看见的AAARelease、AAADebug、BBBRelease、BBBDebug等。 - 将三个容器集合添加到
VariantManager中,BuildType、ProductFlavor和SigningConfig都在variantManager对象中,由其来统一管理。
接下来我们分析第二部分,毕竟重要的是createExtension 、createVariantFactory以及 createTaskManager。这三个方法并不是在BasePlugin中实现的,而是在AbstractAppPlugin里。其中createExtension在之前的文章中已经分析过了,是用来创建“android”这个extentsion的。
createTaskManager
@NonNull
@Override
protected TaskManager createTaskManager(
@NonNull GlobalScope globalScope,
@NonNull Project project,
@NonNull ProjectOptions projectOptions,
@NonNull DataBindingBuilder dataBindingBuilder,
@NonNull AndroidConfig androidConfig,
@NonNull SdkHandler sdkHandler,
@NonNull ToolingModelBuilderRegistry toolingRegistry,
@NonNull Recorder recorder) {
return new ApplicationTaskManager(...);
}
这里创建了ApplicationTaskManager,在它的构造方法中定义了许许多多的Task,用于Android的构建。
createVariantFactory
@NonNull
@Override
protected ApplicationVariantFactory createVariantFactory(
@NonNull GlobalScope globalScope,
@NonNull AndroidConfig androidConfig) {
return new ApplicationVariantFactory(globalScope, androidConfig);
}
这里创建了ApplicationVariantFactory,里面利用Density、ABI、Language这三种属性创建了variantData。最后会生成一个splits集合,存放这三种属性组合的笛卡尔积,后面用于生成ApkData(里面包括了生成Apk文件的信息)。
总结一下:
1. 我们知道了BuildType、ProductFlavor和SigningConfig是在哪里定义的、由什么对象进行管理
2. `Extension`、`TaskManager`、`ApplicationVariantFactory`在哪里被初始化,同时里面大概做了什么事情。
后面系列会对TaskManager中的一些Task作简单的分析
BasePlugin.createTasks
private void createTasks() {
threadRecorder.record(
ExecutionType.TASK_MANAGER_CREATE_TASKS,
project.getPath(),
null,
() -> taskManager.createTasksBeforeEvaluate());
project.afterEvaluate(
project -> {
sourceSetManager.runBuildableArtifactsActions();
threadRecorder.record(
ExecutionType.BASE_PLUGIN_CREATE_ANDROID_TASKS,
project.getPath(),
null,
() -> createAndroidTasks());
});
}
这里以 Evaluate 为界限,分为两个阶段:Evaluate之前和Evaluate之后。Evaluate可以简单理解为每一个build.gradle解析执行之前和之后。
Evaluate之前:taskManager.createTasksBeforeEvaluate()
public void createTasksBeforeEvaluate() {
//UninstallTask创建
taskFactory.create(
UNINSTALL_ALL,
uninstallAllTask -> {
uninstallAllTask.setDescription("Uninstall all applications.");
uninstallAllTask.setGroup(INSTALL_GROUP);
});
//DeviceCheckTask创建
taskFactory.create(
DEVICE_CHECK,
deviceCheckTask -> {
deviceCheckTask.setDescription(
"Runs all device checks using Device Providers and Test Servers.");
deviceCheckTask.setGroup(JavaBasePlugin.VERIFICATION_GROUP);
});
//ConnectedCheckTask创建,
taskFactory.create(
CONNECTED_CHECK,
connectedCheckTask -> {
connectedCheckTask.setDescription(
"Runs all device checks on currently connected devices.");
connectedCheckTask.setGroup(JavaBasePlugin.VERIFICATION_GROUP);
});
//MAIN_PREBUILD其实就是PreBuild这个Task
taskFactory.create(MAIN_PREBUILD, task -> {});
//生成一个Task,用于提取Proguard的文件
ExtractProguardFiles extractProguardFiles =
taskFactory.create(EXTRACT_PROGUARD_FILES, ExtractProguardFiles.class, task -> {});
// Make sure MAIN_PREBUILD runs first:
extractProguardFiles.dependsOn(MAIN_PREBUILD);
// 这里创建的SourceSetsTask其实是做一些日志打印的工作
taskFactory.create(new SourceSetsTask.ConfigAction(extension));
taskFactory.create(
ASSEMBLE_ANDROID_TEST,
assembleAndroidTestTask -> {
assembleAndroidTestTask.setGroup(BasePlugin.BUILD_GROUP);
assembleAndroidTestTask.setDescription("Assembles all the Test applications.");
});
taskFactory.create(new LintCompile.ConfigAction(globalScope));
// Lint task is configured in afterEvaluate, but created upfront as it is used as an
// anchor task.
//创建全局LintTask
createGlobalLintTask();
//如果自定义了Lint检查规则,这里可以配置进去
configureCustomLintChecksConfig();
globalScope.setAndroidJarConfig(createAndroidJarConfig(project));
//清除BuildCache
if (buildCache != null) {
taskFactory.create(new CleanBuildCache.ConfigAction(globalScope));
}
// for testing only.
taskFactory.create(
new TaskConfigAction<ConfigAttrTask>() {
@NonNull
@Override
public String getName() {
return "resolveConfigAttr";
}
@NonNull
@Override
public Class<ConfigAttrTask> getType() {
return ConfigAttrTask.class;
}
@Override
public void execute(@NonNull ConfigAttrTask task) {
task.resolvable = true;
}
});
taskFactory.create(
new TaskConfigAction<ConfigAttrTask>() {
@NonNull
@Override
public String getName() {
return "consumeConfigAttr";
}
@NonNull
@Override
public Class<ConfigAttrTask> getType() {
return ConfigAttrTask.class;
}
@Override
public void execute(@NonNull ConfigAttrTask task) {
task.consumable = true;
}
});
}
这里没什么好讲的,着重还是讲解一下Evaluate之后
Evaluate之后:createAndroidTasks()
@VisibleForTesting
final void createAndroidTasks() {
//..进行一些check
extension.disableWrite();
taskManager.configureCustomLintChecks();
ProcessProfileWriter.getProject(project.getPath())
.setCompileSdk(extension.getCompileSdkVersion())
.setBuildToolsVersion(extension.getBuildToolsRevision().toString())
.setSplits(AnalyticsUtil.toProto(extension.getSplits()));
//...进行一些check
// setup SDK repositories.
sdkHandler.addLocalRepositories(project);
threadRecorder.record(
ExecutionType.VARIANT_MANAGER_CREATE_ANDROID_TASKS,
project.getPath(),
null,
() -> {
//注意这里,很关键
variantManager.createAndroidTasks();
ApiObjectFactory apiObjectFactory =
new ApiObjectFactory(
globalScope.getAndroidBuilder(),
extension,
variantFactory,
project.getObjects());
for (VariantScope variantScope : variantManager.getVariantScopes()) {
BaseVariantData variantData = variantScope.getVariantData();
apiObjectFactory.create(variantData);
}
//...省略一些
});
// create the global lint task that depends on all the variants
taskManager.configureGlobalLintTask(variantManager.getVariantScopes());
// now publish all variant artifacts.
for (VariantScope variantScope : variantManager.getVariantScopes()) {
variantManager.publishBuildArtifacts(variantScope);
}
//...
variantManager.setHasCreatedTasks(true);
}
从代码中可以看到,variantManager也调用了createAndroidTasks。之后又遍历了VariantScopes,将
BaseVariantData传入了ApiObjectFactory。
variantManager.createAndroidTasks()
public void createAndroidTasks() {
//...
if (variantScopes.isEmpty()) {
recorder.record(
ExecutionType.VARIANT_MANAGER_CREATE_VARIANTS,
project.getPath(),
null /*variantName*/,
this::populateVariantDataList);
}
//...
for (final VariantScope variantScope : variantScopes) {
recorder.record(
ExecutionType.VARIANT_MANAGER_CREATE_TASKS_FOR_VARIANT,
project.getPath(),
variantScope.getFullVariantName(),
() -> createTasksForVariantData(variantScope));
}
//...
}
这里做了两件事情:
1. 创建所有的`variantData`
2. 遍历`VariantScope`,为`variantData`创建`task`
populateVariantDataList
public void populateVariantDataList() {
List<String> flavorDimensionList = extension.getFlavorDimensionList();
//省略一部分,这部分针对不同情况进行了处理:
//如果productFlavors为空,则向createVariantDataForProductFlavors传入空集合,不创建variant
//如果flavorDimensionList为空,则report错误
//如果flavorDimensionList.size为1,则为每一个flavor设置相同的dimension
//否则,在多个dimension的情况下,则进行下面的逻辑
// Get a list of all combinations of product flavors.
List<ProductFlavorCombo<CoreProductFlavor>> flavorComboList =
ProductFlavorCombo.createCombinations(
flavorDimensionList,
flavorDsl);
for (ProductFlavorCombo<CoreProductFlavor> flavorCombo : flavorComboList) {
//noinspection unchecked
createVariantDataForProductFlavors(
(List<ProductFlavor>) (List) flavorCombo.getFlavorList());
}
}
}
通过源码里面我们可以看到,在非特殊情况下,则会调用createCombinations进行计算flavor,然后获得一个集合。之后遍历这个集合,为每一个计算好的flavor创建ProductFlavor对象。
createCombinations
@NonNull
public static <S extends DimensionAware & Named> List<ProductFlavorCombo<S>> createCombinations(
@Nullable List<String> flavorDimensions,
@NonNull Iterable<S> productFlavors) {
List <ProductFlavorCombo<S>> result = Lists.newArrayList();
if (flavorDimensions == null || flavorDimensions.isEmpty()) {
for (S flavor : productFlavors) {
result.add(new ProductFlavorCombo<>(ImmutableList.of(flavor)));
}
} else {
// need to group the flavor per dimension.
// First a map of dimension -> list(ProductFlavor)
ArrayListMultimap<String, S> map = ArrayListMultimap.create();
for (S flavor : productFlavors) {
String flavorDimension = flavor.getDimension();
if (flavorDimension == null) {
throw new RuntimeException(String.format(
"Flavor '%1$s' has no flavor dimension.", flavor.getName()));
}
if (!flavorDimensions.contains(flavorDimension)) {
throw new RuntimeException(String.format(
"Flavor '%1$s' has unknown dimension '%2$s'.",
flavor.getName(), flavor.getDimension()));
}
map.put(flavorDimension, flavor);
}
createProductFlavorCombinations(result,
Lists.<S>newArrayListWithCapacity(flavorDimensions.size()),
0, flavorDimensions, map);
}
return result;
}
如果flavorDimensions不为空,将其放进一个map中:Key为dimension,Value为flavor。然后传入createProductFlavorCombinations。
createProductFlavorCombinations
private static <S extends DimensionAware & Named> void createProductFlavorCombinations(
List<ProductFlavorCombo<S>> flavorGroups,
List<S> group,
int index,
List<String> flavorDimensionList,
ListMultimap<String, S> map) {
if (index == flavorDimensionList.size()) {
flavorGroups.add(new ProductFlavorCombo<>(Iterables.filter(group, Predicates.notNull())));
return;
}
// fill the array at the current index.
// get the dimension name that matches the index we are filling.
String dimension = flavorDimensionList.get(index);
// from our map, get all the possible flavors in that dimension.
List<S> flavorList = map.get(dimension);
// loop on all the flavors to add them to the current index and recursively fill the next
// indices.
if (flavorList.isEmpty()) {
throw new RuntimeException(String.format(
"No flavor is associated with flavor dimension '%1$s'.", dimension));
} else {
for (S flavor : flavorList) {
group.add(flavor);
createProductFlavorCombinations(
flavorGroups, group, index + 1, flavorDimensionList, map);
group.remove(group.size() - 1);
}
}
}
这里就是创建ProductFlavor的精髓所在了:利用递归的边界条件求取所有的情况,你品,你细品~
完成这里之后,AGP会遍历flavorComboList,利用createTasksForVariantData中生成各种Task,也就是我们熟悉的assembleXXX、bundleXXX的Task了。
在此,我们可以明白createTasks里面大概做了什么,我们的ProductFlavor是如何被生成的,运用了什么样子的算法。
后续
这里讲完了BasePlugin的apply中的三个主要的回调方法,基本上整个Android构建流程都包含在里面。其实还有许许多多的Task值得我们去研究,之后系列的文章将会挑出几个关键的Task进行分析。