android gradle 源码,Android gradle打包涉及task源码解析(五)

本文详细剖析了transformClassesWithDexBuilderForDebug与transformDexArchiveWithExternalLibsDexMergerForDebug任务,揭示了它们如何将依赖库的jar和项目class文件转换为Dex文件,并在transformDexMerger中进行合并。
摘要由CSDN通过智能技术生成

文章序号

此篇文章将分析如下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文件。

源码

主要代码逻辑

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文件的一个压缩集合。

所以通过以上分析可以很清楚的知道transformClassesWithDexBuilderForDebug任务就是将项目依赖的Jar包,以及项目本身的Class文件全部transform为Dex文件。

transformDexArchiveWithExternalLibsDexMergerForDebug

执行命令:./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集合。

源码

主要代码逻辑

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集合)。

源码

主要代码逻辑

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中。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值