Android 探索之 Task 分析(三)
看到这里才是真正的开始分析,let's go
本文将会分析如下几个task
preBuild
extractProguardFiles
preReleaseBuild
compileReleaseAidl
compileReleaseRenderscript
checkReleaseManifest
1.preBuild
啥都没有做,它就是一个空的task,做锚点用的,这就是整个打包任务的起点,我们可以hook prebuild做一些特殊的事情,比如build之前下载插件等。
源码分析
我们来找到TaskManager
中 createDefaultPreBuildTask
方法到底做了什么,
protected DefaultTask createDefaultPreBuildTask(@NonNull VariantScope scope) {
return taskFactory.create(
scope.getTaskName("pre", "Build"),
task -> scope.getVariantData().preBuildTask = task);
}
输入和输出
-----task begin-------->
project: project ':app'
name: preBuild
group: null
description: null
conv: [:]
inputs:
outputs:
<------task end -------
从输出日志来看,也并没有任何输入和输出
2.extractProguardFiles
解压特定的内置prugard规则到指定目录
源码分析
//com.android.build.gradle.internal.tasks.ExtractProguardFiles
public class ExtractProguardFiles extends DefaultTask {
private final ImmutableList generatedFiles;
public ExtractProguardFiles() {
ImmutableList.Builder outputs = ImmutableList.builder();
for (String name : ProguardFiles.KNOWN_FILE_NAMES) {
outputs.add(ProguardFiles.getDefaultProguardFile(name, getProject()));
}
this.generatedFiles = outputs.build();
}
@OutputFiles
public List getGeneratedFiles() {
return generatedFiles;
}
@TaskAction
public void run() throws IOException {
for (String name : ProguardFiles.KNOWN_FILE_NAMES) {
File defaultProguardFile = ProguardFiles.getDefaultProguardFile(name, getProject());
if (!defaultProguardFile.isFile()) {
ProguardFiles.createProguardFile(name, defaultProguardFile);
}
}
}
}
我们发现,该task会在根目录的build/intermediates/proguard-files文件夹下生成三个文件,请注意此处是根目录,非app主目录
这些源文件在 com.android.tools.build:gradle-core java resource 的 rules 目录下
这个rules目录下的文件为proguard的预制rules
在app打包过程中,ProGuard rules 的来源主要分为 4 类:
- 预置 rules:默认有三种
proguard-android.txt
,proguard-android-optimize.txt
,proguard-defaults.txt
, 在 Gradle 在编译的时候通过任务extractProguardFiles
将预置在依赖 com.android.tools.build:gradle-core java resource 的 rules 解压到根项目 build/intermediates/proguard-files 文件下。
默认引入的是proguard-android.txt
。 该项关闭了Optimize
。如果想开启Optimize
可以引用proguard-android-optimize.txt
或者不使用预置的 rules 。 - project rules:定义在主工程的 rules
- aar rules:每个 library 携带关于自身的一份 rules。
- aapt_rules:aapt 在资源时候生成。
我们在配置build.gradle 的时候会配置如下内容
后续分析proguard流程时再看
输入和输出
-----task begin-------->
project: project ':app_driver'
name: extractProguardFiles
group: null
description: null
conv: [:]
inputs:
outputs:
.../app/build/intermediates/proguard-files/proguard-android-optimize.txt-3.1.4
.../app/build/intermediates/proguard-files/proguard-defaults.txt-3.1.4
.../app/build/intermediates/proguard-files/proguard-android.txt-3.1.4
<------task end -------
3.preReleaseBuild
主要是得到compile 和 runtime的依赖包并对其做一些版本号,依赖等的校验工作。
源码分析
核心代码
// com.android.build.gradle.internal.tasks.AppPreBuildTask
@TaskAction
void run() {
// 1、compile 和 runtime的依赖包
Set compileArtifacts = compileManifests.getArtifacts();
Set runtimeArtifacts = runtimeManifests.getArtifacts();
// 2、runtimeIds,(key:group+module,value:version)
// Artifact 组成(group:module:version)
// eg:com.android.support:appcompat-v7:26.1.0,group:com.android.support,module:appcompat-v7, version:26.1.0
// create a map where the key is either the sub-project path, or groupId:artifactId for
// external dependencies.
// For external libraries, the value is the version.
Map<String, String> runtimeIds = Maps.newHashMapWithExpectedSize(runtimeArtifacts.size());
// 3、生成runtimeIds
// build a list of the runtime artifacts
for (ResolvedArtifactResult artifact : runtimeArtifacts) {
handleArtifact(artifact.getId().getComponentIdentifier(), runtimeIds::put);
}
// 4、对compile的依赖包进行校验(版本、依赖属性等)
// run through the compile ones to check for provided only.
for (ResolvedArtifactResult artifact : compileArtifacts) {
final ComponentIdentifier compileId = artifact.getId().getComponentIdentifier();
handleArtifact(
compileId,
(key, value) -> {
String runtimeVersion = runtimeIds.get(key);
if (runtimeVersion == null) {
String display = compileId.getDisplayName();
throw new RuntimeException(
"Android dependency '"
+ display
+ "' is set to compileOnly/provided which is not supported");
} else if (!runtimeVersion.isEmpty()) {
// compare versions.
if (!runtimeVersion.equals(value)) {
throw new RuntimeException(
String.format(
"Android dependency '%s' has different version for the compile (%s) and runtime (%s) classpath. You should manually set the same version via DependencyResolution",
key, value, runtimeVersion));
}
}
});
}
}
从以上代码可以看出来,常见到的运行时和编译时版本不一致的error就是从这里报出来的。'Android dependency '%s' has different version for the compile (%s) and runtime (%s) classpath. You should manually set the same version via DependencyResolution'
此处check是基于manifest而不是基于dependency,
比如你在apk中compileOnly了一个libA,此时生成的 的依赖结构中compile的classpath是有libA的,但是在runtime的classpath中是没有libA的,这是日常常识,but,如果这时候打一个apk并且存在compileOnly的是一个aar,由于aar中存在manifest的,manifest实际上是存在某些声明metadata等信息,此时,从架构上来讲,是不允许app继续打包的,此处就是通过manifest进行反向校验依赖包
输入和输出
-----task begin-------->
project: project ':app_driver'
name: preReleaseBuild
group: null
description: null
conv: [:]
inputs:
.../app/ymm_app_driver_main_module/build/intermediates/manifests/full/release/AndroidManifest.xml
/Users/dongkai/.gradle/caches/transforms-1/files-1.1/activity_result_util-1.0.1.aar/4b6d13d6fe7210d069c29939ba8fea95/AndroidManifest.xml
/Users/dongkai/.gradle/caches/transforms-1/files-1.1/biz_kefu-1.0.2.aar/ee1777acecc24eeb4e8b78c1aae4dd65/AndroidManifest.xml
/Users/dongkai/.gradle/caches/transforms-1/files-1.1/biz_kefu_service-1.0.1.aar/c04a2060a018ecf52b962c7ce4ef6a71/AndroidManifest.xml
...
...
...
/Users/dongkai/.gradle/caches/transforms-1/files-1.1/amr-1.1.1.aar/87551331cd242da3aded6c076e32f2f7/AndroidManifest.xml
outputs:
.../app/app_driver/build/intermediates/prebuild/release
<------task end -------
从源码也可以看出来,输入为 compile
和 runtime
的manifest文件,输出为 prebuild 目录,preBuild内容为空
4.compileReleaseAidl
源码分析
//com.android.build.gradle.tasks.AidlCompile
//gradle配置阶段执行代码(AidlCompile->ConfigAction->execute()),主要设置输入输出等配置信息
public static class ConfigAction implements TaskConfigAction<AidlCompile> {
@NonNull
VariantScope scope;
public ConfigAction(@NonNull VariantScope scope) {
this.scope = scope;
}
@Override
@NonNull
public String getName() {
return scope.getTaskName("compile", "Aidl");
}
@Override
@NonNull
public Class<AidlCompile> getType() {
return AidlCompile.class;
}
@Override
public void execute(@NonNull AidlCompile compileTask) {
final VariantConfiguration<?, ?, ?> variantConfiguration = scope
.getVariantConfiguration();
scope.getVariantData().aidlCompileTask = compileTask;
compileTask.setAndroidBuilder(scope.getGlobalScope().getAndroidBuilder());
compileTask.setVariantName(scope.getVariantConfiguration().getFullName());
compileTask.setIncrementalFolder(scope.getIncrementalDir(getName()));
compileTask.sourceDirs = TaskInputHelper
.bypassFileSupplier(variantConfiguration::getAidlSourceList);
compileTask.importDirs = scope.getArtifactFileCollection(
COMPILE_CLASSPATH, ALL, AIDL);
compileTask.setSourceOutputDir(scope.getAidlSourceOutputDir());
if (variantConfiguration.getType() == VariantType.LIBRARY) {
File packagedAidlDir = scope.getPackagedAidlDir();
compileTask.setPackagedDir(packagedAidlDir);
scope.addTaskOutput(
TaskOutputHolder.TaskOutputType.AIDL_PARCELABLE,
packagedAidlDir,
getName());
compileTask.setPackageWhitelist(
scope.getGlobalScope().getExtension().getAidlPackageWhiteList());
}
}
}
AidlCompile.java
中的doFullTaskAction()
方法,看和核心逻辑compileAllFiles(processor)
;
@Override
protected void doFullTaskAction() throws IOException {
.....
DepFileProcessor processor = new DepFileProcessor();
try {
// 核心
compileAllFiles(processor);
} catch (Exception e) {
throw new RuntimeException(e);
}
.....
}
compileAllFiles()
方法调用getBuilder().compileAllAidlFiles()
/**
* Action methods to compile all the files.
*
* <p>The method receives a {@link DependencyFileProcessor} to be used by the {@link
* com.android.builder.internal.compiler.SourceSearcher.SourceFileProcessor} during the
* compilation.
*
* @param dependencyFileProcessor a DependencyFileProcessor
*/
private void compileAllFiles(DependencyFileProcessor dependencyFileProcessor)
throws InterruptedException, ProcessException, IOException {
getBuilder().compileAllAidlFiles(
sourceDirs.get(),
getSourceOutputDir(),
getPackagedDir(),
getPackageWhitelist(),
getImportDirs().getFiles(),
dependencyFileProcessor,
new LoggedProcessOutputHandler(getILogger()));
}
AndroidBuilder
类中的compileAllAidlFiles()方法
/**
* Compiles all the aidl files found in the given source folders.
*
* @param sourceFolders all the source folders to find files to compile
* @param sourceOutputDir the output dir in which to generate the source code
* @param packagedOutputDir the output dir for the AIDL files that will be packaged in an aar
* @param packageWhiteList a list of AIDL FQCNs that are not parcelable that should also be
* packaged in an aar
* @param importFolders import folders
* @param dependencyFileProcessor the dependencyFileProcessor to record the dependencies of the
* compilation.
* @throws IOException failed
* @throws InterruptedException failed
*/
public void compileAllAidlFiles(
@NonNull Collection<File> sourceFolders,
@NonNull File sourceOutputDir,
@Nullable File packagedOutputDir,
@Nullable Collection<String> packageWhiteList,
@NonNull Collection<File> importFolders,
@Nullable DependencyFileProcessor dependencyFileProcessor,
@NonNull ProcessOutputHandler processOutputHandler)
throws IOException, InterruptedException, ProcessException {
......
IAndroidTarget target = mTargetInfo.getTarget();
BuildToolInfo buildToolInfo = mTargetInfo.getBuildTools();
// 1、aidl 工具名称
String aidl = buildToolInfo.getPath(BuildToolInfo.PathId.AIDL);
if (aidl == null || !new File(aidl).isFile()) {
throw new IllegalStateException("aidl is missing from '" + aidl + "'");
}
List<File> fullImportList = Lists.newArrayListWithCapacity(
sourceFolders.size() + importFolders.size());
fullImportList.addAll(sourceFolders);
fullImportList.addAll(importFolders);
// 2、初始化AidlPeocessor
AidlProcessor processor = new AidlProcessor(
aidl,
target.getPath(IAndroidTarget.ANDROID_AIDL),
fullImportList,
sourceOutputDir,
packagedOutputDir,
packageWhiteList,
dependencyFileProcessor != null ?
dependencyFileProcessor : DependencyFileProcessor.NO_OP,
mProcessExecutor,
processOutputHandler);
// 3、执行aidl工具生产相应的java文件
for (File dir : sourceFolders) {
DirectoryWalker.builder()
.root(dir.toPath())
.extensions("aidl")
.action(processor)
.build()
.walk();
}
}
通过上面代码分析以及输入输出文件,可以知道此task主要是找到需要编译的aidl文件,然后调用aidl工具生成相应的java接口。
>输入和输出
-----task begin-------->
project: project ':app'
name: compileReleaseAidl
group: null
description: null
conv: [:]
inputs:
.../app/ymm_app_main_module/build/intermediates/packaged-aidl/release
.../app/biz_common/build/intermediates/packaged-aidl/release
/Users/dongkai/.gradle/caches/transforms-1/files-1.1/support-media-compat-26.1.0.aar/ea1ac66d3c730f3c05fe5e221aef35b9/aidl
/Users/dongkai/.gradle/caches/transforms-1/files-1.1/support-compat-26.1.0.aar/b2a6de2785dce92886a3ebef9b061222/aidl
/Users/dongkai/.gradle/caches/transforms-1/files-1.1/mmkv-static-1.0.17.aar/0c39c42d42d3b884ed1b1cdbff217b58/aidl
outputs:
.../app/app/build/intermediates/incremental/compileReleaseAidl
.../app/app/build/generated/source/aidl/release
<------task end -------
5.compileReleaseRenderscript
Renderscript是android平台上进行高性能计算的框架。Renderscript主要面向并行计算,虽然它对计算密集型工作也是有益的。Renderscript在运行时将在设备上可用的处理器间平衡负载,比如多核CPU,GPU或者DSP,它让你专注于算法而不是平衡负载。RenderScript对图像处理,计算摄影学,计算机视觉方面的应用非常有用。
这个task就是处理RenderScript的,感兴趣的可以查看源码仔细分析,由于日常的打包不会使用到RenderScript,暂不分析,只需要知道是处理RenderScript即可。
源码分析
//com.android.build.gradle.tasks.RenderscriptCompile
输入和输出
-----task begin-------->
project: project ':app_driver'
name: compileReleaseRenderscript
group: null
description: null
conv: [:]
inputs:
.../app/ymm_app_driver_main_module/build/intermediates/renderscript_headers/release
.../app/biz_common/build/intermediates/renderscript_headers/release
.../app/app_driver/src/main/rs
.../app/app_driver/src/release/rs
outputs:
.../app/app_driver/build/intermediates/rs/release/lib
.../app/app_driver/build/intermediates/rs/release/obj
.../app/app_driver/build/generated/res/rs/release
.../app/app_driver/build/generated/source/rs/release
<------task end -------
6.checkReleaseManifest
此task在配置阶段得到manifest文件,在执行阶段做一个简单的文件校验工作。
if (!isOptional && manifest != null && !manifest.isFile()) 判断是否为空,是否是文件,以及是否存在
源码分析
//com.android.build.gradle.internal.tasks.CheckManifest
// 主要代码逻辑
@Override
public void execute(@NonNull CheckManifest checkManifestTask) {
scope.getVariantData().checkManifestTask = checkManifestTask;
checkManifestTask.setVariantName(
scope.getVariantData().getVariantConfiguration().getFullName());
checkManifestTask.setOptional(isManifestOptional);
// 1、得到MainManifest文件
checkManifestTask.manifest =
scope.getVariantData().getVariantConfiguration().getMainManifest();
// 2、outputs路径
checkManifestTask.fakeOutputDir =
new File(
scope.getGlobalScope().getIntermediatesDir(),
"check-manifest/" + scope.getVariantConfiguration().getDirName());
}
//task执行逻辑,逻辑很简单,仅作简单的manifest文件校验;
@TaskAction
void check() {
if (!isOptional && manifest != null && !manifest.isFile()) {
throw new IllegalArgumentException(
String.format(
"Main Manifest missing for variant %1$s. Expected path: %2$s",
getVariantName(), getManifest().getAbsolutePath()));
}
}
输入和输出
-----task begin-------->
project: project ':app_driver'
name: checkReleaseManifest
group: null
description: null
conv: [:]
inputs:
outputs:
.../app/app_driver/build/intermediates/check-manifest/release
<------task end -------
其实依旧没啥输出,目录为空的,仅仅check