Android Plugin源碼與Gradle構建(一)

1、前言

如今Android開發最經常使用的IDE就是Android Studio了。在Android Studio中使用了Gradle構建功能,這使得模塊之間的管理、依賴都很是的方便清晰。java

同時,國內比較火熱的Android插件化、熱更新等都涉及到了Gradle插件的知識,熟練的掌握Gradle,可讓咱們更加清楚的瞭解Android的構建過程,改造構建過程以達到某些功能需求。從這個角度來講,Android開發是須要掌握Gradle這項技能的。android

2、Android Plugin源碼獲取

在Android項目的build.gradle中,有這樣的一行代碼:緩存

apply plugin: 'com.android.application'
複製代碼

想必你們對這行代碼都至關的熟悉吧。它要表達的意思是將名爲com.android.application的插件運用到咱們的項目中,這個插件就是大名鼎鼎的Android Pluginbash

那麼Android Plugin是怎麼進入到咱們的工程中的呢? app

在項目的 根目錄下的build.gradle中找到 classpath 對gradle插件的引用,如上圖。Android Plugin就包含在上述的 gradle 插件中。

咱們將上面的gradle插件複製到項目的build.gradle中(注意:這裏是項目的build.gradle,和上面的build.gradle不同!)。 ide

同步(sync)一下項目,就能夠在項目的依賴樹中找到Android Plugin源碼。 gradle

3、Android Plugin源碼解析

展開上面的com.android.tools.build:gradle:3.1.0@jar,能夠看到AppPlugin和LibraryPlugin,其中AppPlugin就是Android項目須要依賴的插件,而LibraryPlugin是組件項目須要依賴的插件ui

下面咱們就解析一下AppPlugin的源碼,並從中瞭解App構建過程。Let's do it~this

首先AppPlugin繼承了BasePlugin,BasePlugin實現了Plugin接口,並實現了其中的apply方法。apply方法做爲BasePlugin的入口類,其實現以下:lua

@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方法主要完成了三個邏輯: 一、建立了建立GradleBuildProfileSpan.Builder對象; 二、執行回調方法; 三、最後將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。

其實從這三個方法傳進來的第一個參數,咱們能大概看出每個方法實現的邏輯: 一、BASE_PLUGIN_PROJECT_CONFIGURE:插件的基本配置信息、初始化等。 二、BASE_PLUGIN_PROJECT_BASE_EXTENSION_CREATION:插件Extension的初始化。 三、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這個方法代碼有點長,可是其主要執行了如下幾個邏輯: 一、初始化了SdkHandler、AndroidBuilder和DataBindingBuilder對象。 二、依賴了JavaBasePlugin插件,在其中建立了不少關於構建的task,其中build、assemble等task就是在裏面建立的。 三、對項目的建立作了監聽,在構建結束後執行了磁盤緩存等操做。

4、總結

本文主要介紹瞭如何查看Android Plugin插件源碼,以及對Android Plugin插件的執行流程進行了簡單的分析,後面會繼續接着分析插件Extension和插件任務的建立這兩個流程。

相關文章
相關標籤/搜索