Android Plugin源码与Gradle构建(一)

一、前言

现在Android开发最常用的IDE就是Android Studio了。在Android Studio中使用了Gradle构建功能,这使得模块之间的管理、依赖都非常的方便清晰。

同时,国内比较火热的Android插件化、热更新等都涉及到了Gradle插件的知识,熟练的掌握Gradle,可以让我们更加清楚的了解Android的构建过程,改造构建过程以达到某些功能需求。从这个角度来说,Android开发是需要掌握Gradle这项技能的。

二、Android Plugin源码获取

在Android项目的build.gradle中,有这样的一行代码:

apply plugin: 'com.android.application'

想必大家对这行代码都相当的熟悉吧。它要表达的意思是将名为com.android.application的插件运用到我们的项目中,这个插件就是大名鼎鼎的Android Plugin

那么Android Plugin是怎么进入到我们的工程中的呢?


在项目的根目录下的build.gradle中找到 classpath 对gradle插件的引用,如上图。Android Plugin就包含在上述的 gradle 插件中。

我们将上面的gradle插件复制到项目的build.gradle中(注意:这里是项目的build.gradle,和上面的build.gradle不一样!)。

同步(sync)一下项目,就可以在项目的依赖树中找到Android Plugin源码。

三、Android Plugin源码解析

展开上面的com.android.tools.build:gradle:3.1.0@jar,可以看到AppPlugin和LibraryPlugin,其中AppPlugin就是Android项目需要依赖的插件,而LibraryPlugin是组件项目需要依赖的插件

下面我们就解析一下AppPlugin的源码,并从中了解App构建过程。Let's do it~

首先AppPlugin继承了BasePlugin,BasePlugin实现了Plugin接口,并实现了其中的apply方法。apply方法作为BasePlugin的入口类,其实现如下:

@Override
public void apply(@NonNull Project project) {
    //初始化项,省略

    //构造线程记录器,用于记录执行时间
    threadRecorder = ThreadRecorder.get();

    ProcessProfileWriter.getProject(project.getPath())
            .setAndroidPluginVersion(Version.ANDROID_GRADLE_PLUGIN_VERSION)
            .setAndroidPlugin(getAnalyticsPluginType())
            .setPluginGeneration(GradleBuildProject.PluginGeneration.FIRST)
            .setOptions(AnalyticsUtil.toProto(projectOptions));

    BuildableArtifactImpl.Companion.disableResolution();
    if (!projectOptions.get(BooleanOption.ENABLE_NEW_DSL_AND_API)) {
        //不使用新的DSL API
        TaskInputHelper.enableBypass();
        // configureProject 配置项目
        threadRecorder.record(
                ExecutionType.BASE_PLUGIN_PROJECT_CONFIGURE,
                project.getPath(),
                null,
                this::configureProject);
        // configureExtension 配置 Extension,之后我们才能使用 android {} 进行配置
        threadRecorder.record(
                ExecutionType.BASE_PLUGIN_PROJECT_BASE_EXTENSION_CREATION,
                project.getPath(),
                null,
                this::configureExtension);
        // createTasks 创建必须的 task
        threadRecorder.record(
                ExecutionType.BASE_PLUGIN_PROJECT_TASKS_CREATION,
                project.getPath(),
                null,
                this::createTasks);
    } else {
        //省略
    }
}

上面的apply方法最重要的是调用了三次ThreadRecorder的record方法。注意:this::configureProject是java8 的lambda写法,所以接下来我们看一下record方法的实现:

@Override
public void record(
        @NonNull ExecutionType executionType,
        @NonNull String projectPath,
        @Nullable String variant,
        @NonNull VoidBlock block) {
    ProfileRecordWriter profileRecordWriter = ProcessProfileWriter.get();
    //创建GradleBuildProfileSpan.Builder对象
    GradleBuildProfileSpan.Builder currentRecord =
            create(profileRecordWriter, executionType, null);
    try {
        //回调方法
        block.call();
    } catch (IOException e) {
        throw new UncheckedIOException(e);
    } finally {
        //将上面创建的GradleBuildProfileSpan对象写入到ProfileRecordWriter对象的span(队列)变量中
        write(profileRecordWriter, currentRecord, projectPath, variant);
    }
}

record方法主要完成了三个逻辑:
1、创建了创建GradleBuildProfileSpan.Builder对象;
2、执行回调方法;
3、最后将GradleBuildProfileSpan对象写入到ProfileRecordWriter对象的span(队列)变量中。

首先我们看1的逻辑,调用了create方法创建了GradleBuildProfileSpan.Builder对象:

private GradleBuildProfileSpan.Builder create(
        @NonNull ProfileRecordWriter profileRecordWriter,
        @NonNull ExecutionType executionType,
        @Nullable GradleTransformExecution transform) {
    long thisRecordId = profileRecordWriter.allocateRecordId();

    // am I a child ?
    @Nullable
    Long parentId = recordStacks.get().peek();

    long startTimeInMs = System.currentTimeMillis();

    final GradleBuildProfileSpan.Builder currentRecord =
            GradleBuildProfileSpan.newBuilder()
                    .setId(thisRecordId)
                    .setType(executionType)
                    .setStartTimeInMs(startTimeInMs);

    if (transform != null) {
        currentRecord.setTransform(transform);
    }

    if (parentId != null) {
        currentRecord.setParentId(parentId);
    }

    currentRecord.setThreadId(threadId.get());
    recordStacks.get().push(thisRecordId);
    return currentRecord;
}

create方法的逻辑就是创建了一个GradleBuildProfileSpan.Builder对象,并且将一些线程相关的变量设置进去,并将thisRecordId保存到一个双向队列中。

然后再看3的逻辑:

private void write(
        @NonNull ProfileRecordWriter profileRecordWriter,
        @NonNull GradleBuildProfileSpan.Builder currentRecord,
        @NonNull String projectPath,
        @Nullable String variant) {
    // pop this record from the stack.
    if (recordStacks.get().pop() != currentRecord.getId()) {
        Logger.getLogger(ThreadRecorder.class.getName())
                .log(Level.SEVERE, "Profiler stack corrupted");
    }
    currentRecord.setDurationInMs(
            System.currentTimeMillis() - currentRecord.getStartTimeInMs());
    profileRecordWriter.writeRecord(projectPath, variant, currentRecord);
}

其中执行了ProfileRecordWriter对象的writeRecord方法:

@Override
public void writeRecord(
        @NonNull String project,
        @Nullable String variant,
        @NonNull final GradleBuildProfileSpan.Builder executionRecord) {

    executionRecord.setProject(mNameAnonymizer.anonymizeProjectPath(project));
    executionRecord.setVariant(mNameAnonymizer.anonymizeVariant(project, variant));
    spans.add(executionRecord.build());
}

最后运用建造者模式生成GradleBuildProfileSpan对象写入到ProfileRecordWriter对象的span(队列)变量中。

最后我们看2的逻辑,即回调方法的执行。还记得上面提到过的BasePlugin的apply方法执行的三个record方法吗?其中每一个record方法都有自己的回调方法,即configureProject、configureExtension、createTasks。

其实从这三个方法传进来的第一个参数,我们能大概看出每一个方法实现的逻辑:
1、BASE_PLUGIN_PROJECT_CONFIGURE:插件的基本配置信息、初始化等。
2、BASE_PLUGIN_PROJECT_BASE_EXTENSION_CREATION:插件Extension的初始化。
3、BASE_PLUGIN_PROJECT_TASKS_CREATION:插件任务的创建。

我们先来看第一个第一个方法的实现逻辑,后面两个方法放到后面的两篇文章来讲。

private void configureProject() {
    final Gradle gradle = project.getGradle();

    extraModelInfo = new ExtraModelInfo(project.getPath(), projectOptions, project.getLogger());
    checkGradleVersion(project, getLogger(), projectOptions);

    sdkHandler = new SdkHandler(project, getLogger());
    if (!gradle.getStartParameter().isOffline()
            && projectOptions.get(BooleanOption.ENABLE_SDK_DOWNLOAD)) {
        SdkLibData sdkLibData = SdkLibData.download(getDownloader(), getSettingsController());
        sdkHandler.setSdkLibData(sdkLibData);
    }

    androidBuilder =
            new AndroidBuilder(
                    project == project.getRootProject() ? project.getName() : project.getPath(),
                    creator,
                    new GradleProcessExecutor(project),
                    new GradleJavaProcessExecutor(project),
                    extraModelInfo.getSyncIssueHandler(),
                    extraModelInfo.getMessageReceiver(),
                    getLogger(),
                    isVerbose());
    dataBindingBuilder = new DataBindingBuilder();
    dataBindingBuilder.setPrintMachineReadableOutput(
            SyncOptions.getErrorFormatMode(projectOptions) == ErrorFormatMode.MACHINE_PARSABLE);

    if (projectOptions.hasRemovedOptions()) {
        androidBuilder
                .getIssueReporter()
                .reportWarning(Type.GENERIC, projectOptions.getRemovedOptionsErrorMessage());
    }

    if (projectOptions.hasDeprecatedOptions()) {
        extraModelInfo
                .getDeprecationReporter()
                .reportDeprecatedOptions(projectOptions.getDeprecatedOptions());
    }

    // Apply the Java plugin
    project.getPlugins().apply(JavaBasePlugin.class);

    project.getTasks()
            .getByName("assemble")
            .setDescription(
                    "Assembles all variants of all applications and secondary packages.");

    // call back on execution. This is called after the whole build is done (not
    // after the current project is done).
    // This is will be called for each (android) projects though, so this should support
    // being called 2+ times.
    gradle.addBuildListener(
            new BuildListener() {
                @Override
                public void buildStarted(@NonNull Gradle gradle) {
                    TaskInputHelper.enableBypass();
                    BuildableArtifactImpl.Companion.disableResolution();
                }

                @Override
                public void settingsEvaluated(@NonNull Settings settings) {}

                @Override
                public void projectsLoaded(@NonNull Gradle gradle) {}

                @Override
                public void projectsEvaluated(@NonNull Gradle gradle) {}

                @Override
                public void buildFinished(@NonNull BuildResult buildResult) {
                    // Do not run buildFinished for included project in composite build.
                    if (buildResult.getGradle().getParent() != null) {
                        return;
                    }
                    sdkHandler.unload();
                    threadRecorder.record(
                            ExecutionType.BASE_PLUGIN_BUILD_FINISHED,
                            project.getPath(),
                            null,
                            () -> {
                                WorkerActionServiceRegistry.INSTANCE
                                        .shutdownAllRegisteredServices(
                                                ForkJoinPool.commonPool());
                                PreDexCache.getCache()
                                        .clear(
                                                FileUtils.join(
                                                        project.getRootProject().getBuildDir(),
                                                        FD_INTERMEDIATES,
                                                        "dex-cache",
                                                        "cache.xml"),
                                                getLogger());
                                Main.clearInternTables();
                            });
                }
            });

    gradle.getTaskGraph()
            .addTaskExecutionGraphListener(
                    taskGraph -> {
                        TaskInputHelper.disableBypass();
                        Aapt2DaemonManagerService.registerAaptService(
                                Objects.requireNonNull(androidBuilder.getTargetInfo())
                                        .getBuildTools(),
                                loggerWrapper,
                                WorkerActionServiceRegistry.INSTANCE);

                        for (Task task : taskGraph.getAllTasks()) {
                            if (task instanceof TransformTask) {
                                Transform transform = ((TransformTask) task).getTransform();
                                if (transform instanceof DexTransform) {
                                    PreDexCache.getCache()
                                            .load(
                                                    FileUtils.join(
                                                            project.getRootProject()
                                                                    .getBuildDir(),
                                                            FD_INTERMEDIATES,
                                                            "dex-cache",
                                                            "cache.xml"));
                                    break;
                                }
                            }
                        }
                    });

    createLintClasspathConfiguration(project);
}

configureProject这个方法代码有点长,但是其主要执行了以下几个逻辑:
1、初始化了SdkHandler、AndroidBuilder和DataBindingBuilder对象。
2、依赖了JavaBasePlugin插件,在其中创建了很多关于构建的task,其中build、assemble等task就是在里面创建的。
3、对项目的创建做了监听,在构建结束后执行了磁盘缓存等操作。

四、总结

本文主要介绍了如何查看Android Plugin插件源码,以及对Android Plugin插件的执行流程进行了简单的分析,后面会继续接着分析插件Extension和插件任务的创建这两个流程。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 201,681评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,710评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,623评论 0 334
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,202评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,232评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,368评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,795评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,461评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,647评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,476评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,525评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,226评论 3 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,785评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,857评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,090评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,647评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,215评论 2 341