Android App 編譯流程詳解

1 Android Gradle Plugin 的下載,編譯,debug

1.1 下載

安裝 repo:

mkdir ~/bin
PATH=~/bin:$PATH
curl https://storage.googleapis.com/git-repo-downloads/repo > ~/bin/repo
chmod a+x ~/bin/repo

初始化並下載:

mkdir android_gradle_plugin
cd android_gradle_plugin
repo init -u https://android.googlesource.com/platform/manifest -b studio-master-dev
repo sync

1.2 編譯發佈

插件代碼在 tools/base:

cd tools
./gradlew :publishAndroidGradleLocal

版本號:

在 tools/buildSrc/base/version.properties 中。

1.3 debug

  • Run/Debug Configurations 中創建一個 Remote;
  • ./gradlew --no-daemon -Dorg.gradle.debug=true xxxTask;
  • 運行 debug。

2 Android Gradle plugin 的具體流程

2.1 流程

先看一張經典的打包流程圖:

Android App 編譯流程詳解


可以看到一共有以下幾步:

  1. 通過 aapt 打包 res 資源文件,生成 R.java、resources.arsc 和 res 文件(二進制 & 非二進制如 res/raw 和 pic 保持原樣);
  2. 處理 .aidl 文件,生成對應的 Java 接口文件;
  3. 通過 Java Compiler 編譯 R.java、Java 接口文件、Java 源文件,生成 .class 文件;
  4. 通過 dex 命令,將 .class 文件和第三方庫中的 .class 文件處理生成 classes.dex;
  5. 通過 apkbuilder 工具,將 aapt 生成的 resources.arsc 和 res 文件、assets 文件和 classes.dex 一起打包生成 apk;
  6. 通過 Jarsigner 工具,對上面的 apk 進行 debug 或 release 簽名;
  7. 通過 zipalign 工具,將簽名後的 apk 進行對齊處理。

再看一張現在官網給的構建流程圖:

Android App 編譯流程詳解


  1. 編譯器將您的源代碼轉換成 DEX(Dalvik Executable) 文件(其中包括運行在 Android 設備上的字節碼),將所有其他內容轉換成已編譯資源。
  2. APK 打包器將 DEX 文件和已編譯資源合併成單個 APK。不過,必須先簽署 APK,才能將應用安裝並部署到 Android 設備上。
  3. APK 打包器使用調試或發佈密鑰庫簽署您的 APK:
  4. 如果您構建的是調試版本的應用(即專用於測試和分析的應用),打包器會使用調試密鑰庫簽署您的應用。Android Studio 自動使用調試密鑰庫配置新項目。
  5. 如果您構建的是打算向外發佈的發佈版本應用,打包器會使用發佈密鑰庫簽署您的應用。要創建發佈密鑰庫,請閱讀在 Android Studio 中籤署您的應用
  6. 在生成最終 APK 之前,打包器會使用 zipalign 工具對應用進行優化,減少其在設備上運行時的內存佔用。

2.2 構建

有了初步的瞭解後,我們來看看 Android Gradle plugin 是如何實現構建流程的。

com.android.application 和 com.android.library 是我們最熟悉的 android gradle plugin,我們可以在 tools/base/build-system/gradle-core/src/main/resources/META-INF/gradle-plugins 路徑下找到。

可以看到 com.android.application 插件對應的類是 com.android.build.gradle.AppPlugin,而 com.android.library 插件對應的類是 com.android.build.gradle.LibraryPlugin

AppPlugin 類和 LibraryPlugin 類最終都是 extends BasePlugin、implements Plugin<project>。/<project>

從 apply() 方法開始執行,主要內容都在 basePluginApply() 方法中。

private void basePluginApply(@NonNull Project project) {
// 省略一些檢查,運行管理,輸出profile等代碼,核心就三個方法
configureProject();
configureExtension();
createTasks();
}
  • configureProject()
private void configureProject() {
// 以下代碼有精簡省略
new ExtraModelInfo();
new SdkHandler();
// AndroidBuilder作用是process the build
AndroidBuilder androidBuilder =
new AndroidBuilder(
project == project.getRootProject() ? project.getName() : project.getPath(),
creator,
// GradleProcessExecutor作用是execute external processes
new GradleProcessExecutor(project),
// GradleJavaProcessExecutor作用是execute external java processes
new GradleJavaProcessExecutor(project),
extraModelInfo.getSyncIssueHandler(),
extraModelInfo.getMessageReceiver(),
getLogger(),
isVerbose());
new DataBindingBuilder();

// 最小版本
GradlePluginUtils.enforceMinimumVersionsOfPlugins();
// 依賴JavaBasePlugin插件
project.getPlugins().apply(JavaBasePlugin.class);
new DslScopeImpl();
BuildCacheUtils.createBuildCacheIfEnabled();
// plugin的全局變量
new GlobalScope();
// 添加描述
project.getTasks()
.getByName("assemble")
.setDescription(
"Assembles all variants of all applications and secondary packages.");
// buildFinished後關閉釋放操作
gradle.addBuildListener(new BuildListener() {...});
// 創建lint配置
createLintClasspathConfiguration(project);
}
  • configureExtension()
private void configureExtension() {
ObjectFactory objectFactory = project.getObjects();
// 創建`BuildType`,`ProductFlavor`,`SigningConfig`,`BaseVariantOutput`的容器
final NamedDomainObjectContainer<buildtype> buildTypeContainer =
project.container(
BuildType.class,
new BuildTypeFactory(
objectFactory,
project,
extraModelInfo.getSyncIssueHandler(),
extraModelInfo.getDeprecationReporter()));
final NamedDomainObjectContainer<productflavor> productFlavorContainer =
project.container(
ProductFlavor.class,
new ProductFlavorFactory(
objectFactory,
project,
extraModelInfo.getDeprecationReporter(),
project.getLogger()));
final NamedDomainObjectContainer<signingconfig> signingConfigContainer =
project.container(
SigningConfig.class,
new SigningConfigFactory(

objectFactory,
GradleKeystoreHelper.getDefaultDebugKeystoreLocation()));
final NamedDomainObjectContainer<basevariantoutput> buildOutputs =
project.container(BaseVariantOutput.class);
project.getExtensions().add("buildOutputs", buildOutputs);
sourceSetManager = createSourceSetManager();
// 創建BaseExtension
extension =
createExtension(
project,
projectOptions,
globalScope,
sdkHandler,
buildTypeContainer,
productFlavorContainer,
signingConfigContainer,
buildOutputs,
sourceSetManager,
extraModelInfo);
globalScope.setExtension(extension);
variantFactory = createVariantFactory(globalScope, extension);
// 創建TaskManager
taskManager =
createTaskManager(
globalScope,
project,
projectOptions,
dataBindingBuilder,
extension,
sdkHandler,
variantFactory,
registry,
threadRecorder);
// 創建VariantManager
variantManager =
new VariantManager(
globalScope,
project,
projectOptions,
extension,
variantFactory,
taskManager,
sourceSetManager,
threadRecorder);
registerModels(registry, globalScope, variantManager, extension, extraModelInfo);
// map the whenObjectAdded callbacks on the containers.
signingConfigContainer.whenObjectAdded(variantManager::addSigningConfig);
buildTypeContainer.whenObjectAdded(
buildType -> {
if (!this.getClass().isAssignableFrom(DynamicFeaturePlugin.class)) {

SigningConfig signingConfig =
signingConfigContainer.findByName(BuilderConstants.DEBUG);
buildType.init(signingConfig);
} else {
// initialize it without the signingConfig for dynamic-features.
buildType.init();
}
variantManager.addBuildType(buildType);
});
productFlavorContainer.whenObjectAdded(variantManager::addProductFlavor);
// map whenObjectRemoved on the containers to throw an exception.
signingConfigContainer.whenObjectRemoved(
new UnsupportedAction("Removing signingConfigs is not supported."));
buildTypeContainer.whenObjectRemoved(
new UnsupportedAction("Removing build types is not supported."));
productFlavorContainer.whenObjectRemoved(
new UnsupportedAction("Removing product flavors is not supported."));
// create default Objects, signingConfig first as its used by the BuildTypes.
variantFactory.createDefaultComponents(
buildTypeContainer, productFlavorContainer, signingConfigContainer);
}
/<basevariantoutput>/<signingconfig>/<productflavor>/<buildtype>

配置含義可以參考Android Plugin DSL Reference。

大家一定發現沒有 ProductFlaovr,那它是什麼呢,引用官方:

The product flavors support the same properties as defaultConfig—this is because defaultConfig actually belongs to the ProductFlavor class

其實 ProductFlavor 就是去自定義 defaultConfig 中的配置。

  • createTasks()
private void createTasks() {
// 註冊一些默認task
createTasksBeforeEvaluate();
createAndroidTasks();
}

看 createAndroidTasks() 方法:

final void createAndroidTasks() {
// 省略其中 check和初始化,剩下下面這個方法

List<variantscope> variantScopes = variantManager.createAndroidTasks();
}
/<variantscope>

VariantManager 類的 createAndroidTasks() 方法:

public List<variantscope> createAndroidTasks() {
variantFactory.validateModel(this);
variantFactory.preVariantWork(project);
// Create all variants.
if (variantScopes.isEmpty()) {
populateVariantDataList();
}
// Create top level test tasks.
taskManager.createTopLevelTestTasks(!productFlavors.isEmpty());
for (final VariantScope variantScope : variantScopes) {
createTasksForVariantData(variantScope);
}
// reportSourceSetTransform
taskManager.createSourceSetArtifactReportTask(globalScope);
// 註冊 androidDependencies 和 signingReport 兩個task
taskManager.createReportTasks(variantScopes);
return variantScopes;
}
/<variantscope>

Variant 是什麼呢,引用官方:

A build variant is a cross product of a build type and product flavor, and is the configuration Gradle uses to build your app

所以 Variant 就是 BuildType 和 ProductFlaovr。

再來看 createTasksForVariantData() 方法:

public void createTasksForVariantData(final VariantScope variantScope) {
// 創建assembleDebug assembleRelease方法
taskManager.createAssembleTask(variantData);
// 省略test
// ApplicationTaskManager類 和LibraryTaskManager類
taskManager.createTasksForVariantScope(variantScope);
}

主要的流程都在 taskManager.createTasksForVariantScope(variantScope),我們主要研究 com.android.application,所以來看 ApplicationTaskManager 類的 createTasksForVariantScope() 方法。

  • ApplicationTaskManager.createTasksForVariantScope()

注:Task 執行函數在 @TaskAction

public void createTasksForVariantScope(@NonNull final VariantScope variantScope) {
// 創建preXXXBuild,generateXXXSources,generateXXXResources,generateXXXAssets,compileXXXSources 這些Task
createAnchorTasks(variantScope);
// checkXXXManifest 檢查Manifest文件存在,路徑
createCheckManifestTask(variantScope);
// 穿戴設備
handleMicroApp(variantScope);
// 把依賴放到TransformManager中
createDependencyStreams(variantScope);
// Add a task to publish the applicationId.
createApplicationIdWriterTask(variantScope);
taskFactory.register(new MainApkListPersistence.CreationAction(variantScope));
createBuildArtifactReportTask(variantScope);
// merge manifest(s),在ProcessApplicationManifest的doFullTaskAction()方法
createMergeApkManifestsTask(variantScope);
// buildType中定義的變量會在build/generated/res/resValues/${buildTypes}/values路徑生成generated.xml文件
// GenerateResValues的generate()方法,主要生成一些變量在代碼中使用
createGenerateResValuesTask(variantScope);
// Add a task to compile renderscript files.
createRenderscriptTask(variantScope);
// merge resource
createMergeResourcesTask(
variantScope,
true,
Sets.immutableEnumSet(MergeResources.Flag.PROCESS_VECTOR_DRAWABLES));
// Add tasks to compile shader
createShaderTask(variantScope);
// merge asset
createMergeAssetsTask(variantScope);
// Add a task to create the BuildConfig class
// 在build/generated/source/${buildTypes}/${packageName}路徑下生存BuildConfig.java文件
// GenerateBuildConfig的generate()方法
createBuildConfigTask(variantScope);
// 通過aapt打包Android Resources
createApkProcessResTask(variantScope);
// Add a task to process the java resources
createProcessJavaResTask(variantScope);

createAidlTask(variantScope);
// Add NDK tasks
createNdkTasks(variantScope);
// Add external native build tasks
createExternalNativeBuildJsonGenerators(variantScope);
createExternalNativeBuildTasks(variantScope);
// Add a task to merge the jni libs folders
createMergeJniLibFoldersTasks(variantScope);
// Add feature related tasks if necessary
if (variantScope.getType().isBaseModule()) {
// Base feature specific tasks.
taskFactory.register(new FeatureSetMetadataWriterTask.CreationAction(variantScope));
// Add a task to produce the signing config file
taskFactory.register(
new SigningConfigWriterTask.CreationAction(
variantScope, getValidateSigningTask(variantScope)));
if (extension.getDataBinding().isEnabled()) {
// Create a task that will package the manifest ids(the R file packages) of all
// features into a file. This file's path is passed into the Data Binding annotation
// processor which uses it to known about all available features.
//
//

see: {@link TaskManager#setDataBindingAnnotationProcessorParams(VariantScope)}
taskFactory.register(
new DataBindingExportFeatureApplicationIdsTask.CreationAction(
variantScope));
}
} else {
// Non-base feature specific task.
// Task will produce artifacts consumed by the base feature
taskFactory.register(
new FeatureSplitDeclarationWriterTask.CreationAction(variantScope));
if (extension.getDataBinding().isEnabled()) {
// Create a task that will package necessary information about the feature into a
// file which is passed into the Data Binding annotation processor.
taskFactory.register(
new DataBindingExportFeatureInfoTask.CreationAction(variantScope));
}
taskFactory.register(new MergeConsumerProguardFilesTask.CreationAction(variantScope));
}
// Add data binding tasks if enabled
createDataBindingTasksIfNecessary(variantScope, MergeType.MERGE);
// 編譯
createCompileTask(variantScope);
createStripNativeLibraryTask(taskFactory, variantScope);
if (variantScope.getVariantData().getMultiOutputPolicy().equals(MultiOutputPolicy.SPLITS)) {
if (extension.getBuildToolsRevision().getMajor() < 21) {
throw new RuntimeException(
"Pure splits can only be used with buildtools 21 and later");
}


createSplitTasks(variantScope);
}
TaskProvider<buildinfowritertask> buildInfoWriterTask =
createInstantRunPackagingTasks(variantScope);
// 打包,簽名(可選)
createPackagingTask(variantScope, buildInfoWriterTask);
// Create the lint tasks, if enabled
createLintTasks(variantScope);
taskFactory.register(new FeatureSplitTransitiveDepsWriterTask.CreationAction(variantScope));
createDynamicBundleTask(variantScope);
}
/<buildinfowritertask>

下面詳細講幾個 task。

2.3 task

2.3.1 createMergeApkManifestsTask()

ProcessApplicationManifest的doFullTaskAction() 方法,最終調了 AndroidBuilder 的mergeManifestsForApplication() 方法。

ProcessLibraryManifest 也會調用 AndroidBuilder 的 mergeManifestsForApplication()方法,可以不講 lib。

public MergingReport mergeManifestsForApplication(...){
// ManifestMerger2.Invoker類
// 設置placeHolder,BuildType,ProductFlavor等
Invoker manifestMergerInvoker =
ManifestMerger2.newMerger(mainManifest, mLogger, mergeType)
.setPlaceHolderValues(placeHolders)
.addFlavorAndBuildTypeManifests(manifestOverlays.toArray(new File[0]))
.addManifestProviders(dependencies)
.addNavigationFiles(navigationFiles)
.withFeatures(optionalFeatures.toArray(new Invoker.Feature[0]))
.setMergeReportFile(reportFile)
.setFeatureName(featureName);
// APPLICATION插件,添加刪除tools的featrue
if (mergeType == ManifestMerger2.MergeType.APPLICATION) {
manifestMergerInvoker.withFeatures(Invoker.Feature.REMOVE_TOOLS_DECLARATIONS);
}
// APPLICATION插件,傳的null
if (outAaptSafeManifestLocation != null) {

// lib中可能傳一些佔位符placeHolder,需要添加featrue,在application中賦值
manifestMergerInvoker.withFeatures(Invoker.Feature.MAKE_AAPT_SAFE);
}
// 設置別的參數
setInjectableValues(manifestMergerInvoker,
packageOverride, versionCode, versionName,
minSdkVersion, targetSdkVersion, maxSdkVersion);
// ManifestMerger2.Invoker.merge()最終調了ManifestMerger2的merge(),核心都在這
MergingReport mergingReport = manifestMergerInvoker.merge();
...
}

ManifestMerger2 的 merge() 方法:

private MergingReport merge() throws MergeFailureException {
// initiate a new merging report
MergingReport.Builder mergingReportBuilder = new MergingReport.Builder(mLogger, this);
SelectorResolver selectors = new SelectorResolver();
// load the main manifest file to do some checking along the way.
LoadedManifestInfo loadedMainManifestInfo =
load(
new ManifestInfo(
mManifestFile.getName(),
mManifestFile,
mDocumentType,
Optional.absent() /* mainManifestPackageName */),
selectors,
mergingReportBuilder);
// first do we have a package declaration in the main manifest ?
Optional<xmlattribute> mainPackageAttribute =
loadedMainManifestInfo.getXmlDocument().getPackage();
if (mDocumentType != XmlDocument.Type.OVERLAY && !mainPackageAttribute.isPresent()) {
mergingReportBuilder.addMessage(
loadedMainManifestInfo.getXmlDocument().getSourceFile(),
MergingReport.Record.Severity.ERROR,
String.format(
"Main AndroidManifest.xml at %1$s manifest:package attribute "
+ "is not declared",
loadedMainManifestInfo.getXmlDocument().getSourceFile()
.print(true)));
return mergingReportBuilder.build();
}
// load all the libraries xml files early to have a list of all possible node:selector
// values.
List<loadedmanifestinfo> loadedLibraryDocuments =
loadLibraries(

selectors,
mergingReportBuilder,
mainPackageAttribute.isPresent()
? mainPackageAttribute.get().getValue()
: null);
// perform system property injection
performSystemPropertiesInjection(mergingReportBuilder,
loadedMainManifestInfo.getXmlDocument());
// force the re-parsing of the xml as elements may have been added through system
// property injection.
loadedMainManifestInfo = new LoadedManifestInfo(loadedMainManifestInfo,
loadedMainManifestInfo.getOriginalPackageName(),
loadedMainManifestInfo.getXmlDocument().reparse());
// invariant : xmlDocumentOptional holds the higher priority document and we try to
// merge in lower priority documents.
Optional<xmldocument> xmlDocumentOptional = Optional.absent();
for (File inputFile : mFlavorsAndBuildTypeFiles) {
mLogger.verbose("Merging flavors and build manifest %s \n", inputFile.getPath());
LoadedManifestInfo overlayDocument =
load(
new ManifestInfo(
null,
inputFile,
XmlDocument.Type.OVERLAY,
mainPackageAttribute.transform(it -> it.getValue())),
selectors,
mergingReportBuilder);
// check package declaration.
Optional<xmlattribute> packageAttribute =
overlayDocument.getXmlDocument().getPackage();
// if both files declare a package name, it should be the same.
if (loadedMainManifestInfo.getOriginalPackageName().isPresent() &&
packageAttribute.isPresent()
&& !loadedMainManifestInfo.getOriginalPackageName().get().equals(
packageAttribute.get().getValue())) {
// no suggestion for library since this is actually forbidden to change the
// the package name per flavor.
String message = mMergeType == MergeType.APPLICATION
? String.format(
"Overlay manifest:package attribute declared at %1$s value=(%2$s)\n"
+ "\thas a different value=(%3$s) "
+ "declared in main manifest at %4$s\n"
+ "\tSuggestion: remove the overlay declaration at %5$s "
+ "\tand place it in the build.gradle:\n"
+ "\t\tflavorName {\n"
+ "\t\t\tapplicationId = "%2$s"\n"
+ "\t\t}",
packageAttribute.get().printPosition(),
packageAttribute.get().getValue(),
mainPackageAttribute.get().getValue(),

mainPackageAttribute.get().printPosition(),
packageAttribute.get().getSourceFile().print(true))
: String.format(
"Overlay manifest:package attribute declared at %1$s value=(%2$s)\n"
+ "\thas a different value=(%3$s) "
+ "declared in main manifest at %4$s",
packageAttribute.get().printPosition(),
packageAttribute.get().getValue(),
mainPackageAttribute.get().getValue(),
mainPackageAttribute.get().printPosition());
mergingReportBuilder.addMessage(
overlayDocument.getXmlDocument().getSourceFile(),
MergingReport.Record.Severity.ERROR,
message);
return mergingReportBuilder.build();
}
if (mainPackageAttribute.isPresent()) {
overlayDocument
.getXmlDocument()
.getRootNode()
.getXml()
.setAttribute("package", mainPackageAttribute.get().getValue());
}
xmlDocumentOptional = merge(xmlDocumentOptional, overlayDocument, mergingReportBuilder);
if (!xmlDocumentOptional.isPresent()) {
return mergingReportBuilder.build();
}
}
mLogger.verbose("Merging main manifest %s\n", mManifestFile.getPath());
xmlDocumentOptional =
merge(xmlDocumentOptional, loadedMainManifestInfo, mergingReportBuilder);
if (!xmlDocumentOptional.isPresent()) {
return mergingReportBuilder.build();
}
// force main manifest package into resulting merged file when creating a library manifest.
if (mMergeType == MergeType.LIBRARY) {
// extract the package name...
String mainManifestPackageName = loadedMainManifestInfo.getXmlDocument().getRootNode()
.getXml().getAttribute("package");
// save it in the selector instance.
if (!Strings.isNullOrEmpty(mainManifestPackageName)) {
xmlDocumentOptional.get().getRootNode().getXml()
.setAttribute("package", mainManifestPackageName);
}
}
for (LoadedManifestInfo libraryDocument : loadedLibraryDocuments) {
mLogger.verbose("Merging library manifest " + libraryDocument.getLocation());
xmlDocumentOptional = merge(
xmlDocumentOptional, libraryDocument, mergingReportBuilder);
if (!xmlDocumentOptional.isPresent()) {

return mergingReportBuilder.build();
}
}
// done with proper merging phase, now we need to expand <nav-graph> elements, trim unwanted
// elements, perform placeholder substitution and system properties injection.
Map<string> loadedNavigationMap = new HashMap<>();
for (File navigationFile : mNavigationFiles) {
String navigationId = navigationFile.getName().replaceAll("\\.xml$", "");
if (loadedNavigationMap.get(navigationId) != null) {
continue;
}
try (InputStream inputStream = mFileStreamProvider.getInputStream(navigationFile)) {
loadedNavigationMap.put(
navigationId,
NavigationXmlLoader.INSTANCE.load(
navigationId, navigationFile, inputStream));
} catch (Exception e) {
throw new MergeFailureException(e);
}
}
xmlDocumentOptional =
Optional.of(
NavGraphExpander.INSTANCE.expandNavGraphs(
xmlDocumentOptional.get(),
loadedNavigationMap,
mergingReportBuilder));
if (mergingReportBuilder.hasErrors()) {
return mergingReportBuilder.build();
}
ElementsTrimmer.trim(xmlDocumentOptional.get(), mergingReportBuilder);
if (mergingReportBuilder.hasErrors()) {
return mergingReportBuilder.build();
}
if (!mOptionalFeatures.contains(Invoker.Feature.NO_PLACEHOLDER_REPLACEMENT)) {
// do one last placeholder substitution, this is useful as we don't stop the build
// when a library failed a placeholder substitution, but the element might have
// been overridden so the problem was transient. However, with the final document
// ready, all placeholders values must have been provided.
MergingReport.Record.Severity severity =
mMergeType == MergeType.LIBRARY
? MergingReport.Record.Severity.INFO
: MergingReport.Record.Severity.ERROR;
performPlaceHolderSubstitution(
loadedMainManifestInfo,
xmlDocumentOptional.get(),
mergingReportBuilder,
severity);
if (mergingReportBuilder.hasErrors()) {
return mergingReportBuilder.build();
}

}
// perform system property injection.
performSystemPropertiesInjection(mergingReportBuilder, xmlDocumentOptional.get());
XmlDocument finalMergedDocument = xmlDocumentOptional.get();
if (!mOptionalFeatures.contains(Invoker.Feature.REMOVE_TOOLS_DECLARATIONS)) {
PostValidator.enforceToolsNamespaceDeclaration(finalMergedDocument);
}
PostValidator.validate(finalMergedDocument, mergingReportBuilder);
if (mergingReportBuilder.hasErrors()) {
finalMergedDocument.getRootNode().addMessage(mergingReportBuilder,
MergingReport.Record.Severity.WARNING,
"Post merge validation failed");
}
finalMergedDocument.clearNodeNamespaces();
// extract fully qualified class names before handling other optional features.
if (mOptionalFeatures.contains(Invoker.Feature.EXTRACT_FQCNS)) {
extractFqcns(finalMergedDocument);
}
// handle optional features which don't need access to XmlDocument layer.
processOptionalFeatures(finalMergedDocument.getXml(), mergingReportBuilder);
// call blame after other optional features handled.
if (!mOptionalFeatures.contains(Invoker.Feature.SKIP_BLAME)) {
try {
mergingReportBuilder.setMergedDocument(
MergingReport.MergedManifestKind.BLAME,
mergingReportBuilder.blame(finalMergedDocument));
} catch (Exception e) {
mLogger.error(e, "Error while saving blame file, build will continue");
}
}
mergingReportBuilder.setFinalPackageName(finalMergedDocument.getPackageName());
mergingReportBuilder.setMergedXmlDocument(
MergingReport.MergedManifestKind.MERGED, finalMergedDocument);
MergingReport mergingReport = mergingReportBuilder.build();
if (mReportFile.isPresent()) {
writeReport(mergingReport);
}
return mergingReport;
}
/<string>/<nav-graph>/<xmlattribute>/<xmldocument>/<loadedmanifestinfo>/<xmlattribute>

關鍵就是 LoadedManifestInfo 對象,一共三個 loadedMainManifestInfo、loadedLibraryDocuments、overlayDocument,分別是 main manifest,lib manifests 和 build variants(src/debug/ 之類的)。

最終都調 merge()。

xmlDocumentOptional = merge(xmlDocumentOptional, overlayDocument, mergingReportBuilder);
xmlDocumentOptional = merge(xmlDocumentOptional, loadedMainManifestInfo, mergingReportBuilder);

xmlDocumentOptional = merge(xmlDocumentOptional, libraryDocument, mergingReportBuilder);

現在來看這個 merge() 方法:

private Optional<xmldocument> merge(
@NonNull Optional<xmldocument> xmlDocument,
@NonNull LoadedManifestInfo lowerPriorityDocument,
@NonNull MergingReport.Builder mergingReportBuilder) throws MergeFailureException {
MergingReport.Result validationResult = PreValidator
.validate(mergingReportBuilder, lowerPriorityDocument.getXmlDocument());
if (validationResult == MergingReport.Result.ERROR) {
mergingReportBuilder.addMessage(
lowerPriorityDocument.getXmlDocument().getSourceFile(),
MergingReport.Record.Severity.ERROR,
"Validation failed, exiting");
return Optional.absent();
}
Optional<xmldocument> result;
if (xmlDocument.isPresent()) {
result = xmlDocument.get().merge(
lowerPriorityDocument.getXmlDocument(), mergingReportBuilder,
!mOptionalFeatures.contains(Invoker.Feature.NO_IMPLICIT_PERMISSION_ADDITION));
} else {
// exhaustiveSearch is true in recordAddedNodeAction() below because some of this
// manifest's nodes might have already been recorded from the loading of
// the main manifest, but we want to record any unrecorded descendants.
// e.g., if the main manifest did not contain any meta-data nodes below its
// application node, we still want to record the addition of any such
// meta-data nodes this manifest contains.
mergingReportBuilder
.getActionRecorder()
.recordAddedNodeAction(
lowerPriorityDocument.getXmlDocument().getRootNode(), true);
result = Optional.of(lowerPriorityDocument.getXmlDocument());
}
// if requested, dump each intermediary merging stage into the report.
if (mOptionalFeatures.contains(Invoker.Feature.KEEP_INTERMEDIARY_STAGES)
&& result.isPresent()) {
mergingReportBuilder.addMergingStage(result.get().prettyPrint());
}
return result;
}
/<xmldocument>/<xmldocument>/<xmldocument>

關鍵就在 if 和 else 中。

先看 if:

 public Optional<xmldocument> merge(
@NonNull XmlDocument lowerPriorityDocument,
@NonNull MergingReport.Builder mergingReportBuilder,
boolean addImplicitPermissions) {
if (getFileType() == Type.MAIN) {
mergingReportBuilder.getActionRecorder().recordAddedNodeAction(getRootNode(), false);
}
// 處理Manifest裡面tools裡的操作
getRootNode().mergeWithLowerPriorityNode(
lowerPriorityDocument.getRootNode(), mergingReportBuilder);
// 檢查uses-sdk, targetSdk,權限等
addImplicitElements(
lowerPriorityDocument, reparse(), mergingReportBuilder, addImplicitPermissions);
// force re-parsing as new nodes may have appeared.
return mergingReportBuilder.hasErrors()
? Optional.<xmldocument>absent()
: Optional.of(reparse());
}
/<xmldocument>/<xmldocument>

else 中的意思就是把 lowerPriorityDocument 的元素加到 mergingReportBuilder 中,有了則不加。

有一些例外:

  • <manifest>元素中的屬性絕不合並—僅使用優先級最高的清單中的屬性。/<manifest>
  • android:required 屬性 <uses-feature> 和 <uses-library> 元素使用 OR 合併,因此如果出現衝突,系統將應用 "true" 並始終包括某個清單所需的功能或庫。/<uses-library>/<uses-feature>
  • <uses-sdk>元素始終使用優先級較高的清單中的值,但以下情況除外:/<uses-sdk>
  • 如果低優先級清單的 minSdkVersion 值較高,除非您應用 overrideLibrary 合併規則。
  • 如果低優先級清單的 targetSdkVersion 值較低,合併工具將使用高優先級清單中的值,但也會添加任何必要的系統權限,以確保所導入的庫繼續正常工作(適用於較高的 Android 版本具有更多權限限制的情況)。
  • 絕不會在清單之間匹配<intent-filter>元素。 每個元素都被視為唯一元素,並添加至合併清單中的常用父元素。/<intent-filter>

參考詳細官方文檔。

2.3.2 createApkProcessResTask()

最終調用 LinkApplicationAndroidResourcesTask 的 doFullTaskAction() 方法,其中的核心方法又是 invokeAaptForSplit()。

BuildOutput invokeAaptForSplit(
BuildOutput manifestOutput,
@NonNull Set<file> dependencies,
Set<file> imports,
@NonNull SplitList splitList,
@NonNull Set<file> featureResourcePackages,
ApkInfo apkData,
boolean generateCode,
@Nullable Aapt2ServiceKey aapt2ServiceKey)
throws IOException {
...
// 文件路徑`build/intermediates/res/resources-${buildType}.ap_
// aapt執行完成後輸出的壓縮文件,裡面包含了所有資源文件,和最終的`resources.arsc`
File resOutBaseNameFile =
new File(
resPackageOutputFolder,
FN_RES_BASE
+ RES_QUALIFIER_SEP
+ apkData.getFullName()
+ SdkConstants.DOT_RES);
...
// 設置aapt2
AaptPackageConfig.Builder configBuilder =
new AaptPackageConfig.Builder()
.setManifestFile(manifestFile)
.setOptions(DslAdaptersKt.convert(aaptOptions))
.setCustomPackageForR(packageForR)
.setSymbolOutputDir(symbolOutputDir)

.setSourceOutputDir(srcOut)
.setResourceOutputApk(resOutBaseNameFile)
.setProguardOutputFile(proguardOutputFile)
.setMainDexListProguardOutputFile(mainDexListProguardOutputFile)
.setVariantType(getType())
.setDebuggable(getDebuggable())
.setResourceConfigs(splitList.getResourceConfigs())
.setSplits(getSplits(splitList))
.setPreferredDensity(preferredDensity)
.setPackageId(getResOffset())
.setAllowReservedPackageId(
resOffsetSupplier != null
&& resOffsetSupplier.get()
< FeatureSetMetadata.BASE_ID)
.setDependentFeatures(featurePackagesBuilder.build())
.setImports(imports)
.setIntermediateDir(getIncrementalFolder())
.setAndroidTarget(getBuilder().getTarget());
...
// 執行aapt2
ry (Aapt2DaemonManager.LeasedAaptDaemon aaptDaemon =
Aapt2DaemonManagerService.getAaptDaemon(aapt2ServiceKey)) {
AndroidBuilder.processResources(
aaptDaemon, configBuilder.build(), getILogger());
} catch (Aapt2Exception e) {
throw Aapt2ErrorUtils.rewriteLinkException(
e, new MergingLog(getMergeBlameLogFolder()));
}
...
}
/<file>/<file>/<file>

具體 aapt2 如何執行的,可以參考詳細官方文檔。

2.3.3 createCompileTask()

protected void createCompileTask(@NonNull VariantScope variantScope) {
TaskProvider extends JavaCompile> javacTask = createJavacTask(variantScope);
addJavacClassesStream(variantScope);
setJavaCompilerTask(javacTask, variantScope);
createPostCompilationTasks(variantScope);
}
  • createJavacTask()

先來看看 createJavacTask() 方法:

public TaskProvider extends JavaCompile> createJavacTask(@NonNull final VariantScope scope) {
taskFactory.register(new JavaPreCompileTask.CreationAction(scope));
boolean processAnnotationsTaskCreated = ProcessAnnotationsTask.taskShouldBeCreated(scope);
if (processAnnotationsTaskCreated) {
taskFactory.register(new ProcessAnnotationsTask.CreationAction(scope));
}
final TaskProvider extends JavaCompile> javacTask =
taskFactory.register(
new AndroidJavaCompile.CreationAction(
scope, processAnnotationsTaskCreated));
postJavacCreation(scope);
return javacTask;
}

這裡又註冊了 3 個 task,一個是預處理,一個是註解處理,一個就是編譯,調用 super.compile(inputs),調用 Java 的 JavaCompile,生成 class 文件。

  • createPostCompilationTasks()

再來看 createPostCompilationTasks() 方法,主要就是把 class 文件變成 dex。

public void createPostCompilationTasks(
@NonNull final VariantScope variantScope) {
checkNotNull(variantScope.getTaskContainer().getJavacTask());
final BaseVariantData variantData = variantScope.getVariantData();
final GradleVariantConfiguration config = variantData.getVariantConfiguration();
TransformManager transformManager = variantScope.getTransformManager();
// ---- Code Coverage first -----
boolean isTestCoverageEnabled =
config.getBuildType().isTestCoverageEnabled()
&& !config.getType().isForTesting()
&& !variantScope.getInstantRunBuildContext().isInInstantRunMode();
if (isTestCoverageEnabled) {
createJacocoTask(variantScope);
}
maybeCreateDesugarTask(variantScope, config.getMinSdkVersion(), transformManager);
AndroidConfig extension = variantScope.getGlobalScope().getExtension();
// Merge Java Resources.

createMergeJavaResTransform(variantScope);
// ----- External Transforms -----
// apply all the external transforms.
List<transform> customTransforms = extension.getTransforms();
List<list>> customTransformsDependencies = extension.getTransformsDependencies();
for (int i = 0, count = customTransforms.size(); i < count; i++) {
Transform transform = customTransforms.get(i);
List<object> deps = customTransformsDependencies.get(i);
transformManager.addTransform(
taskFactory,
variantScope,
transform,
null,
task -> {
if (!deps.isEmpty()) {
task.dependsOn(deps);
}
},
taskProvider -> {
// if the task is a no-op then we make assemble task depend on it.
if (transform.getScopes().isEmpty()) {
TaskFactoryUtils.dependsOn(
variantScope.getTaskContainer().getAssembleTask(),
taskProvider);
}
});
}
// Add transform to create merged runtime classes if this is a feature, a dynamic-feature,
// or a base module consuming feature jars. Merged runtime classes are needed if code
// minification is enabled in a project with features or dynamic-features.
if (variantData.getType().isFeatureSplit() || variantScope.consumesFeatureJars()) {
createMergeClassesTransform(variantScope);
}
// ----- Android studio profiling transforms
final VariantType type = variantData.getType();
if (variantScope.getVariantConfiguration().getBuildType().isDebuggable()
&& type.isApk()
&& !type.isForTesting()) {
boolean addDependencies = !type.isFeatureSplit();
for (String jar : getAdvancedProfilingTransforms(projectOptions)) {
if (jar != null) {
transformManager.addTransform(
taskFactory,
variantScope,
new CustomClassTransform(jar, addDependencies));
}
}
}
// ----- Minify next -----
CodeShrinker shrinker = maybeCreateJavaCodeShrinkerTransform(variantScope);

if (shrinker == CodeShrinker.R8) {
maybeCreateResourcesShrinkerTransform(variantScope);
maybeCreateDexSplitterTransform(variantScope);
// TODO: create JavaResSplitterTransform and call it here (http://b/77546738)
return;
}
// ----- 10x support
TaskProvider<precoldswaptask> preColdSwapTask = null;
if (variantScope.getInstantRunBuildContext().isInInstantRunMode()) {
TaskProvider extends Task> allActionsAnchorTask =
createInstantRunAllActionsTasks(variantScope);
assert variantScope.getInstantRunTaskManager() != null;
preColdSwapTask =
variantScope.getInstantRunTaskManager().createPreColdswapTask(projectOptions);
TaskFactoryUtils.dependsOn(preColdSwapTask, allActionsAnchorTask);
}
// ----- Multi-Dex support
DexingType dexingType = variantScope.getDexingType();
// Upgrade from legacy multi-dex to native multi-dex if possible when using with a device
if (dexingType == DexingType.LEGACY_MULTIDEX) {
if (variantScope.getVariantConfiguration().isMultiDexEnabled()
&& variantScope
.getVariantConfiguration()
.getMinSdkVersionWithTargetDeviceApi()
.getFeatureLevel()
>= 21) {
dexingType = DexingType.NATIVE_MULTIDEX;
}
}
if (variantScope.getNeedsMainDexList()) {
// ---------
// create the transform that's going to take the code and the proguard keep list
// from above and compute the main class list.
D8MainDexListTransform multiDexTransform = new D8MainDexListTransform(variantScope);
transformManager.addTransform(
taskFactory,
variantScope,
multiDexTransform,
taskName -> {
File mainDexListFile =
variantScope
.getArtifacts()
.appendArtifact(
InternalArtifactType.LEGACY_MULTIDEX_MAIN_DEX_LIST,
taskName,
"mainDexList.txt");
multiDexTransform.setMainDexListOutputFile(mainDexListFile);
},
null,
variantScope::addColdSwapBuildTask);

}
if (variantScope.getNeedsMainDexListForBundle()) {
D8MainDexListTransform bundleMultiDexTransform =
new D8MainDexListTransform(variantScope, true);
variantScope
.getTransformManager()
.addTransform(
taskFactory,
variantScope,
bundleMultiDexTransform,
taskName -> {
File mainDexListFile =
variantScope
.getArtifacts()
.appendArtifact(
InternalArtifactType
.MAIN_DEX_LIST_FOR_BUNDLE,
taskName,
"mainDexList.txt");
bundleMultiDexTransform.setMainDexListOutputFile(mainDexListFile);
},
null,
null);
}
createDexTasks(variantScope, dexingType);
...
}
/<precoldswaptask>/<object>/<list>/<transform>

這裡的 TransformManager 就是管理 Transform 的,我們可以通過 Transform 在打包 dex 文件之前的編譯過程中操作 .class 文件,而 proguard,multi-dex,Instant-Run 都是通過 transform 實現的。做混淆相關的操作 ProGuardTransform 和 ShrinkResourcesTransform。dex 相關的操作在 DexArchiveBuilderTransform。

具體 dex 如何執行的,可以參考詳細官方文檔。

2.3.4 createPackagingTask()

public void createPackagingTask(
@NonNull VariantScope variantScope,
@Nullable TaskProvider<buildinfowritertask> fullBuildInfoGeneratorTask) {
...
// 註冊了一個打包task,PackageApplication extends PackageAndroidArtifact,doFullTaskAction()方法最終調了splitFullAction(apkInfo, inputFile)

new PackageApplication.StandardCreationAction(
variantScope,
outputDirectory,
useSeparateApkForResources
? INSTANT_RUN_MAIN_APK_RESOURCES
: resourceFilesInputType,
manifests,
manifestType,
variantScope.getOutputScope(),
globalScope.getBuildCache(),
taskOutputType)
...
// 添加各種task依賴
...
// 安裝
if (signedApk) {
createInstallTask(variantScope);
}
...
}
/<buildinfowritertask>

PackageAndroidArtifact 的 splitFullAction() 方法:

public File splitFullAction(@NonNull ApkInfo apkData, @Nullable File processedResources)
throws IOException {
// 清緩存,添加路徑
...
doTask(
apkData,
incrementalDirForSplit,
outputFile,
cacheByPath,
manifestOutputs,
updatedDex,
updatedJavaResources,
updatedAssets,
updatedAndroidResources,
updatedJniResources);
...
// 更新文件
}

doTask() 方法最終會執行:

// 更新dex,JavaResources,Assets,AndroidResources,NativeLibraries 

packager.updateDex(dexFilesToPackage);
packager.updateJavaResources(changedJavaResources);
packager.updateAssets(changedAssets);
packager.updateAndroidResources(changedAndroidResources);
packager.updateNativeLibraries(changedNLibs);

這幾個方法最終都會執行 mApkCreator.writeFile(out, rf.getName()); 和 mApkCreator.writeZip(arch, pathNameMap::get, name -> !names.contains(name));,

而 mApkCreator 其實就是 ApkZFileCreator 對象,最終 ZFile 的 add() 方法。

ZFile 又是什麼呢?

public static ZFile apk(
File f,
ZFileOptions options,
@Nullable PrivateKey key,
ImmutableList<x509certificate> certificates,
boolean v1SigningEnabled,
boolean v2SigningEnabled,
@Nullable String builtBy,
@Nullable String createdBy,
int minSdkVersion)
throws IOException {
ZFile zfile = apk(f, options);
if (builtBy == null) {
builtBy = DEFAULT_BUILD_BY;
}
if (createdBy == null) {
createdBy = DEFAULT_CREATED_BY;
}
ManifestGenerationExtension manifestExt = new ManifestGenerationExtension(builtBy, createdBy);
manifestExt.register(zfile);
// 用於簽名
if (key != null && !certificates.isEmpty()) {
try {
new SigningExtension(minSdkVersion, certificates, key, v1SigningEnabled, v2SigningEnabled)
.register(zfile);
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
throw new IOException("Failed to create signature extensions", e);
}
}
return zfile;
}
/<x509certificate>

ZFile 作用就把所有的 Dex、JavaResources、Assets、 AndroidResources、 JniResources,寫入 Apk 文件中。

最後再來看一下官網給的詳細編譯流程圖:

Android App 編譯流程詳解


這邊只講了一部分,更多的 task 大家可以自行查看源碼。

3 構建過程中,我們可以做些什麼

先來說說 Gradle 生命週期:

  • 初始化階段(Initiliazation phase),執行 settings.gradle,並將每個 Project 內的build.gradle 轉化為 Project 對象。
  • Configration 階段,配置對應的 Project 對象,執行 build.gradle 中的代碼,Configuration 階段完了後,整個 build 的 project 以及內部的 Task 關係就確定了。Configuration 會建立一個有向圖來描述 Task 之間的依賴關係。
  • 執行階段,執行相應的 Task。

每個生命週期後都能 hook:

  1. 我們可以在 setting 中設置不同的 gradle,本地和遠端用不同的,可以引入不同的 moudle 等;
  2. 可以在 build.gradle 定義各種變體,可以設置使用本地 moudle 還是遠端 moudle 等;
  3. 我們可以在任意 Task 前插入自己的 Task,編譯時增刪改 Java 代碼,修改字節碼等等,各種插件、hook 代碼都是在這裡做的,還可以做模塊熱插拔,優化編譯速度等等。

4 總結

本文主要講了Android Gradle plugin 中主要的構建流程,拋磚引玉,講一講構建過程中我們可以做的事情。Android Gradle plugin 也在不停地更新優化,細節也很多,但是通過這篇文章希望大家都能對 Android Gradle plugin 有一個整體的把控,遇到問題能夠知道去哪找到源碼,通過打 log、debug 的方式自行分析。


分享到:


相關文章: