Android|Android gradle打包涉及task源码解析(五)
文章序号
- Android gradle打包涉及task源码解析(一)准备工作
- Android gradle打包涉及task源码解析(二)
- Android gradle打包涉及task源码解析(三)
- Android gradle打包涉及task源码解析(四)
- Android gradle打包涉及task源码解析(五)
- Android gradle打包涉及task源码解析(六)
此篇文章将分析如下3个task。
:app:transformClassesWithDexBuilderForDebug
:app:transformDexArchiveWithExternalLibsDexMergerForDebug
:app:transformDexArchiveWithDexMergerForDebug
transform vs task
本篇文章主要分析transform相关的任务,分析transform任务之前跟大家大致的聊下transform和task的关联。
在本篇之前的文章分析的task基本都是Task
的子类。 transform相关的任务均是Transform
的子类。那task和transform有什么关联呢?
先看下Transform:
public abstract class Transform {public abstract String getName();
public abstract Set getInputTypes();
public Set getOutputTypes() {
return getInputTypes();
}public abstract Set super Scope> getScopes();
public Set super Scope> getReferencedScopes() {
return ImmutableSet.of();
}public Collection getSecondaryFileInputs() {
return ImmutableList.of();
}public Collection getSecondaryFiles() {
return ImmutableList.of();
}public Collection getSecondaryFileOutputs() {
return ImmutableList.of();
}public Collection getSecondaryDirectoryOutputs() {
return ImmutableList.of();
}public Map getParameterInputs() {
return ImmutableMap.of();
}public abstract boolean isIncremental();
public void transform(
@NonNull Context context,
@NonNull Collection inputs,
@NonNull Collection referencedInputs,
@Nullable TransformOutputProvider outputProvider,
boolean isIncremental) throws IOException, TransformException, InterruptedException {
}public void transform(@NonNull TransformInvocation transformInvocation)
throws TransformException, InterruptedException, IOException {
// Just delegate to old method, for code that uses the old API.
//noinspection deprecation
transform(transformInvocation.getContext(), transformInvocation.getInputs(),
transformInvocation.getReferencedInputs(),
transformInvocation.getOutputProvider(),
transformInvocation.isIncremental());
}public boolean isCacheable() {
return false;
}
}
Transform实际就是一个抽象类,提供了一些抽象方法,仔细看会发现很多方法定义的和Task中的方法定义的很类似。
接下来我们下TransformManager.java
里面的addTransform()方法:
public Optional> addTransform(
@NonNull TaskFactory taskFactory,
@NonNull TransformVariantScope scope,
@NonNull T transform,
@Nullable TransformTask.ConfigActionCallback callback) {...transforms.add(transform);
// create the task...
AndroidTask task =
taskRegistry.create(
taskFactory,
new TransformTask.ConfigAction<>(
scope.getFullVariantName(),
taskName,
transform,
inputStreams,
referencedStreams,
outputStream,
recorder,
callback));
return Optional.ofNullable(task);
}
addTransform 方法在执行过程中,会将 Transform 包装成一个 AndroidTask 对象,所以transfrom最终会被转换成一个task。了解了transform,我们接下来继续分析这几个task。transformClassesWithDexBuilderForDebug
输入命令:./gradlew transformClassesWithDexBuilderForDebug
- inputs&outputs
input file:/Users/chao.zheng/.gradle/caches/transforms-1/files-1.1/appcompat-v7-26.1.0.aar/6b443e96f1af9aa241aaa70576c67a57/jars/classes.jar
input file:/Users/chao.zheng/.gradle/caches/transforms-1/files-1.1/constraint-layout-1.1.3.aar/f44da5c361a1f52801511229596f72e7/jars/classes.jar
input file:/Users/chao.zheng/.gradle/caches/transforms-1/files-1.1/butterknife-8.5.1.aar/9d5de52440cb778daab09db33955642f/jars/classes.jar
...
input file:/Users/chao.zheng/sunday/OpenSpace/TasksPro/app/build/intermediates/classes/debug/android/support/constraint/R$id.class
input file:/Users/chao.zheng/sunday/OpenSpace/TasksPro/app/build/intermediates/classes/debug/android/support/constraint/R$styleable.class
input file:/Users/chao.zheng/sunday/OpenSpace/TasksPro/app/build/intermediates/classes/debug/android/support/constraint/R$attr.class
input file:/Users/chao.zheng/sunday/OpenSpace/TasksPro/app/build/intermediates/classes/debug/android/arch/lifecycle/R.class
input file:/Users/chao.zheng/sunday/OpenSpace/TasksPro/app/build/intermediates/classes/debug/butterknife/R.class
---------------------------------------------------
output file:/Users/chao.zheng/sunday/OpenSpace/TasksPro/app/build/intermediates/transforms/dexBuilder/debug
输入文件比较多,中间省略了一部分,但是还是比较明显的看出输入文件分为两种类型:
1、依赖库的jar文件;
2、intermediates/classes/debug/
目下的class文件(即本项目产生的class文件)。
输出文件:
1、编号0-16的jar包;
2、本项目的class文件生成的dex文件。
- 源码
【Android|Android gradle打包涉及task源码解析(五)】https://android.googlesource.com/platform/tools/base/+/gradle_3.0.0/build-system/gradle-core/src/main/java/com/android/build/gradle/internal/transforms/DexArchiveBuilderTransform.java
- 主要代码逻辑
DexArchiveBuilderTransform.java类中的transform()方法:
public void transform(@NonNull TransformInvocation transformInvocation)
throws TransformException, IOException, InterruptedException {...try {
// 1、遍历输入
for (TransformInput input : transformInvocation.getInputs()) {
// 2、输入的类型是Directory,调用convertToDexArchive()方法。
for (DirectoryInput dirInput : input.getDirectoryInputs()) {
logger.verbose("Dir input %s", dirInput.getFile().toString());
convertToDexArchive(
transformInvocation.getContext(),
dirInput,
outputProvider,
transformInvocation.isIncremental());
}
// 3、输入类型是Jar,则调用processJarInput方法。
for (JarInput jarInput : input.getJarInputs()) {
logger.verbose("Jar input %s", jarInput.getFile().toString());
List dexArchives =
processJarInput(
transformInvocation.getContext(),
transformInvocation.isIncremental(),
jarInput,
outputProvider);
cacheableItems.putAll(jarInput, dexArchives);
}
}
...
}
通过代码分析,知道transform是遍历所有的输入文件,分为两种类型来处理:
1、输入的类型是Directory,调用convertToDexArchive()方法,接着调用了launchProcessing()方法,最终调用DxDexArchiveBuilder的dex()方法,代码如下:
// 1、通过方法应该就能知道,这个方法就是将class转变成dex的。
public void dex(String relativePath, ByteArray classBytes, DexArchive output)
throws IOException {// Copied from dx, from com.android.dx.command.dexer.Main
DirectClassFile cf = new DirectClassFile(classBytes, relativePath, true);
cf.setAttributeFactory(StdAttributeFactory.THE_ONE);
cf.getMagic();
// triggers the actual parsing// 2、DexFile obj
// starts the actual translation and writes the content to the dex file
// specified
DexFile dexFile = new DexFile(config.getDexOptions());
// Copied from dx, from com.android.dx.command.dexer.Main
ClassDefItem classDefItem =
CfTranslator.translate(
config.getDxContext(),
cf,
null,
config.getCfOptions(),
config.getDexOptions(),
dexFile);
dexFile.add(classDefItem);
if (outStorage != null) {
ByteArrayAnnotatedOutput byteArrayAnnotatedOutput = dexFile.writeTo(outStorage);
output.addFile(
ClassFileEntry.withDexExtension(relativePath),
byteArrayAnnotatedOutput.getArray(),
0,
byteArrayAnnotatedOutput.getCursor());
} else {
// 3、dexFile to dex
byte[] bytes = dexFile.toDex(null, false);
output.addFile(ClassFileEntry.withDexExtension(relativePath), bytes, 0, bytes.length);
}
}
通过方法的注视,可以看出此方法就是将输入文件生成dex文件。所以如果输入类型为目录的话,transform()方法会将该目录下的所有class文件转成dex文件。
现在再次回到transform()方法中来,另一个分支输入类型是Jar,则调用processJarInput方法,该方法代码如下:
private List processJarInput(
@NonNull Context context,
boolean isIncremental,
@NonNull JarInput jarInput,
TransformOutputProvider transformOutputProvider)
throws Exception {
// 1、非增量编译
if (!isIncremental) {
...
// 2、调用convertJarToDexArchive方法
return convertJarToDexArchive(context, jarInput, transformOutputProvider);
} else if (jarInput.getStatus() != Status.NOTCHANGED) {
// 3、增量编译处理逻辑
...
}
return ImmutableList.of();
}
通过注视1、2可知,在非增量编译的情况下,会调用convertJarToDexArchive()方法。(增量编译的逻辑再分析完所有的任务后,会单独写文章来分析gradle tool 3+版本如何实现增量编译的。)继续分析convertJarToDexArchive()方法,代码入下:
private List convertJarToDexArchive(
@NonNull Context context,
@NonNull JarInput toConvert,
@NonNull TransformOutputProvider transformOutputProvider)
throws Exception {
// 1、获取JarInput的缓存版本。
File cachedVersion = cacheHandler.getCachedVersionIfPresent(toConvert);
// 2、如果不存在缓存版本,则调用convertToDexArchive方法,将其转变成Dex文件,convertToDexArchive方法前面已经分析。
if (cachedVersion == null) {
return convertToDexArchive(context, toConvert, transformOutputProvider, false);
} else {
// 3、如果存在缓存版本,则直接copy缓存文件。
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();
}
}
通过注视1、2、3可以知道,此方法有两种方式得到Dex文件,
1、在没有缓存版本的时候,则调用convertToDexArchive方法,将其生成Dex文件。(实际平时在执行的时候,基本都是复用的缓存,如果有兴趣的朋友,可以先执行./gradlew cleanBuildCache
命令,清除build cache,看看执行输出,会发现此任务的task输出是不一样的)
2、在有缓存版本的时候,直接复用缓存版本。
现在我们的transform分析完了,但是大家有没有一个疑问,通过我们的分析实际上不管输入文件类型是Dir还是Jar,最终都是转变成了Dex文件,但是我们发现输入文件是Jar类型时,最后的输出是.jar结尾的Jar文件。大家可以把生成的jar文件后缀改成zip,然后解压看下,实际上里面的文件都是dex文件,这个jar只是dex文件的一个压缩集合。
所以通过以上分析可以很清楚的知道transformDexArchiveWithExternalLibsDexMergerForDebugtransformClassesWithDexBuilderForDebug
任务就是将项目依赖的Jar包,以及项目本身的Class文件全部transform为Dex文件。
执行命令:./gradlew transformDexArchiveWithExternalLibsDexMergerForDebug
- inputs&outputs
...
input file:/Users/chao.zheng/sunday/OpenSpace/TasksPro/app/build/intermediates/transforms/dexBuilder/debug/17/android/support/graphics/drawable/animated/R$attr.dex
input file:/Users/chao.zheng/sunday/OpenSpace/TasksPro/app/build/intermediates/transforms/dexBuilder/debug/17/android/arch/lifecycle/R.dex
input file:/Users/chao.zheng/sunday/OpenSpace/TasksPro/app/build/intermediates/transforms/dexBuilder/debug/17/butterknife/R.dex
input file:/Users/chao.zheng/sunday/OpenSpace/TasksPro/app/build/intermediates/transforms/dexBuilder/debug/5.jar
input file:/Users/chao.zheng/sunday/OpenSpace/TasksPro/app/build/intermediates/transforms/dexBuilder/debug/7.jar
input file:/Users/chao.zheng/sunday/OpenSpace/TasksPro/app/build/intermediates/transforms/dexBuilder/debug/6.jar
---------------------------------------------------
output file:/Users/chao.zheng/sunday/OpenSpace/TasksPro/app/build/intermediates/transforms/externalLibsDexMerger/debug
输入文件进行了部分删减,此任务的输入即transformClassesWithDexBuilderForDebug
任务的输出。
输出文件是一个classes.dex
文件,以及相应内容的json文件,json文件内容如下:
[{
"name": "main",
"index": 0,
"scopes": ["EXTERNAL_LIBRARIES"],
"types": ["DEX_ARCHIVE"],
"format": "DIRECTORY",
"present": true
}]
json文件的scopes
的value是EXTERNAL_LIBRARIES
,所以大胆猜测,这个classes.dex是所有依赖的jar的dex集合。
- 源码
https://android.googlesource.com/platform/tools/base/+/gradle_3.0.0/build-system/gradle-core/src/main/java/com/android/build/gradle/internal/transforms/ExternalLibsMergerTransform.kt
- 主要代码逻辑
ExternalLibsMergerTransform类是用kotlin实现,代码如下:
override fun transform(transformInvocation: TransformInvocation) {// we need to re-merge all jars except the removed ones.
val jarInputList = flattenInputs
.filter { it.status != Status.REMOVED }
.map {it.file.toPath()}
.toList()
...outputHandler.createOutput().use { processOutputHandler ->
val callable = callableFactory.create(dexingType,
processOutputHandler,
outputDir,
jarInputList,
null,
forkJoinPool,
dexMergerTool,
minSdkVersion,
isDebuggable)
// since we are merging into a single DEX_ARCHIVE (possibly containing 1 to many DEX
// merged DEX files, no need to use a separate thread.
callable.call()
}
}
代码很简单,jarInputList是dex jar的集合,最终调用了callable.call()方法,该方法代码如下:
public Void call() throws Exception {
DexArchiveMerger merger;
switch (dexMerger) {
case DX:
DxContext dxContext =
new DxContext(
processOutput.getStandardOutput(), processOutput.getErrorOutput());
merger = DexArchiveMerger.createDxDexMerger(dxContext, forkJoinPool);
break;
...
}merger.mergeDexArchives(dexArchives, dexOutputDir.toPath(), mainDexList, dexingType);
return null;
}
call方法中DexArchiveMerger 对象,然后调用mergeDexArchives()方法。所以我们前面的猜测是完全正确的,此task就是将前一个task生成的依赖库的dex jar 执行merge操作,生成一个classes.dex文件。transformDexArchiveWithDexMergerForDebug
命令输入:./gradlew transformDexArchiveWithDexMergerForDebug
- inputs&outputs
input file:/Users/chao.zheng/sunday/OpenSpace/TasksPro/app/build/intermediates/transforms/dexBuilder/debug/8.jar
input file:/Users/chao.zheng/sunday/OpenSpace/TasksPro/app/build/intermediates/transforms/dexBuilder/debug/9.jar
...
input file:/Users/chao.zheng/sunday/OpenSpace/TasksPro/app/build/intermediates/transforms/dexBuilder/debug/17/android/support/constraint/R$styleable.dex
input file:/Users/chao.zheng/sunday/OpenSpace/TasksPro/app/build/intermediates/transforms/dexBuilder/debug/17/android/arch/lifecycle/R.dex
input file:/Users/chao.zheng/sunday/OpenSpace/TasksPro/app/build/intermediates/transforms/dexBuilder/debug/17/butterknife/R.dex
input file:/Users/chao.zheng/sunday/OpenSpace/TasksPro/app/build/intermediates/transforms/dexBuilder/debug/5.jar
input file:/Users/chao.zheng/sunday/OpenSpace/TasksPro/app/build/intermediates/transforms/dexBuilder/debug/7.jar
input file:/Users/chao.zheng/sunday/OpenSpace/TasksPro/app/build/intermediates/transforms/dexBuilder/debug/6.jar
input file:/Users/chao.zheng/sunday/OpenSpace/TasksPro/app/build/intermediates/transforms/externalLibsDexMerger/debug/0/classes.dex
---------------------------------------------------
output file:/Users/chao.zheng/sunday/OpenSpace/TasksPro/app/build/intermediates/transforms/dexMerger/debug
输入文件进行了部分删减,此任务的输入是前面两个任务的输出。
输出文件是一个classes.dex
文件,以及相应内容的json文件,json文件内容如下:
[{
"name": "main",
"index": 0,
"scopes": ["PROJECT", "SUB_PROJECTS", "EXTERNAL_LIBRARIES"],
"types": ["DEX"],
"format": "DIRECTORY",
"present": true
}]
json文件的scopes
的value是PROJECT
、SUB_PROJECTS
和EXTERNAL_LIBRARIES
,所以大胆猜测,这个classes.dex是本项目、子模块和依赖的库的dex集合(也就是所有的dex集合)。
- 源码
https://android.googlesource.com/platform/tools/base/+/gradle_3.0.0/build-system/gradle-core/src/main/java/com/android/build/gradle/internal/transforms/DexMergerTransform.java
- 主要代码逻辑
public void transform(@NonNull TransformInvocation transformInvocation)
throws TransformException, IOException, InterruptedException {
...
mergeTasks = handleLegacyAndMonoDex(
transformInvocation.getInputs(), output, outputProvider);
// now wait for all merge tasks completion
mergeTasks.forEach(ForkJoinTask::join);
...
}
transform()方法里面调用了handleLegacyAndMonoDex(),该方法又调用了submitForMerging(),该方法代码如下:
private ForkJoinTask submitForMerging(
@NonNull ProcessOutput output,
@NonNull File dexOutputDir,
@NonNull Iterable dexArchives,
@Nullable Path mainDexList) {
DexMergerTransformCallable callable =
new DexMergerTransformCallable(
dexingType,
output,
dexOutputDir,
dexArchives,
mainDexList,
forkJoinPool,
dexMerger,
minSdkVersion,
isDebuggable);
return forkJoinPool.submit(callable);
}
submitForMerging()方法又调用了DexMergerTransformCallable 这个类,又回到了上个task所介绍的。
所以这个task正如我们前面分析的猜测的一样,将本项目、子模块和依赖的库的dex merge到一个classes.dex中。
推荐阅读
- android第三方框架(五)ButterKnife
- Android中的AES加密-下
- 带有Hilt的Android上的依赖注入
- Beego打包部署到Linux
- android|android studio中ndk的使用
- Android事件传递源码分析
- RxJava|RxJava 在Android项目中的使用(一)
- Android7.0|Android7.0 第三方应用无法访问私有库
- 深入理解|深入理解 Android 9.0 Crash 机制(二)
- android防止连续点击的简单实现(kotlin)