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

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

一、写在前面

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

        由于gradle已经出来很久了,相关配置文章也比较多,所以就不从头开始说了,这里推荐几篇文章:

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

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

二、为什么要学gradle

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

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

三、自定义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方法,如下:

apply plugin: com.atom.MyPlugin

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

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

以上代码做了如下事情:

1、创建GradleBuildProfileSpan.Builder

2、回调方法

3、写入GradleBuildProfileSpan并保存到spans中

我们先不管回调,看1、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里。

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

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

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

1、BASE_PLUGIN_PROJECT_CONFIGURE:plugin的基础设置、初始化工作

2、BASE_PLUGIN_PROJECT_BASE_EXTENSION_CREATION:EXTENSION的初始化工作

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

这个方法主要做了以下几件事情:

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

2、依赖了JavaBasePlugin,这个很重要,我们打包需要的“assemble”task就是在其中创建的。

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


五、结语

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

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





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值