// 说明 Task 支持增量
protected boolean isIncremental() {
return true;
}
- doFullTaskAction
- 通过 getConfiguredResourceSets() 获取 resourceSets,包括了自己的 res/ 和 依赖库的 res/ 以及 build/generated/res/rs
// MergeResources.doFullTaskAction()
List resourceSets = getConfiguredResourceSets(preprocessor);
- 创建 ResourceMerger
// MergeResources.doFullTaskAction()
ResourceMerger merger = new ResourceMerger(minSdk);
- 创建 QueueableResourceCompiler,因为 gradle3.x 以后支持了 aapt2,所以这里有两种选择 aapt 和 aapt2。其中 aapt2 有三种模式,OutOfProcessAaptV2,AaptV2Jni,QueueableAapt2,这里默认创建了 QueueableAapt2,resourceCompiler = QueueableAapt2
// MergeResources.doFullTaskAction()
// makeAapt 中会判断使用 aapt 还是 aapt2,这里以 aapt2 为例,返回的是 QueueableAapt2 对象
QueueableResourceCompiler resourceCompiler =
makeAapt(
aaptGeneration,
getBuilder(),
fileCache,
crunchPng,
variantScope,
getAaptTempDir(),
mergingLog)
- 将第一步获取的 resourceSet 加入 ResourceMerger 中
for (ResourceSet resourceSet : resourceSets) {
resourceSet.loadFromFiles(getILogger());
merger.addDataSet(resourceSet);
}
- 创建 MergedResourceWriter
- 调用 ResourceMerger.mergeData 合并资源
// MergeResources.doFullTaskAction()
merger.mergeData(writer, false /doCleanUp/);
- 调用 MergedResourceWriter 的 start(),addItem(),end() 方法,伪代码如下:
// DataMerger.mergeData
consumer.start()
for item in sourceSets:
// item 包括了需要处理的资源,包括 xml 和 图片资源,每一个 item 对应的文件,会创建一个 CompileResourceRequest 对象,加入到 mCompileResourceRequests 里
consumer.addItem(item)
consumer.end()
- 调用 QueueableAapt2 -> Aapt2QueuedResourceProcessor -> AaptProcess 处理资源
// MergedResourceWriter.end()
Future result = this.mResourceCompiler.compile(new CompileResourceRequest(fileToCompile, request.getOutput(), request.getFolderName(), this.pseudoLocalesEnabled, this.crunchPng));
// AaptProcess.compile
public void compile(
@NonNull CompileResourceRequest request,
@NonNull Job job,
@Nullable ProcessOutputHandler processOutputHandler)
throws IOException {
// …
// 使用 AaptV2CommandBuilder 生成 aapt2 命令
mWriter.write(joiner.join(AaptV2CommandBuilder.makeCompile(request)));
mWriter.flush(); // 输出命令
}
这一步调用 aapt2 命令去处理资源,处理完以后 xxx.xml.flat 格式
- doIncrementalTaskAction
增量任务过程和全量其实差异不大,只不过是在获取 resourceSets 的时候,使用的是修改后的文件
4.3 processDebugResources
4.3.1 实现类
ProcessAndroidResources
4.3.2 整体实现图
4.3.3 调用链路
ProcessAndroidResources.doFullTaskAction -> ProcessAndroidResources.invokeAaptForSplit -> AndroidBuilder.processResources -> QueueAapt2.link -> Aapt2QueuedResourceProcessor.link -> AaptProcess.link -> AaptV2CommandBuilder.makeLink
4.3.4 主要代码分析
ProcessAndroidResources 也是继承自 IncrementalTask,但是没有重写 isIncremental,所以不是增量的 Task,直接看 doFullTaskAction 即可
- doFullTaskAction
这个里面代码虽然多,但是主要的逻辑比较简单,就是调用 aapt2 link 去生成资源包。
这里会处理 splits apk 相关的内容,关于 splits apk 具体可以查看 splits apk,简单来说,就是可以按照屏幕分辨率,abis 来生成不同的 apk,从而让特定用户的安装包变小。
分下面几个步骤:
- 获取 split 数据
List splitsToGenerate =
getApksToGenerate(outputScope, supportedAbis, buildTargetAbi, buildTargetDensity);
返回的是一个 ApkData 列表,ApkData 有三个子类,分别是 Main,Universal,FullSplit
我们配置 如下:
android {
splits {
// Configures multiple APKs based on screen density.
density {
// Configures multiple APKs based on screen density.
enable true
// Specifies a list of screen densities Gradle should not create multiple APKs for.
exclude “ldpi”, “xxhdpi”, “xxxhdpi”
// Specifies a list of compatible screen size settings for the manifest.
compatibleScreens ‘small’, ‘normal’, ‘large’, ‘xlarge’
}
}
}
这里的 ApkData 会返回一个 Universal 和多个 FullSplit,Universal 代表的是主 apk,FullSplit 就是根据屏幕密度拆分的 apk。
如果我们没有配置 splits apk,那么这里只会返回一个 Main 的实例,标识完整的 apk。
2. 先处理 main 和 不依赖 density 的 ApkData 资源
// ProcessAndroidResources.doFullTaskAction
List apkDataList = new ArrayList<>(splitsToGenerate);
for (ApkData apkData : splitsToGenerate) {
if (apkData.requiresAapt()) {
// 这里只处理 main 和不依赖 density 的资源
boolean codeGen =
(apkData.getType() == OutputFile.OutputType.MAIN
|| apkData.getFilter(OutputFile.FilterType.DENSITY) == null);
if (codeGen) {
apkDataList.remove(apkData);
invokeAaptForSplit(
manifestsOutputs,
libraryInfoList,
packageIdFileSet,
splitList,
featureResourcePackages,
apkData,
codeGen,
aapt);
break;
}
}
}
- 调用 invokeAaptForSplit 处理资源
// ProcessAndroidResources.invokeAaptForSplit
void invokeAaptForSplit(…) {
// …
String packageForR = null;
File srcOut = null;
File symbolOutputDir = null;
File proguardOutputFile = null;
File mainDexListProguardOutputFile = null;
// 如果传了 generateCode 参数,会生成 R.java
if (generateCode) {
packageForR = originalApplicationId;
// we have to clean the source folder output in case the package name changed.
srcOut = getSourceOutputDir();
if (srcOut != null) {
FileUtils.cleanOutputDir(srcOut);
}
symbolOutputDir = textSymbolOutputDir.get();
proguardOutputFile = getProguardOutputFile();
mainDexListProguardOutputFile = getMainDexListProguardOutputFile();
}
// …
getBuilder().processResources(aapt, config);
}
- 调用 AndroidBuilder.processResources -> QueueAapt2.link -> Aapt2QueuedResourceProcessor.link -> AaptProcess.link -> AaptV2CommandBuilder.makeLink 处理资源,生成资源包以及 R.java 文件
- 处理其他 ApkData 资源,这里只会生成资源包而不会生成 R.java 文件
关于 aapt2 的 compile 和 link 参数,可以在 developer.android.com/studio/comm… 这里看
4.4 processDebugManifest
4.4.1 实现类
MergeManifests
4.4.2 整体实现图
4.4.3 调用链路
MergeManifests.dofFullTaskAction -> AndroidBuilder.mergeManifestsForApplication -> Invoker.merge -> ManifestMerge2.merge
4.4.4 主要代码分析
MergeManifests 也是继承了 IncrementalTask,但是没有实现 isIncremental,所以只看其 doFullTaskAction 即可。
这个 task 功能主要是合并 mainfest,包括 module 和 flavor 里的,整个过程通过 MergingReport,ManifestMerger2 和 XmlDocument 进行。
这里直接看 ManifestMerger2.merge() 的 merge 过程 。 主要有几个步骤:
- 获取依赖库的 manifest 信息,用 LoadedManifestInfo 标识
- 获取主 module 的 manifest 信息
- 替换主 module 的 Manifest 中定义的某些属性,替换成 gradle 中定义的属性 例如: package, version_code, version_name, min_sdk_versin 等等
performSystemPropertiesInjection(mergingReportBuilder, xmlDocumentOptional.get());
// ManifestMerger2.performSystemPropertiesInjection
protected void performSystemPropertiesInjection(
@NonNull MergingReport.Builder mergingReport,
@NonNull XmlDocument xmlDocument) {
for (ManifestSystemProperty manifestSystemProperty : ManifestSystemProperty.values()) {
String propertyOverride = mSystemPropertyResolver.getValue(manifestSystemProperty);
if (propertyOverride != null) {
manifestSystemProperty.addTo(
mergingReport.getActionRecorder(), xmlDocument, propertyOverride);
}
}
}
- 合并 flavor,buildType 中的 manifest
for (File inputFile : mFlavorsAndBuildTypeFiles) {
LoadedManifestInfo overlayDocument = load(
new ManifestInfo(null, inputFile, XmlDocument.Type.OVERLAY,
Optional.of(mainPackageAttribute.get().getValue())),
selectors,
mergingReportBuilder);
// 检查 package 定义
Optional packageAttribute =
overlayDocument.getXmlDocument().getPackage();
if (loadedMainManifestInfo.getOriginalPackageName().isPresent() &&
packageAttribute.isPresent()
&& !loadedMainManifestInfo.getOriginalPackageName().get().equals(
packageAttribute.get().getValue())) {
// 如果 package 定义重复的话,会输出下面信息,我们平时应该或多或少见过类似的错误
String message = mMergeType == MergeType.APPLICATION
? String.format(
“Overlay manifest:package attribute declared at %1
s
v
a
l
u
e
=
(
s value=(%2
svalue=(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 v a l u e = ( s value=(%2 svalue=(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());
// …
return mergingReportBuilder.build();
}
}
- 合并依赖库的 manifest
for (LoadedManifestInfo libraryDocument : loadedLibraryDocuments) {
mLogger.verbose("Merging library manifest " + libraryDocument.getLocation());
xmlDocumentOptional = merge(
xmlDocumentOptional, libraryDocument, mergingReportBuilder);
if (!xmlDocumentOptional.isPresent()) {
return mergingReportBuilder.build();
}
}
- 处理 manifest 的 placeholders
performPlaceHolderSubstitution(loadedMainManifestInfo, xmlDocumentOptional.get(), mergingReportBuilder, severity);
- 之后对最终合并后的 manifest 中的一些属性重新进行一次替换,类似步骤 4
- 保存 manifest 到 build/intermediates/manifest/fullxxx/AndroidManifest.xml 这就生成了最终的 Manifest 文件
4.5 transformClassesWithDexBuilderForDebug
4.5.1 实现类
DexArchiveBuilderTransform
4.5.2 整体实现图
4.5.3 调用链路
DexArchiveBuilderTransform.transform -> DexArchiveBuilderTransform.convertJarToDexArchive -> DexArchiveBuilderTransform.convertToDexArchive -> DexArchiveBuilderTransform.launchProcessing -> DxDexArchiveBuilder.convert
4.5.4 主要代码分析
在 DexArchiveBuilderTransform 中,对 class 的处理分为两种方式,一种是对 目录下的 class 进行处理,一种是对 .jar 里的 class 进行处理。
为什么要分为这两种方式呢?.jar 中的 class 一般来说都是依赖库,基本上不会改变,gradle 在这里做了一个缓存,但是两种方式最终都会调用到 convertToDexArchive,可以说是殊途同归吧。
- convertJarToDexArchive 处理 jar
处理 .jar 时,会对 jar 包中的每一个 class 都单独打成一个 .dex 文件,之后还是放在 .jar 包中
private List convertJarToDexArchive(
@NonNull Context context,
@NonNull JarInput toConvert,
@NonNull TransformOutputProvider transformOutputProvider)
throws Exception {
File cachedVersion = cacheHandler.getCachedVersionIfPresent(toConvert);
if (cachedVersion == null) {
// 如果没有缓存,调用 convertToDexArchive 去生成 dex
return convertToDexArchive(context, toConvert, transformOutputProvider, false);
} else {
// 如果有缓存,直接使用缓存的 jar
File outputFile = getPreDexJar(transformOutputProvider, toConvert, null);
Files.copy(
cachedVersion.toPath(),
outputFile.toPath(),
StandardCopyOption.REPLACE_EXISTING);
// no need to try to cache an already cached version.
return ImmutableList.of();
}
}
- convertToDexArchive 处理 dir 以及 jar 的后续处理
对 dir 处理使用 convertToDexArchive
其中会调用 launchProcessing
private static void launchProcessing(
@NonNull DexConversionParameters dexConversionParameters,
@NonNull OutputStream outStream,
@NonNull OutputStream errStream)
throws IOException, URISyntaxException {
// …
boolean hasIncrementalInfo =
dexConversionParameters.isDirectoryBased() && dexConversionParameters.isIncremental;
// 判断 class 是否新增或者修改过,如果新增或者修改过,就需要处理
学习分享
在当下这个信息共享的时代,很多资源都可以在网络上找到,只取决于你愿不愿意找或是找的方法对不对了
很多朋友不是没有资料,大多都是有几十上百个G,但是杂乱无章,不知道怎么看从哪看起,甚至是看后就忘
如果大家觉得自己在网上找的资料非常杂乱、不成体系的话,我也分享一套给大家,比较系统,我平常自己也会经常研读。
2021最新上万页的大厂面试真题
七大模块学习资料:如NDK模块开发、Android框架体系架构…
只有系统,有方向的学习,才能在段时间内迅速提高自己的技术。
这份体系学习笔记,适应人群:
**第一,**学习知识比较碎片化,没有合理的学习路线与进阶方向。
**第二,**开发几年,不知道如何进阶更进一步,比较迷茫。
**第三,**到了合适的年纪,后续不知道该如何发展,转型管理,还是加强技术研究。如果你有需要,我这里恰好有为什么,不来领取!说不定能改变你现在的状态呢!
由于文章内容比较多,篇幅不允许,部分未展示内容以截图方式展示 。如有需要获取完整的资料文档的朋友点击我的【GitHub】免费获取。
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级安卓工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年最新Android移动开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Android)
技术停滞不前!**
因此收集整理了一份《2024年最新Android移动开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-s9d25Wrd-1710987210859)]
[外链图片转存中…(img-R3cY1sWP-1710987210860)]
[外链图片转存中…(img-BicATiqQ-1710987210860)]
[外链图片转存中…(img-QXXXd1x5-1710987210861)]
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Android)
[外链图片转存中…(img-mOCaOiaP-1710987210861)]