在如今这个时代,仅仅会简单的页面开发已经很难找到好工作了。
所以在实习的业余时间,我开始学习Android的Gradle构建流程,并且想将此心路历程整理为博客,方便加深理解。
在本文,我将给大家介绍BasePlugin
中configureProject
、configureExtension
、createTasks
这三个回调方法的主要流程
PS:本文基于Gradle 3.2.1
版本
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
方法看起
@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
,其他方法大家有兴趣可以私下去看
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(); } })); } }); } 复制代码
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
等等问题就迎刃而解了。
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
的。
@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的构建。
@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
作简单的分析
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
解析执行之前和之后。
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
之后
@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
。
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` 复制代码
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
对象。
@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
。
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进行分析。