美文网首页
Android gradle打包涉及task源码解析(五)

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

作者: 枫吹一痕 | 来源:发表于2018-12-12 21:29 被阅读0次

    文章序号

    此篇文章将分析如下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<ContentType> getInputTypes();
    
        public Set<ContentType> getOutputTypes() {
            return getInputTypes();
        }
    
        public abstract Set<? super Scope> getScopes();
    
        public Set<? super Scope> getReferencedScopes() {
            return ImmutableSet.of();
        }
    
        public Collection<File> getSecondaryFileInputs() {
            return ImmutableList.of();
        }
    
        public Collection<SecondaryFile> getSecondaryFiles() {
            return ImmutableList.of();
        }
    
        public Collection<File> getSecondaryFileOutputs() {
            return ImmutableList.of();
        }
    
        public Collection<File> getSecondaryDirectoryOutputs() {
            return ImmutableList.of();
        }
    
        public Map<String, Object> getParameterInputs() {
            return ImmutableMap.of();
        }
    
        public abstract boolean isIncremental();
    
        public void transform(
                @NonNull Context context,
                @NonNull Collection<TransformInput> inputs,
                @NonNull Collection<TransformInput> 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 <T extends Transform> Optional<AndroidTask<TransformTask>> addTransform(
                @NonNull TaskFactory taskFactory,
                @NonNull TransformVariantScope scope,
                @NonNull T transform,
                @Nullable TransformTask.ConfigActionCallback<T> callback) {
            
            ...
    
            transforms.add(transform);
    
            // create the task...
            AndroidTask<TransformTask> 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文件。

    • 源码

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

    • 源码

    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是PROJECTSUB_PROJECTSEXTERNAL_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<Void> submitForMerging(
                @NonNull ProcessOutput output,
                @NonNull File dexOutputDir,
                @NonNull Iterable<Path> 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 gradle打包涉及task源码解析(五)

          本文链接:https://www.haomeiwen.com/subject/idcmhqtx.html