本文由玉剛說寫作平臺提供寫作贊助,版權(quán)歸玉剛說微信公眾號所有
原作者:ShinyZeng
版權(quán)聲明:未經(jīng)玉剛說許可睡毒,不得以任何形式轉(zhuǎn)載
前言:這篇文章在一個月之前已經(jīng)發(fā)布到玉剛說微信公眾號。Gradle 這一塊對于我們Android開發(fā)來說冗栗,一直是比較無人問津的一塊演顾,平日的開發(fā)中也比較少涉及,即使涉及也是網(wǎng)上查查資料就可以解決隅居,并不會深入理解原理钠至,即使深入了,過一段時間不接觸也容易忘記胎源,譬如我今日再看這篇文章棉钧,就發(fā)現(xiàn)有很多已經(jīng)忘記了...真是汗顏啊...所以簡書中再發(fā)出來,做個備忘吧涕蚤。
一宪卿、源碼依賴
本文基于:
android gradle plugin版本:com.android.tools.build:gradle:2.3.0
gradle 版本:4.1
Gradle源碼總共30個G的诵,為簡單起見,方便大家看源碼佑钾,此處通過gradle依賴的形式來查看源碼西疤,依賴源碼姿勢:
創(chuàng)建一個新工程,app 項目目錄中刪除所有文件休溶,僅留下gradle文件代赁,依賴
apply plugin: 'java'
sourceCompatibility = 1.8
dependencies {
compile gradleApi()
compile 'com.android.tools.build:gradle:2.3.0'
}
將跟目錄下的gradle文件,刪除掉gradle依賴
buildscript {
repositories {
google()
jcenter()
}
dependencies {
// compile 'com.android.tools.build:gradle:2.3.0'
}
}
然后rebuild一下邮偎,就可以在External Libraries中查看到android gradle的源碼已經(jīng)依賴了
二管跺、Android Gradle Plugin簡介
我們知道Android gradle plugin是用來構(gòu)建Android工程的gradle插件,在Android gradle 插件中禾进,可以看到app工程和library工程所依賴的plugin是不一樣的
// app 工程
apply plugin: 'com.android.application'
// library 工程
apply plugin: 'com.android.library'
而對應填寫andorid塊中所填寫的配置也不同豁跑,這就是區(qū)分Application和Library的插件的extension塊
分別為:
app工程 -> AppPlugin -> AppExtension
librar工程 -> LibraryPlugin -> LibraryExtension
對應的是AppPlugin和AppExtension,這兩個插件構(gòu)建的流程大抵是相同的泻云,只是各自插件生成的任務不同艇拍,接下來我們著重分析Application插件是如何構(gòu)建我們的Android應用的
三、AppPlugin的構(gòu)建流程
我們先看下app工程中gradle的文件格式
apply plugin: 'com.android.application'
android {
compileSdkVersion 25
buildToolsVersion '26.0.2'
defaultConfig {
applicationId "com.zengshaoyi.gradledemo"
minSdkVersion 15
targetSdkVersion 25
versionCode project.ext.versionCode
versionName project.ext.versionName
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
lintOptions {
abortOnError false
}
}
跟蹤apply方法宠纯,其實是進入到
AppPlugin的apply的方法卸夕,我們可以看到內(nèi)部實現(xiàn)是直接調(diào)用父類BasePlugin的apply方法
protected void apply(@NonNull Project project) {
checkPluginVersion();
this.project = project;
ExecutionConfigurationUtil.setThreadPoolSize(project);
checkPathForErrors();
checkModulesForErrors();
ProfilerInitializer.init(project);
threadRecorder = ThreadRecorder.get();
ProcessProfileWriter.getProject(project.getPath())
.setAndroidPluginVersion(Version.ANDROID_GRADLE_PLUGIN_VERSION)
.setAndroidPlugin(getAnalyticsPluginType())
.setPluginGeneration(GradleBuildProject.PluginGeneration.FIRST);
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);
// Apply additional plugins
for (String plugin : AndroidGradleOptions.getAdditionalPlugins(project)) {
project.apply(ImmutableMap.of("plugin", plugin));
}
}
threadRecoirder.recode()是記錄最后一個參數(shù)的路徑和執(zhí)行的時間點,前面做了一些必要性的信息檢測之前婆瓜,其實主要做了以下幾件事情:
// 配置項目快集,設置構(gòu)建回調(diào)
this::configureProject
// 配置Extension
this::configureExtension
// 創(chuàng)建任務
this::createTasks
::是java 8引入的特性,詳情可以查看java8特性 廉白,這里就是方法的調(diào)用
configureProject
直接來看源碼
private void configureProject() {
extraModelInfo = new ExtraModelInfo(project);
checkGradleVersion();
AndroidGradleOptions.validate(project);
// Android SDK處理類
sdkHandler = new SdkHandler(project, getLogger());
// 設置項目評估階段回調(diào)
project.afterEvaluate(p -> {
// TODO: Read flag from extension.
if (!p.getGradle().getStartParameter().isOffline()
&& AndroidGradleOptions.getUseSdkDownload(p)) {
// 相關配置依賴的下載處理
SdkLibData sdkLibData =
SdkLibData.download(getDownloader(), getSettingsController());
dependencyManager.setSdkLibData(sdkLibData);
sdkHandler.setSdkLibData(sdkLibData);
}
});
// 創(chuàng)建AndroidBuilder
androidBuilder = new AndroidBuilder(
project == project.getRootProject() ? project.getName() : project.getPath(),
creator,
new GradleProcessExecutor(project),
new GradleJavaProcessExecutor(project),
extraModelInfo,
getLogger(),
isVerbose());
// dataBinding的相關處理
dataBindingBuilder = new DataBindingBuilder();
dataBindingBuilder.setPrintMachineReadableOutput(
extraModelInfo.getErrorFormatMode() ==
ExtraModelInfo.ErrorFormatMode.MACHINE_PARSABLE);
// Apply the Java and Jacoco plugins.
project.getPlugins().apply(JavaBasePlugin.class);
project.getPlugins().apply(JacocoPlugin.class);
// 給assemble任務添加描述
project.getTasks()
.getByName("assemble")
.setDescription(
"Assembles all variants of all applications and secondary packages.");
...
可以看到 configureProject 方法中在 project.afterEvaluate 設置了回調(diào)个初,當項目評估結(jié)束時,根據(jù)項目配置情況猴蹂,設置 dependece 依賴院溺;創(chuàng)建了 AndroidBuilder 對象,這個對象是用來合并manifest 和創(chuàng)建 dex 等作用磅轻,后面在創(chuàng)建任務的過程中會使用到珍逸,結(jié)下來繼續(xù)看 configureProject 的源碼
// 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.
// 設置構(gòu)建回調(diào)
project.getGradle()
.addBuildListener(
new BuildListener() {
private final LibraryCache libraryCache = LibraryCache.getCache();
@Override
public void buildStarted(Gradle gradle) {}
@Override
public void settingsEvaluated(Settings settings) {}
@Override
public void projectsLoaded(Gradle gradle) {}
@Override
public void projectsEvaluated(Gradle gradle) {}
@Override
public void buildFinished(BuildResult buildResult) {
ExecutorSingleton.shutdown();
sdkHandler.unload();
threadRecorder.record(
ExecutionType.BASE_PLUGIN_BUILD_FINISHED,
project.getPath(),
null,
() -> {
// 當任務執(zhí)行完成時,清楚dex緩存
PreDexCache.getCache()
.clear(
FileUtils.join(
project.getRootProject()
.getBuildDir(),
FD_INTERMEDIATES,
"dex-cache",
"cache.xml"),
getLogger());
JackConversionCache.getCache()
.clear(
FileUtils.join(
project.getRootProject()
.getBuildDir(),
FD_INTERMEDIATES,
"jack-cache",
"cache.xml"),
getLogger());
libraryCache.unload();
Main.clearInternTables();
});
}
});
// 設置創(chuàng)建有向圖任務回調(diào)
project.getGradle()
.getTaskGraph()
.addTaskExecutionGraphListener(
taskGraph -> {
for (Task task : taskGraph.getAllTasks()) {
// TransformTask是class編譯成dex的重要任務
if (task instanceof TransformTask) {
Transform transform = ((TransformTask) task).getTransform();
if (transform instanceof DexTransform) {
PreDexCache.getCache()
.load(
FileUtils.join(
project.getRootProject()
.getBuildDir(),
FD_INTERMEDIATES,
"dex-cache",
"cache.xml"));
break;
} else if (transform instanceof JackPreDexTransform) {
JackConversionCache.getCache()
.load(
FileUtils.join(
project.getRootProject()
.getBuildDir(),
FD_INTERMEDIATES,
"jack-cache",
"cache.xml"));
break;
}
}
}
});
這里在添加了 BuildListener聋溜,在 buildFinished 的時候清楚了dex緩存谆膳,而在任務有向圖創(chuàng)建的回調(diào)中,判斷是否是 DexTransfrom勤婚,從而從緩存中加載dex摹量。
總結(jié)一下 configureProject 做的事情,主要是進行版本有效性的判斷馒胆,創(chuàng)建了 AndroidBuilder 對象缨称,并設置了構(gòu)建流程的回調(diào)來處理依賴和dex的加載和緩存清理。
configureExtension
這個階段就是配置 extension 的階段祝迂,就是創(chuàng)建我們 android 塊中的可配置的對象
private void configureExtension() {
final NamedDomainObjectContainer<BuildType> buildTypeContainer =
project.container(
BuildType.class,
new BuildTypeFactory(instantiator, project, project.getLogger()));
final NamedDomainObjectContainer<ProductFlavor> productFlavorContainer =
project.container(
ProductFlavor.class,
new ProductFlavorFactory(
instantiator, project, project.getLogger(), extraModelInfo));
final NamedDomainObjectContainer<SigningConfig> signingConfigContainer =
project.container(SigningConfig.class, new SigningConfigFactory(instantiator));
extension =
createExtension(
project,
instantiator,
androidBuilder,
sdkHandler,
buildTypeContainer,
productFlavorContainer,
signingConfigContainer,
extraModelInfo);
...
首先創(chuàng)建了 BuildType睦尽、ProductFlavor、SigningConfig 三個類型的Container型雳,接著傳入到了createExtension方法中当凡,點入查看是個抽象的方法,各自的實現(xiàn)在子類中纠俭,這里也就是我們的AppPlugin 中
@NonNull
@Override
protected BaseExtension createExtension(
@NonNull Project project,
@NonNull Instantiator instantiator,
@NonNull AndroidBuilder androidBuilder,
@NonNull SdkHandler sdkHandler,
@NonNull NamedDomainObjectContainer<BuildType> buildTypeContainer,
@NonNull NamedDomainObjectContainer<ProductFlavor> productFlavorContainer,
@NonNull NamedDomainObjectContainer<SigningConfig> signingConfigContainer,
@NonNull ExtraModelInfo extraModelInfo) {
return project.getExtensions()
.create(
"android",
AppExtension.class,
project,
instantiator,
androidBuilder,
sdkHandler,
buildTypeContainer,
productFlavorContainer,
signingConfigContainer,
extraModelInfo);
}
這里也就是可以看到我們android塊配置是如何來的了沿量,對應的Extension也確實是AppExtension,繼續(xù)查看 configureExtension 的源碼
dependencyManager = new DependencyManager(
project,
extraModelInfo,
sdkHandler);
ndkHandler = new NdkHandler(
project.getRootDir(),
null, /* compileSkdVersion, this will be set in afterEvaluate */
"gcc",
"" /*toolchainVersion*/);
taskManager =
createTaskManager(
project,
androidBuilder,
dataBindingBuilder,
extension,
sdkHandler,
ndkHandler,
dependencyManager,
registry,
threadRecorder);
variantFactory = createVariantFactory(instantiator, androidBuilder, extension);
variantManager =
new VariantManager(
project,
androidBuilder,
extension,
variantFactory,
taskManager,
instantiator,
threadRecorder);
// Register a builder for the custom tooling model
ModelBuilder modelBuilder = new ModelBuilder(
androidBuilder,
variantManager,
taskManager,
extension,
extraModelInfo,
ndkHandler,
new NativeLibraryFactoryImpl(ndkHandler),
getProjectType(),
AndroidProject.GENERATION_ORIGINAL);
registry.register(modelBuilder);
// Register a builder for the native tooling model
NativeModelBuilder nativeModelBuilder = new NativeModelBuilder(variantManager);
registry.register(nativeModelBuilder);
這一部分主要是創(chuàng)建一些管理類冤荆,其中 createTaskManager朴则、createVariantFactory 都是抽象方法,對應的實現(xiàn)類
createTaskManager
AppPlugin -> ApplicationTaskManager
LibraryPlugin -> LibraryTaskManager
createVariantFactory
AppPlugin -> ApplicationVariantFactory
LibraryPlugin -> LibraryVariantFactory
這里簡單介紹一下 TaskManager 就是創(chuàng)建具體任務的管理類钓简,app 工程和庫 library 工程所需的構(gòu)建任務是不同的乌妒,后面我們會介紹 app 工程創(chuàng)建的構(gòu)建任務;VariantFactory 就是我們常說的構(gòu)建變體的工廠類外邓,主要是生成Variant(構(gòu)建變體)的對象撤蚊。我們回到 createExtension 的源碼中
// map the whenObjectAdded callbacks on the containers.
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.
variantFactory.createDefaultComponents(
buildTypeContainer, productFlavorContainer, signingConfigContainer);
這一部分做得事情,配置了 BuildTypeContainer损话、ProductFlavorContainer侦啸、SigningConfigContainer 這三個配置項的 whenObjectAdded 的回調(diào)过椎,每個配置的添加都會加入到 variantManager 中喳资;創(chuàng)建默認配置音念,下面是 ApplicationVariantFactory 的 createDefaultComponents 代碼
@Override
public void createDefaultComponents(
@NonNull NamedDomainObjectContainer<BuildType> buildTypes,
@NonNull NamedDomainObjectContainer<ProductFlavor> productFlavors,
@NonNull NamedDomainObjectContainer<SigningConfig> signingConfigs) {
// must create signing config first so that build type 'debug' can be initialized
// with the debug signing config.
signingConfigs.create(DEBUG);
buildTypes.create(DEBUG);
buildTypes.create(RELEASE);
}
總結(jié)一下 configureExtension 方法的作用果漾,主要是創(chuàng)建 Android 插件的擴展對象奏路,對配置項 BuildType霉囚、ProductFlavor蝶念、SigningConfig 做了統(tǒng)一的創(chuàng)建和回調(diào)處理翰蠢, 創(chuàng)建taskManager屎篱、variantFactory服赎、variantManager。
createTasks
private void createTasks() {
threadRecorder.record(
ExecutionType.TASK_MANAGER_CREATE_TASKS,
project.getPath(),
null,
() -> // 在項目評估之前創(chuàng)建任務
taskManager.createTasksBeforeEvaluate(
new TaskContainerAdaptor(project.getTasks())));
project.afterEvaluate(
project ->
threadRecorder.record(
ExecutionType.BASE_PLUGIN_CREATE_ANDROID_TASKS,
project.getPath(),
null,
// 在項目評估完成之后創(chuàng)建 androidTask
() -> createAndroidTasks(false)));
}
這里主要是分兩塊交播,一個是在 beforeEvaluate 創(chuàng)建任務重虑;一個是在 afterEvaluate 創(chuàng)建任務。這里的區(qū)別是 AndroidTask 是依賴配置項的配置才能生成相應任務秦士,所以是需要在 afterEvaluate 之后創(chuàng)建缺厉,如果對項目評估回調(diào)不理解的話,可以查閱Project文檔。beforeEvaluate 創(chuàng)建的任務跟我們編譯沒有太大關系提针,我們重點查看一下 afterEvaluate 創(chuàng)建的任務 createAndroidTasks
@VisibleForTesting
final void createAndroidTasks(boolean force) {
...
threadRecorder.record(
ExecutionType.VARIANT_MANAGER_CREATE_ANDROID_TASKS,
project.getPath(),
null,
() -> {
// 創(chuàng)建AndroidTasks
variantManager.createAndroidTasks();
ApiObjectFactory apiObjectFactory =
new ApiObjectFactory(
androidBuilder, extension, variantFactory, instantiator);
for (BaseVariantData variantData : variantManager.getVariantDataList()) {
apiObjectFactory.create(variantData);
}
});
...
}
我們主要看下variantManager的createAndroidTasks的方法
/**
* Variant/Task creation entry point.
*
* Not used by gradle-experimental.
*/
public void createAndroidTasks() {
variantFactory.validateModel(this);
variantFactory.preVariantWork(project);
final TaskFactory tasks = new TaskContainerAdaptor(project.getTasks());
if (variantDataList.isEmpty()) {
recorder.record(
ExecutionType.VARIANT_MANAGER_CREATE_VARIANTS,
project.getPath(),
null /*variantName*/,
this::populateVariantDataList);
}
// Create top level test tasks.
recorder.record(
ExecutionType.VARIANT_MANAGER_CREATE_TESTS_TASKS,
project.getPath(),
null /*variantName*/,
() -> taskManager.createTopLevelTestTasks(tasks, !productFlavors.isEmpty()));
for (final BaseVariantData<? extends BaseVariantOutputData> variantData : variantDataList) {
recorder.record(
ExecutionType.VARIANT_MANAGER_CREATE_TASKS_FOR_VARIANT,
project.getPath(),
variantData.getName(),
() -> createTasksForVariantData(tasks, variantData));
}
taskManager.createReportTasks(tasks, variantDataList);
}
首先判斷 variantDataList 是否是空命爬,如果是空的就會進入到 populateVariantDataList 方法中
/**
* Create all variants.
*/
public void populateVariantDataList() {
if (productFlavors.isEmpty()) {
createVariantDataForProductFlavors(Collections.emptyList());
} else {
List<String> flavorDimensionList = extension.getFlavorDimensionList();
// Create iterable to get GradleProductFlavor from ProductFlavorData.
Iterable<CoreProductFlavor> flavorDsl =
Iterables.transform(
productFlavors.values(),
ProductFlavorData::getProductFlavor);
// 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());
}
}
}
從方法注釋可以看到,這個方法主要的作用就是創(chuàng)建所有的 variants辐脖,試想一下該段代碼會做哪些事情饲宛,是否是解析 buildType、productFlavor 配置嗜价?
創(chuàng)建構(gòu)建變體(BuildVariant)
繼續(xù)觀察上面的代碼艇抠,可以看到無論是否有配置productFlavor 子項,都會進入到 createVariantDataForProductFlavors 方法久锥。如果有配置的話家淤,通過獲取配置的 flavorDimension 和 productFlavor 數(shù)組,調(diào)用 ProductFlavorCombo.createCombinations 組合出最后的產(chǎn)品風味數(shù)組 flavorComboList 瑟由,最后通過遍歷調(diào)用 createVariantDataForProductFlavors 方法
/**
* Creates VariantData for a specified list of product flavor.
*
* This will create VariantData for all build types of the given flavors.
*
* @param productFlavorList the flavor(s) to build.
*/
private void createVariantDataForProductFlavors(
@NonNull List<ProductFlavor> productFlavorList) {
...
for (BuildTypeData buildTypeData : buildTypes.values()) {
boolean ignore = false;
...
if (!ignore) {
BaseVariantData<?> variantData = createVariantData(
buildTypeData.getBuildType(),
productFlavorList);
variantDataList.add(variantData);
...
}
}
...
}
看上述代碼絮重,通過 creatVariantData 方法,將 buildType 和 productFlavor 的作為參數(shù)傳入错妖,創(chuàng)建了 variantData绿鸣,并且加入到了 variantDataList 集合中,這里我們就是將所有的構(gòu)建變體集合到了 variantDataList 中暂氯。
接著我們返回繼續(xù)看 createAndroidTasks 方法
/**
* Variant/Task creation entry point.
*
* Not used by gradle-experimental.
*/
public void createAndroidTasks() {
...
for (final BaseVariantData<? extends BaseVariantOutputData> variantData : variantDataList) {
recorder.record(
ExecutionType.VARIANT_MANAGER_CREATE_TASKS_FOR_VARIANT,
project.getPath(),
variantData.getName(),
() -> createTasksForVariantData(tasks, variantData));
}
...
}
通過上面拿到的variantDataList潮模,遍歷該集合來創(chuàng)建任務
/**
* Create tasks for the specified variantData.
*/
public void createTasksForVariantData(
final TaskFactory tasks,
final BaseVariantData<? extends BaseVariantOutputData> variantData) {
final BuildTypeData buildTypeData = buildTypes.get(
variantData.getVariantConfiguration().getBuildType().getName());
if (buildTypeData.getAssembleTask() == null) {
// 創(chuàng)建assemble + buildType任務
buildTypeData.setAssembleTask(taskManager.createAssembleTask(tasks, buildTypeData));
}
// Add dependency of assemble task on assemble build type task.
tasks.named("assemble", new Action<Task>() {
@Override
public void execute(Task task) {
assert buildTypeData.getAssembleTask() != null;
// 將 assemble 任務依賴于我們的 assemble + buildType 任務
task.dependsOn(buildTypeData.getAssembleTask().getName());
}
});
VariantType variantType = variantData.getType();
// 根據(jù) variantData 創(chuàng)建 assemble + flavor + buildType 任務
createAssembleTaskForVariantData(tasks, variantData);
if (variantType.isForTesting()) {
...
} else {
// 根據(jù) variantData 創(chuàng)建一系列任務
taskManager.createTasksForVariantData(tasks, variantData);
}
}
首先會先根據(jù) buildType 信息創(chuàng)建 assemble + buildType 的任務,可以看下taskManager. createAssembleTask里的代碼
@NonNull
public AndroidTask<DefaultTask> createAssembleTask(
@NonNull TaskFactory tasks,
@NonNull VariantDimensionData dimensionData) {
final String sourceSetName =
StringHelper.capitalize(dimensionData.getSourceSet().getName());
return androidTasks.create(
tasks,
// 設置任務名字為 assembleXXX
"assemble" + sourceSetName,
assembleTask -> {
// 設置描述和任務組
assembleTask.setDescription("Assembles all " + sourceSetName + " builds.");
assembleTask.setGroup(BasePlugin.BUILD_GROUP);
});
}
創(chuàng)建完任務之后痴施,將assemble任務依賴于我們的assembleXXX任務擎厢,隨后調(diào)用 createAssembleTaskForVariantData 方法,此方法是創(chuàng)建 assemble + flavor + buildType 任務辣吃,流程多了 productFlavor 任務的創(chuàng)建动遭,這里就不贅述了。后面會執(zhí) createTasksForVariantData神得,這個方法就是根據(jù) variant 生成一系列 Android 構(gòu)建所需任務(后面會詳細介紹)厘惦,回到 createAndroidTasks 方法中
threadRecorder.record(
ExecutionType.VARIANT_MANAGER_CREATE_ANDROID_TASKS,
project.getPath(),
null,
() -> {
variantManager.createAndroidTasks();
ApiObjectFactory apiObjectFactory =
new ApiObjectFactory(
androidBuilder, extension, variantFactory, instantiator);
for (BaseVariantData variantData : variantManager.getVariantDataList()) {
// 創(chuàng)建variantApi,添加到extensions中
apiObjectFactory.create(variantData);
}
});
最后就遍歷 variantDataList 通過 ApiObjectFactory 創(chuàng)建 variantApi哩簿,添加到 extensions 中宵蕉;
至此,我們就已經(jīng)將配置的構(gòu)建變種任務已經(jīng)添加到我們的任務列表中节榜,并形成了相關依賴羡玛。
Application 的編譯任務
我們繼續(xù)查看createTasksForVariantData的最后一行,
taskManager.createTasksForVariantData宗苍,發(fā)現(xiàn) createTasksForVariantData 是抽象方法稼稿,這里的 taskManager 具體實現(xiàn)是 ApplicationTaskManager薄榛,查看 ApplicationTaskManager 的 createTasksForVariantData 方法
/**
* Creates the tasks for a given BaseVariantData.
*/
@Override
public void createTasksForVariantData(
@NonNull final TaskFactory tasks,
@NonNull final BaseVariantData<? extends BaseVariantOutputData> variantData) {
assert variantData instanceof ApplicationVariantData;
final VariantScope variantScope = variantData.getScope();
//create sourceGenTask, resGenTask, assetGenTask
createAnchorTasks(tasks, variantScope);
createCheckManifestTask(tasks, variantScope);
handleMicroApp(tasks, variantScope);
// Create all current streams (dependencies mostly at this point)
createDependencyStreams(tasks, variantScope);
// Add a task to process the manifest(s)
// Add a task to create the res values
// Add a task to compile renderscript files.
// Add a task to merge the resource folders
// Add a task to merge the asset folders
// Add a task to create the BuildConfig class
// Add a task to process the Android Resources and generate source files
// Add a task to process the java resources
// Add a task to process this aidl file
// Add a task to process shader source
// Add NDK tasks
// Add external native build tasks
// Add a task to merge the jni libs folders
// Add a compile task
// Add data binding tasks if enabled
// create packaging task
// create the lint tasks.
...
}
代碼實在太長了,我只留下了每段代碼的注釋让歼,注釋也已經(jīng)非常清楚了敞恋,這個主要就是生成 variantData 的一系列像 compileXXX、generateXXX是越、processXXX耳舅、mergeXXX的任務碌上,這一系列 task 就是構(gòu)建一個可運行的完整APK的所需的所有task倚评。下面介紹在編譯dex中的過程,涉及的幾個task馏予。
Dex的編譯過程
// Add a compile task
recorder.record(
ExecutionType.APP_TASK_MANAGER_CREATE_COMPILE_TASK,
project.getPath(),
variantScope.getFullVariantName(),
() -> {
CoreJackOptions jackOptions =
variantData.getVariantConfiguration().getJackOptions();
// create data binding merge task before the javac task so that it can
// parse jars before any consumer
createDataBindingMergeArtifactsTaskIfNecessary(tasks, variantScope);
AndroidTask<? extends JavaCompile> javacTask =
// 創(chuàng)建 javac 任務
createJavacTask(tasks, variantScope);
if (jackOptions.isEnabled()) {
AndroidTask<TransformTask> jackTask =
createJackTask(tasks, variantScope, true /*compileJavaSource*/);
setJavaCompilerTask(jackTask, tasks, variantScope);
} else {
...
addJavacClassesStream(variantScope);
setJavaCompilerTask(javacTask, tasks, variantScope);
getAndroidTasks()
.create(
tasks,
// 創(chuàng)建 AndroidJarTask 天梧,生成classes.jar
new AndroidJarTask.JarClassesConfigAction(variantScope));
createPostCompilationTasks(tasks, variantScope);
}
});
我們直接查看 Add a compile task 注釋下的代碼,在執(zhí)行 createPostCompilationTasks 之前霞丧,先創(chuàng)建了 javac 任務呢岗,任務名稱為 compileXXXJavaWithJavac ,該任務是將 java 源文件編譯成 class 文件蛹尝,具體實現(xiàn)是在 JavaCompileConfigAction 類中后豫。創(chuàng)建 javac 任務之后,接著創(chuàng)建了 AndroidJarTask 任務突那,該任務是將 class 文件整合輸出 jar 包挫酿,具體實現(xiàn)就是在 AndroidJarTask 類中。
緊接著我們來看一下 createPostCompilationTasks 的方法
/**
* Creates the post-compilation tasks for the given Variant.
*
* These tasks create the dex file from the .class files, plus optional intermediary steps like
* proguard and jacoco
*
*/
public void createPostCompilationTasks(
@NonNull TaskFactory tasks,
@NonNull final VariantScope variantScope) {
checkNotNull(variantScope.getJavacTask());
variantScope.getInstantRunBuildContext().setInstantRunMode(
getIncrementalMode(variantScope.getVariantConfiguration()) != IncrementalMode.NONE);
final BaseVariantData<? extends BaseVariantOutputData> variantData = variantScope.getVariantData();
final GradleVariantConfiguration config = variantData.getVariantConfiguration();
TransformManager transformManager = variantScope.getTransformManager();
...
boolean isMinifyEnabled = isMinifyEnabled(variantScope);
boolean isMultiDexEnabled = config.isMultiDexEnabled();
// Switch to native multidex if possible when using instant run.
boolean isLegacyMultiDexMode = isLegacyMultidexMode(variantScope);
AndroidConfig extension = variantScope.getGlobalScope().getExtension();
// ----- External Transforms -----
// apply all the external transforms.
...
// ----- Minify next -----
if (isMinifyEnabled) {
boolean outputToJarFile = isMultiDexEnabled && isLegacyMultiDexMode;
// 內(nèi)部會判斷是否使用 proguard 來創(chuàng)建 proguard 任務和 shrinkResources 任務
createMinifyTransform(tasks, variantScope, outputToJarFile);
}
// ----- 10x support
...
// ----- Multi-Dex support
Optional<AndroidTask<TransformTask>> multiDexClassListTask;
// non Library test are running as native multi-dex
if (isMultiDexEnabled && isLegacyMultiDexMode) {
...
} else {
multiDexClassListTask = Optional.empty();
}
// create dex transform
// 從 extension 中獲取 dexOptions 項的配置
DefaultDexOptions dexOptions = DefaultDexOptions.copyOf(extension.getDexOptions());
...
// 創(chuàng)建 DexTransform
DexTransform dexTransform = new DexTransform(
dexOptions,
config.getBuildType().isDebuggable(),
isMultiDexEnabled,
isMultiDexEnabled && isLegacyMultiDexMode ? variantScope.getMainDexListFile() : null,
variantScope.getPreDexOutputDir(),
variantScope.getGlobalScope().getAndroidBuilder(),
getLogger(),
variantScope.getInstantRunBuildContext(),
AndroidGradleOptions.getBuildCache(variantScope.getGlobalScope().getProject()));
// 創(chuàng)建 dexTask
Optional<AndroidTask<TransformTask>> dexTask =
transformManager.addTransform(tasks, variantScope, dexTransform);
// need to manually make dex task depend on MultiDexTransform since there's no stream
// consumption making this automatic
dexTask.ifPresent(t -> {
t.optionalDependsOn(tasks, multiDexClassListTask.orElse(null));
variantScope.addColdSwapBuildTask(t);
});
...
}
為了講述主流程愕难,我將一些 mutiDex 和 instantRun 判斷的源碼省略了早龟,這里我們關注非mutiDex和非instantRun的情況。我們看到猫缭,如果我們設置了 minifyEnabled 為 true葱弟,那么這里就會去創(chuàng)建 createMinifyTransform ,如果use proguard猜丹,這里會創(chuàng)建 progruad 的任務和 shrinkResources 的任務芝加。后面將創(chuàng)建 dexTask, 這個是 transfromTask 類型的任務射窒,我們先來看下 transFromTask 類
/**
* A task running a transform.
*/
@ParallelizableTask
public class TransformTask extends StreamBasedTask implements Context {
private Transform transform;
...
public Transform getTransform() {
return transform;
}
...
@TaskAction
void transform(final IncrementalTaskInputs incrementalTaskInputs)
throws IOException, TransformException, InterruptedException {
...
recorder.record(
ExecutionType.TASK_TRANSFORM,
executionInfo,
getProject().getPath(),
getVariantName(),
new Recorder.Block<Void>() {
@Override
public Void call() throws Exception {
transform.transform(
new TransformInvocationBuilder(TransformTask.this)
.addInputs(consumedInputs.getValue())
.addReferencedInputs(referencedInputs.getValue())
.addSecondaryInputs(changedSecondaryInputs.getValue())
.addOutputProvider(
outputStream != null
? outputStream.asOutput()
: null)
.setIncrementalMode(isIncremental.getValue())
.build());
return null;
}
});
}
}
我們知道藏杖,自定義任務中,在任務執(zhí)行階段會去執(zhí)行被 @TaskAction 注解的方法轮洋,這里也就是執(zhí)行 transfrom 方法制市,而 transfrom 方法中最后又會調(diào)用到 transform 的 transfrom 方法,在我們 dexTask 中傳入的 transfrom 是DexTransfrom弊予,那我們就去看下 DexTransfrom 的 transfrom 具體實現(xiàn)
public class DexTransform extends Transform {
@Override
public void transform(@NonNull TransformInvocation transformInvocation)
throws TransformException, IOException, InterruptedException {
...
try {
// if only one scope or no per-scope dexing, just do a single pass that
// runs dx on everything.
if ((jarInputs.size() + directoryInputs.size()) == 1
|| !dexOptions.getPreDexLibraries()) {
// since there is only one dex file, we can merge all the scopes into the full
// application one.
File outputDir = outputProvider.getContentLocation("main",
getOutputTypes(),
TransformManager.SCOPE_FULL_PROJECT,
Format.DIRECTORY);
FileUtils.mkdirs(outputDir);
// first delete the output folder where the final dex file(s) will be.
FileUtils.cleanOutputDir(outputDir);
// gather the inputs. This mode is always non incremental, so just
// gather the top level folders/jars
final List<File> inputFiles =
Stream.concat(
jarInputs.stream().map(JarInput::getFile),
directoryInputs.stream().map(DirectoryInput::getFile))
.collect(Collectors.toList());
// 通過 AndroidBuilder 轉(zhuǎn)化為 byte
androidBuilder.convertByteCode(
inputFiles,
outputDir,
multiDex,
mainDexListFile,
dexOptions,
outputHandler);
for (File file : Files.fileTreeTraverser().breadthFirstTraversal(outputDir)) {
if (file.isFile()) {
instantRunBuildContext.addChangedFile(FileType.DEX, file);
}
}
} else {
...
}
最后執(zhí)行到androidBuilder.convertByteCode
/**
* Converts the bytecode to Dalvik format
* @param inputs the input files
* @param outDexFolder the location of the output folder
* @param dexOptions dex options
* @throws IOException
* @throws InterruptedException
* @throws ProcessException
*/
public void convertByteCode(
@NonNull Collection<File> inputs,
@NonNull File outDexFolder,
boolean multidex,
@Nullable File mainDexList,
@NonNull DexOptions dexOptions,
@NonNull ProcessOutputHandler processOutputHandler)
throws IOException, InterruptedException, ProcessException {checkNotNull(inputs, "inputs cannot be null.");
checkNotNull(outDexFolder, "outDexFolder cannot be null.");
checkNotNull(dexOptions, "dexOptions cannot be null.");
checkArgument(outDexFolder.isDirectory(), "outDexFolder must be a folder");
checkState(mTargetInfo != null,
"Cannot call convertByteCode() before setTargetInfo() is called.");
ImmutableList.Builder<File> verifiedInputs = ImmutableList.builder();
for (File input : inputs) {
if (checkLibraryClassesJar(input)) {
verifiedInputs.add(input);
}
}
//創(chuàng)建 DexProcessBuilder
DexProcessBuilder builder = new DexProcessBuilder(outDexFolder);
builder.setVerbose(mVerboseExec)
.setMultiDex(multidex)
.setMainDexList(mainDexList)
.addInputs(verifiedInputs.build());
runDexer(builder, dexOptions, processOutputHandler);
}
創(chuàng)建了 DexProcessBuilder 祥楣,隨后執(zhí)行到了 runDexer 方法中
public void runDexer(
@NonNull final DexProcessBuilder builder,
@NonNull final DexOptions dexOptions,
@NonNull final ProcessOutputHandler processOutputHandler)
throws ProcessException, IOException, InterruptedException {
initDexExecutorService(dexOptions);
if (dexOptions.getAdditionalParameters().contains("--no-optimize")) {
mLogger.warning(DefaultDexOptions.OPTIMIZE_WARNING);
}
if (shouldDexInProcess(dexOptions)) {
dexInProcess(builder, dexOptions, processOutputHandler);
} else {
dexOutOfProcess(builder, dexOptions, processOutputHandler);
}
}
進入到dexInProcess方法
private void dexInProcess(
@NonNull final DexProcessBuilder builder,
@NonNull final DexOptions dexOptions,
@NonNull final ProcessOutputHandler outputHandler)
throws IOException, ProcessException {
final String submission = Joiner.on(',').join(builder.getInputs());
mLogger.verbose("Dexing in-process : %1$s", submission);
try {
sDexExecutorService.submit(() -> {
Stopwatch stopwatch = Stopwatch.createStarted();
ProcessResult result = DexWrapper.run(builder, dexOptions, outputHandler);
result.assertNormalExitValue();
mLogger.verbose("Dexing %1$s took %2$s.", submission, stopwatch.toString());
return null;
}).get();
} catch (Exception e) {
throw new ProcessException(e);
}
}
/**
* Wrapper around the real dx classes.
*/
public class DexWrapper {
/**
* Runs the dex command.
*
* @return the integer return code of com.android.dx.command.dexer.Main.run()
*/
public static ProcessResult run(
@NonNull DexProcessBuilder processBuilder,
@NonNull DexOptions dexOptions,
@NonNull ProcessOutputHandler outputHandler) throws IOException, ProcessException {
ProcessOutput output = outputHandler.createOutput();
int res;
try {
DxContext dxContext = new DxContext(output.getStandardOutput(), output.getErrorOutput());
// 構(gòu)建 Main.Arguments 參數(shù)
Main.Arguments args = buildArguments(processBuilder, dexOptions, dxContext);
res = new Main(dxContext).run(args);
} finally {
output.close();
}
outputHandler.handleOutput(output);
return new DexProcessResult(res);
}
...
}
buildArguments方法通過傳入的DexProcessBuilder、dexOptions、dxContext構(gòu)建 arguments误褪,后面使用的args的參數(shù)fileNames责鳍,outName,jarOutput都是從DexProcessBuilder來的兽间,然后執(zhí)行Main的run方法
package com.android.dx.command.dexer;
...
/**
* Main class for the class file translator.
*/
public class Main {
/**
* Run and return a result code.
* @param arguments the data + parameters for the conversion
* @return 0 if success > 0 otherwise.
*/
public int run(Arguments arguments) throws IOException {
// Reset the error count to start fresh.
errors.set(0);
// empty the list, so that tools that load dx and keep it around
// for multiple runs don't reuse older buffers.
libraryDexBuffers.clear();
args = arguments;
args.makeOptionsObjects(context);
OutputStream humanOutRaw = null;
if (args.humanOutName != null) {
humanOutRaw = openOutput(args.humanOutName);
humanOutWriter = new OutputStreamWriter(humanOutRaw);
}
try {
if (args.multiDex) {
return runMultiDex();
} else {
return runMonoDex();
}
} finally {
closeOutput(humanOutRaw);
}
}
}
這里我們關注非multiDex的情況历葛,即執(zhí)行了runMonoDex的方法
private int runMonoDex() throws IOException {
...
// 內(nèi)部會創(chuàng)建dexFile,并填充class
if (!processAllFiles()) {
return 1;
}
if (args.incremental && !anyFilesProcessed) {
return 0; // this was a no-op incremental build
}
// this array is null if no classes were defined
byte[] outArray = null;
if (!outputDex.isEmpty() || (args.humanOutName != null)) {
// 內(nèi)部通過Dex類toDex 方法將 class 文件轉(zhuǎn)化dex byte[]
outArray = writeDex(outputDex);
if (outArray == null) {
return 2;
}
}
if (args.incremental) {
outArray = mergeIncremental(outArray, incrementalOutFile);
}
outArray = mergeLibraryDexBuffers(outArray);
if (args.jarOutput) {
// Effectively free up the (often massive) DexFile memory.
outputDex = null;
if (outArray != null) {
// 輸出的文件名為 classes.dex
outputResources.put(DexFormat.DEX_IN_JAR_NAME, outArray);
}
if (!createJar(args.outName)) {
return 3;
}
} else if (outArray != null && args.outName != null) {
OutputStream out = openOutput(args.outName);
out.write(outArray);
closeOutput(out);
}
return 0;
}
上面的代碼中嘀略,填充class以及dex流轉(zhuǎn)換恤溶,內(nèi)部流程較為復雜,就不再繼續(xù)深入帜羊,簡單做下總結(jié):
1.通過執(zhí)行 processAllFiles 咒程,內(nèi)部創(chuàng)建 DexFile 也就是outputDex,并且填充 class 文件
2.通過 writeDex 方法讼育,將 outputDex 傳入帐姻,方法內(nèi)部執(zhí)行的是 outputDex.toDex 方法,將 outputDex 內(nèi)部填充的 class 轉(zhuǎn)化為 dex 的 byte[] 返回
3.最后將 byte[] 數(shù)組創(chuàng)建 classes.dex 輸出
總結(jié):
Android Gradle Plugin源碼繁多奶段,以上文章只是對整體流程的簡單梳理饥瓷,其中簡要介紹了構(gòu)建變體任務的解析和添加 ,最后對編譯dex流程做了簡單分析痹籍。個人精力有限呢铆,這里的源碼解析也只是九牛一毛,如有紕漏词裤,歡迎大家拍磚刺洒,希望這篇文章能幫助到想了解 Android Gradle Plguin 原理的同學。