从Android Plugin源码开始完全理解gradle构建:初识AndroidDSL(一)

本篇文章已受权微信公众号 guolin_blog (郭霖)独家发布

1、写在前面

        本系列适合:对gradle基础有必定了解。

        因为gradle已经出来好久了,相关配置文章也比较多,因此就不从头开始说了,这里推荐几篇文章:html

        https://www.jianshu.com/p/8b8a550246bd
java

        刚哥(任玉刚)亲笔:gradle系列文章(这里我想催更!)android

2、为何要学gradle

        Android studio已经出来好久了,相信大部分公司都已经从eclipse转AS了,反正我都快忘记eclipse如何操做了。AS不少强大功能,其中很突出的一项就是gradle构建。还记得第一次用依赖的时候,那感受爽翻。可是由于build代码不熟悉,也遇到不少坑,常常会莫名其妙报错,当时只能上网查,而后一板一眼的配置。做为程序猿这种不能彻底掌握的感受是最不爽的,很早就想完全掌控它了。windows

        其次,做为有梦想的咸鱼,不能只作代码的搬运工,这种高阶必备的知识点仍是须要掌握的。好比国内比较火热的插件化、热更新都会涉及到gradle插件知识,因此想要进阶,必须掌握gradle。
缓存

3、自定义gradle插件

        经过学习上文推荐文章,咱们已经了解到,gradle就是构建工具,他使用的语言是groovy,咱们能够在build.gradle里面写代码来控制,固然,若是代码不少,但愿单独提取出来,那么可使用自定义gradle插件来实现,没错,咱们的主角:AndroidDSL(plugin)就是一个自定义插件而已,因此学习它以前须要了解如何自定义gradle插件。
微信


        首先,咱们新建一个项目,会获得两个build.gradle,一个是主项目的,一个是全局的。咱们先只看项目里的build文件,初始状态以下:
多线程

apply plugin: 'com.android.application'

android {
    compileSdkVersion 26
    defaultConfig {
        applicationId "com.xtu.neo.mylibrary"
        minSdkVersion 14
        targetSdkVersion 26
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"

    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }

}


dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])

    implementation 'com.android.support:appcompat-v7:26.1.0'
    implementation 'com.android.support.constraint:constraint-layout:1.0.2'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.1'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
}

其中自定义插件的重点:
apply plugin: 'com.android.application'

这就表示咱们引入了Android的插件了,下面来演示一下最简单的自定义插件步骤。并发

事实上全部的自定义插件都须要继承一个plugin类,而后重写apply方法,以下:app

apply plugin: com.atom.MyPlugin
eclipse

class MyPlugin implements Plugin<Project>{ 
    @Override 
    void apply(Project project) { 
        println "myPlugin invoked!" 
    }
}

把上述代码加到build.gradle下面,在命令行运行随意的命令:gradlew clean(windows)


调用成功了,固然这是最简单的方式,不过理解这里就能继续看AndroidDSL了

对于自定义插件的步骤我就偷偷懒了,直接给个连接吧

https://blog.csdn.net/huachao1001/article/details/51810328

4、Android Plugin源码解析

        对于如何查看源码,还得感谢刚哥星球的大牛们,其实很简单,只须要把全局build.gradle里的classpath的依赖加入项目build.gradle文件的dependencies里就行了,以下图:


这样就能在项目的依赖树里找到源码了,能够选择复制出来看,也能够直接在AS里看,我的感受AS也挺方便的


打开第一个,就能看见不少plugin展示在咱们眼前了,咱们最熟悉的就是AppPlugin和LibraryPlugin了

前者就是主项目须要依赖的插件,后者就是组件化的module须要依赖的插件

咱们拿最经常使用的AppPlugin来讲把,根据上面定义插件的步骤,咱们就直接看apply方法,因为Appplugin继承了basePlugin,因此又转到basePlugin:

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();
        //判断是否是新的API,这里咱们只看最新实现,老的就很少说了
        if (!projectOptions.get(BooleanOption.ENABLE_NEW_DSL_AND_API)) {
            TaskInputHelper.enableBypass();

            threadRecorder.record(
                    ExecutionType.BASE_PLUGIN_PROJECT_CONFIGURE,
                    project.getPath(),
                    null,
                    this::configureProject);

            threadRecorder.record(
                    ExecutionType.BASE_PLUGIN_PROJECT_BASE_EXTENSION_CREATION,
                    project.getPath(),
                    null,
                    this::configureExtension);

            threadRecorder.record(
                    ExecutionType.BASE_PLUGIN_PROJECT_TASKS_CREATION,
                    project.getPath(),
                    null,
                    this::createTasks);
        } else {
            //省略之前的实现
        }
    }

其实最重要的实如今于调用了三次threadRecorder.record,值得一说的是:this::configureProject这种写法

这是JAVA8里lambda语法,等于:()-> this.configureProject(),匿名内部类的简写方式,后面会回调这里。

J8已经出来好久了,相信你们有了必定的了解,这里就很少说。

 

咱们就来看看这个record方法:

@Override
    public void record(
            @NonNull ExecutionType executionType,
            @NonNull String projectPath,
            @Nullable String variant,
            @NonNull VoidBlock block) {
        //刚刚初始化过的单例
        ProfileRecordWriter profileRecordWriter = ProcessProfileWriter.get();
        //建立GradleBuildProfileSpan的建造者
        GradleBuildProfileSpan.Builder currentRecord =
                create(profileRecordWriter, executionType, null);
        try {
            //刚刚提到的回调
            block.call();
        } catch (IOException e) {
            throw new UncheckedIOException(e);
        } finally {
            //写入GradleBuildProfileSpan并保存
            write(profileRecordWriter, currentRecord, projectPath, variant);
        }
    }

以上代码作了以下事情:

一、建立GradleBuildProfileSpan.Builder

二、回调方法

三、写入GradleBuildProfileSpan并保存到spans中

咱们先无论回调,看一、3的代码,首先create:

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

代码很多,可是作的事情很简单,就是建立了一个GradleBuildProfileSpan.Builder,并设置了它的threadId、Id、parentId...等等一系列线程相关的东西,并保存在一个双向队列里,并放入threadLocal里解决多线程并发问题。这个threadLocal若不理解的能够移步个人另外一篇文章:消息机制:Handler源码解析

接下来是write

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,继续:

/** Append a span record to the build profile. Thread safe. */
    @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,并保存到了spans里。

关于一、3步骤说了这么多,其实也就是作了这点事情,接下来才是重点了,关于回调:

回头看basePlugin里的3个回调方法configureProject、configureExtension、

createTasks,方法里传的type已经暴露了他们的做用:

一、BASE_PLUGIN_PROJECT_CONFIGURE:plugin的基础设置、初始化工做

二、BASE_PLUGIN_PROJECT_BASE_EXTENSION_CREATION:EXTENSION的初始化工做

三、BASE_PLUGIN_PROJECT_TASKS_CREATION:plugin的task建立


这三步基本囊括了自定义插件的全部内容,因为篇幅缘由,我这里简单先介绍一下第一步,后面再详细解析很重要的后面两步

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);
    }

这个方法主要作了如下几件事情:

一、利用project,初始化了sdkHandler、androidBuilder、dataBindingBuilder等几个必备的对象。

二、依赖了JavaBasePlugin,这个很重要,咱们打包须要的“assemble”task就是在其中建立的。

三、对gradle建立作了监听,作了内存、磁盘缓存的工做,你能够在build\intermediates\dex-cache\cache.xml文件下找到JAR包等内容的缓存。


5、结语

本文简单介绍了自定义插件内容,以及如何简单查看Android plugin的源码(固然你也能够下载Android源码查看),并简单梳理了一下插件的执行流程,引出了extensions、task等gradle中较为重要的概念。

后面再一步一步梳理源码,同时介绍gradle部分重要概念,让咱们在学习源码的同时,更加深刻理解gradle的奥妙。