本篇文章已授权微信公众号 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的奥妙。

Categories: gradle

4 Comments

Ellambify · 2019年3月17日 at 上午9:41

Amoxicillin Canada Cialis Pas Cher A Nice vardenafil hcl 20mg tab Canadian Pharmacies Cialis Kamagra Vendita Spagna Amoxil 250 Mg

Elijahaccok · 2019年3月18日 at 下午11:31

We offer you the opportunity to advertise your products and services.
Good man! Please note a fine offering for you. I can help you with sending your commercial offers or messages through feedback forms. The advantage of this method is that the messages sent through the feedback forms are included in the white list. This method increases the chance that your message will be read. The same way you received this message.

Sending via Feedback Forms to any domain zones of the world. (more than 1000 domain zones.).
The cost of sending 1 million messages for any domain zone of the world is $ 49 instead of $ 99.
Domain zone .com – (12 million messages sent) – $399 instead of $699
All domain zones in Europe- (8 million messages sent) – $ 299 instead of $599
All sites in the world (25 million messages sent) – $499 instead of $999
Domain zone .de – (2 million messages sent) – $99 instead of $199
Domain zone .uk – (1.5 million messages sent) – $69 instead of $139
Domain zone .nl – (700 000 sent messages) – $39 instead of $79

In the process of sending messages, we do not violate the rules of GDRP.
Discounts are valid until March 25.

Contact us.
Telegram – @FeedbackFormEU
Skype – FeedbackForm2019
Email – FeedbackForm2019@gmail.com

Thanks for reading.

WilliamDuh · 2019年3月19日 at 上午2:05

Hello,

Download Club Music For DJs, House, Techno, Trance, Dance, Pop…
http://0daymusic.org

Best regards,
William

Kelgriesk · 2019年3月23日 at 上午11:14

Hydrochlorothiazide Hzt Low Price Viagra For Sale Canada Pharmacy How Much Does Viagra Cost At Costco 104 Prescription Free Viagra Online Pharmacy Montreal

发表评论

电子邮件地址不会被公开。