Android 探索之 Task 分析(三)

看到这里才是真正的开始分析,let's go

本文将会分析如下几个task

preBuild
extractProguardFiles
preReleaseBuild
compileReleaseAidl
compileReleaseRenderscript
checkReleaseManifest

1.preBuild

啥都没有做,它就是一个空的task,做锚点用的,这就是整个打包任务的起点,我们可以hook prebuild做一些特殊的事情,比如build之前下载插件等。

源码分析

我们来找到TaskManagercreateDefaultPreBuildTask方法到底做了什么,

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


从源码也可以看出来,输入为 compileruntime 的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

这年头生活不易,人生不息,折腾不止!!!!

发表评论