Android Plugin源碼與Gradle構(gòu)建(一)

一、前言

現(xiàn)在Android開發(fā)最常用的IDE就是Android Studio了念颈。在Android Studio中使用了Gradle構(gòu)建功能,這使得模塊之間的管理、依賴都非常的方便清晰梢为。

同時(shí),國內(nèi)比較火熱的Android插件化、熱更新等都涉及到了Gradle插件的知識铸董,熟練的掌握Gradle祟印,可以讓我們更加清楚的了解Android的構(gòu)建過程,改造構(gòu)建過程以達(dá)到某些功能需求粟害。從這個(gè)角度來說蕴忆,Android開發(fā)是需要掌握Gradle這項(xiàng)技能的。

二悲幅、Android Plugin源碼獲取

在Android項(xiàng)目的build.gradle中套鹅,有這樣的一行代碼:

apply plugin: 'com.android.application'

想必大家對這行代碼都相當(dāng)?shù)氖煜ぐ伞K磉_(dá)的意思是將名為com.android.application的插件運(yùn)用到我們的項(xiàng)目中汰具,這個(gè)插件就是大名鼎鼎的Android Plugin卓鹿。

那么Android Plugin是怎么進(jìn)入到我們的工程中的呢?


在項(xiàng)目的根目錄下的build.gradle中找到 classpath 對gradle插件的引用留荔,如上圖减牺。Android Plugin就包含在上述的 gradle 插件中。

我們將上面的gradle插件復(fù)制到項(xiàng)目的build.gradle中(注意:這里是項(xiàng)目的build.gradle存谎,和上面的build.gradle不一樣0尉巍)。

同步(sync)一下項(xiàng)目既荚,就可以在項(xiàng)目的依賴樹中找到Android Plugin源碼稚失。

三、Android Plugin源碼解析

展開上面的com.android.tools.build:gradle:3.1.0@jar恰聘,可以看到AppPlugin和LibraryPlugin句各,其中AppPlugin就是Android項(xiàng)目需要依賴的插件,而LibraryPlugin是組件項(xiàng)目需要依賴的插件晴叨。

下面我們就解析一下AppPlugin的源碼凿宾,并從中了解App構(gòu)建過程。Let's do it~

首先AppPlugin繼承了BasePlugin兼蕊,BasePlugin實(shí)現(xiàn)了Plugin接口初厚,并實(shí)現(xiàn)了其中的apply方法。apply方法作為BasePlugin的入口類孙技,其實(shí)現(xiàn)如下:

@Override
public void apply(@NonNull Project project) {
    //初始化項(xiàng)产禾,省略

    //構(gòu)造線程記錄器,用于記錄執(zhí)行時(shí)間
    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 配置項(xiàng)目
        threadRecorder.record(
                ExecutionType.BASE_PLUGIN_PROJECT_CONFIGURE,
                project.getPath(),
                null,
                this::configureProject);
        // configureExtension 配置 Extension牵啦,之后我們才能使用 android {} 進(jìn)行配置
        threadRecorder.record(
                ExecutionType.BASE_PLUGIN_PROJECT_BASE_EXTENSION_CREATION,
                project.getPath(),
                null,
                this::configureExtension);
        // createTasks 創(chuàng)建必須的 task
        threadRecorder.record(
                ExecutionType.BASE_PLUGIN_PROJECT_TASKS_CREATION,
                project.getPath(),
                null,
                this::createTasks);
    } else {
        //省略
    }
}

上面的apply方法最重要的是調(diào)用了三次ThreadRecorder的record方法亚情。注意:this::configureProject是java8 的lambda寫法,所以接下來我們看一下record方法的實(shí)現(xiàn):

@Override
public void record(
        @NonNull ExecutionType executionType,
        @NonNull String projectPath,
        @Nullable String variant,
        @NonNull VoidBlock block) {
    ProfileRecordWriter profileRecordWriter = ProcessProfileWriter.get();
    //創(chuàng)建GradleBuildProfileSpan.Builder對象
    GradleBuildProfileSpan.Builder currentRecord =
            create(profileRecordWriter, executionType, null);
    try {
        //回調(diào)方法
        block.call();
    } catch (IOException e) {
        throw new UncheckedIOException(e);
    } finally {
        //將上面創(chuàng)建的GradleBuildProfileSpan對象寫入到ProfileRecordWriter對象的span(隊(duì)列)變量中
        write(profileRecordWriter, currentRecord, projectPath, variant);
    }
}

record方法主要完成了三個(gè)邏輯:
1哈雏、創(chuàng)建了創(chuàng)建GradleBuildProfileSpan.Builder對象楞件;
2衫生、執(zhí)行回調(diào)方法;
3土浸、最后將GradleBuildProfileSpan對象寫入到ProfileRecordWriter對象的span(隊(duì)列)變量中罪针。

首先我們看1的邏輯,調(diào)用了create方法創(chuàng)建了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方法的邏輯就是創(chuàng)建了一個(gè)GradleBuildProfileSpan.Builder對象栅迄,并且將一些線程相關(guān)的變量設(shè)置進(jìn)去,并將thisRecordId保存到一個(gè)雙向隊(duì)列中皆怕。

然后再看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);
}

其中執(zhí)行了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());
}

最后運(yùn)用建造者模式生成GradleBuildProfileSpan對象寫入到ProfileRecordWriter對象的span(隊(duì)列)變量中毅舆。

最后我們看2的邏輯,即回調(diào)方法的執(zhí)行愈腾。還記得上面提到過的BasePlugin的apply方法執(zhí)行的三個(gè)record方法嗎憋活?其中每一個(gè)record方法都有自己的回調(diào)方法,即configureProject虱黄、configureExtension悦即、createTasks。

其實(shí)從這三個(gè)方法傳進(jìn)來的第一個(gè)參數(shù)橱乱,我們能大概看出每一個(gè)方法實(shí)現(xiàn)的邏輯:
1辜梳、BASE_PLUGIN_PROJECT_CONFIGURE:插件的基本配置信息、初始化等泳叠。
2作瞄、BASE_PLUGIN_PROJECT_BASE_EXTENSION_CREATION:插件Extension的初始化。
3危纫、BASE_PLUGIN_PROJECT_TASKS_CREATION:插件任務(wù)的創(chuàng)建宗挥。

我們先來看第一個(gè)第一個(gè)方法的實(shí)現(xiàn)邏輯,后面兩個(gè)方法放到后面的兩篇文章來講种蝶。

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這個(gè)方法代碼有點(diǎn)長契耿,但是其主要執(zhí)行了以下幾個(gè)邏輯:
1、初始化了SdkHandler螃征、AndroidBuilder和DataBindingBuilder對象搪桂。
2、依賴了JavaBasePlugin插件盯滚,在其中創(chuàng)建了很多關(guān)于構(gòu)建的task锅棕,其中build、assemble等task就是在里面創(chuàng)建的淌山。
3裸燎、對項(xiàng)目的創(chuàng)建做了監(jiān)聽,在構(gòu)建結(jié)束后執(zhí)行了磁盤緩存等操作泼疑。

四德绿、總結(jié)

本文主要介紹了如何查看Android Plugin插件源碼,以及對Android Plugin插件的執(zhí)行流程進(jìn)行了簡單的分析,后面會繼續(xù)接著分析插件Extension和插件任務(wù)的創(chuàng)建這兩個(gè)流程移稳。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蕴纳,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子个粱,更是在濱河造成了極大的恐慌古毛,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,110評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件都许,死亡現(xiàn)場離奇詭異稻薇,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)胶征,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,443評論 3 395
  • 文/潘曉璐 我一進(jìn)店門塞椎,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人睛低,你說我怎么就攤上這事案狠。” “怎么了钱雷?”我有些...
    開封第一講書人閱讀 165,474評論 0 356
  • 文/不壞的土叔 我叫張陵骂铁,是天一觀的道長。 經(jīng)常有香客問我罩抗,道長从铲,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,881評論 1 295
  • 正文 為了忘掉前任澄暮,我火速辦了婚禮名段,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘泣懊。我一直安慰自己伸辟,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,902評論 6 392
  • 文/花漫 我一把揭開白布馍刮。 她就那樣靜靜地躺著信夫,像睡著了一般。 火紅的嫁衣襯著肌膚如雪卡啰。 梳的紋絲不亂的頭發(fā)上静稻,一...
    開封第一講書人閱讀 51,698評論 1 305
  • 那天,我揣著相機(jī)與錄音匈辱,去河邊找鬼振湾。 笑死,一個(gè)胖子當(dāng)著我的面吹牛亡脸,可吹牛的內(nèi)容都是我干的押搪。 我是一名探鬼主播树酪,決...
    沈念sama閱讀 40,418評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼大州!你這毒婦竟也來了续语?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,332評論 0 276
  • 序言:老撾萬榮一對情侶失蹤厦画,失蹤者是張志新(化名)和其女友劉穎疮茄,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體根暑,經(jīng)...
    沈念sama閱讀 45,796評論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡力试,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,968評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了购裙。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片懂版。...
    茶點(diǎn)故事閱讀 40,110評論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡鹃栽,死狀恐怖躏率,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情民鼓,我是刑警寧澤薇芝,帶...
    沈念sama閱讀 35,792評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站丰嘉,受9級特大地震影響夯到,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜饮亏,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,455評論 3 331
  • 文/蒙蒙 一耍贾、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧路幸,春花似錦荐开、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,003評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至砰识,卻和暖如春能扒,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背辫狼。 一陣腳步聲響...
    開封第一講書人閱讀 33,130評論 1 272
  • 我被黑心中介騙來泰國打工初斑, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人膨处。 一個(gè)月前我還...
    沈念sama閱讀 48,348評論 3 373
  • 正文 我出身青樓越平,卻偏偏與公主長得像频蛔,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子秦叛,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,047評論 2 355