Android Plugin源码与Gradle构建(二)

一、前言

上一篇文章中我们讲到了Android Plugin中的apply方法中最为重要的三个回调方法,分别是configureProject、configureExtension、createTasks。而且还分析了configureProject回调方法中的逻辑,详见Android Plugin源码与Gradle构建(一)。今天我们就接着上面的思路,继续分析Android Plugin的源码。

二、初识Extension

我们先来看一段熟悉的build.gradle脚本:

android {
    compileSdkVersion 27
    defaultConfig {
        applicationId "com.example.runningh.mydemo"
        minSdkVersion 16
        targetSdkVersion 27
        versionCode 1
        versionName "1.0"
    }

    signingConfigs {
        release {
            storeFile file(release_storeFile)
            storePassword release_storePassword
            keyAlias release_keyAlias
            keyPassword release_keyPassword
        }
    }

    dataBinding {
        enabled true
    }

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

相信大家对上述的build.gradle脚本应该很熟悉了。它是使用groovy语言编写的,上面这段脚本配置了项目的minSdkVersion(项目最低可运行在哪个版本的SDK手机上)、targetSdkVersion(项目已经在哪个版本的SDK上做了优化)、versionCode(版本号)、versionName(版本名)、signingConfigs(签名)、buildTypes(构建配置)等信息。
然而,为什么要这样写呢?我们自定义一个插件(关于自定义插件,可以参考这篇文章在AndroidStudio中自定义Gradle插件)叫做MyPlugin:

class MyPlugin implements Plugin<Project>{   
    @Override   
    void apply(Project project) {        
         project.extensions.add("book", Book)
         project.task('bookInfo') {
            group 'myplugin'
            doLast {
             Book ext = project.book
             println ext 
          }
       }
  }  
}  

apply方法中的第一行调用了project.extensionsadd方法将Book这个类加入到projectextension中,通过“book"(记住这个名字,后面需要在build.gradle中用到)这个名字可以引用到Book类。projectextension可以看做是一个容器。
apply方法的第二行定义了一个名为“bookInfo”的task,并将其放入到了名为“myplugin”的group中,然后该任务最后执行了将该Book类信息打印出来的逻辑。
我们看一下Book类是怎么定义的:

public class Book {
 
    String bookName;
 
    int price;
 
    @Override
    public String toString() {
        return "BookName is $bookName, price is $price."
    }
}

很简单,定义了两个变量,分别为bookName、price,并重写了toString方法,所以上面的MyPlugin插件直接调用println方法可直接执行BooktoString方法。
最后我们需要项目的build.gradle中配置book(还记得这个名字吗?我们通过project.extensionadd方法将该名字作为其中的一个参数):

book {
  bookName 'Android开发’
  price 100
}

看到这里,是不是和上面的android标签相类似呢?其实这种配置方法相当于给book做了初始化,相当于我们可以动态配置Book类的变量。最后我们执行一下bookInfo任务,执行结果如下:

BookName is Android开发, price is 100.

三、Android插件的Extension

private void configureExtension() {
    ObjectFactory objectFactory = project.getObjects();
    final NamedDomainObjectContainer<BuildType> buildTypeContainer =
            project.container(
                    BuildType.class,
                    new BuildTypeFactory(
                            objectFactory,
                            project,
                            extraModelInfo.getSyncIssueHandler(),
                            extraModelInfo.getDeprecationReporter()));
    final NamedDomainObjectContainer<ProductFlavor> productFlavorContainer =
            project.container(
                    ProductFlavor.class,
                    new ProductFlavorFactory(
                            objectFactory,
                            project,
                            extraModelInfo.getDeprecationReporter(),
                            project.getLogger()));
    final NamedDomainObjectContainer<SigningConfig> signingConfigContainer =
            project.container(
                    SigningConfig.class,
                    new SigningConfigFactory(
                            objectFactory,
                            GradleKeystoreHelper.getDefaultDebugKeystoreLocation()));

    final NamedDomainObjectContainer<BaseVariantOutput> buildOutputs =
            project.container(BaseVariantOutput.class);

    project.getExtensions().add("buildOutputs", buildOutputs);

    sourceSetManager = createSourceSetManager();

    extension =
            createExtension(
                    project,
                    projectOptions,
                    androidBuilder,
                    sdkHandler,
                    buildTypeContainer,
                    productFlavorContainer,
                    signingConfigContainer,
                    buildOutputs,
                    sourceSetManager,
                    extraModelInfo);

    ndkHandler =
            new NdkHandler(
                    project.getRootDir(),
                    null, /* compileSkdVersion, this will be set in afterEvaluate */
                    "gcc",
                    "" /*toolchainVersion*/,
                    false /* useUnifiedHeaders */);


    @Nullable
    FileCache buildCache = BuildCacheUtils.createBuildCacheIfEnabled(project, projectOptions);

    GlobalScope globalScope =
            new GlobalScope(
                    project,
                    projectOptions,
                    androidBuilder,
                    extension,
                    sdkHandler,
                    ndkHandler,
                    registry,
                    buildCache);

    variantFactory = createVariantFactory(globalScope, androidBuilder, extension);

    taskManager =
            createTaskManager(
                    globalScope,
                    project,
                    projectOptions,
                    androidBuilder,
                    dataBindingBuilder,
                    extension,
                    sdkHandler,
                    ndkHandler,
                    registry,
                    threadRecorder);

    variantManager =
            new VariantManager(
                    globalScope,
                    project,
                    projectOptions,
                    androidBuilder,
                    extension,
                    variantFactory,
                    taskManager,
                    sourceSetManager,
                    threadRecorder);

    registerModels(registry, globalScope, variantManager, extension, extraModelInfo);

    // map the whenObjectAdded callbacks on the containers.
    signingConfigContainer.whenObjectAdded(variantManager::addSigningConfig);

    buildTypeContainer.whenObjectAdded(
            buildType -> {
                SigningConfig signingConfig =
                        signingConfigContainer.findByName(BuilderConstants.DEBUG);
                buildType.init(signingConfig);
                variantManager.addBuildType(buildType);
            });

    productFlavorContainer.whenObjectAdded(variantManager::addProductFlavor);

    // map whenObjectRemoved on the containers to throw an exception.
    signingConfigContainer.whenObjectRemoved(
            new UnsupportedAction("Removing signingConfigs is not supported."));
    buildTypeContainer.whenObjectRemoved(
            new UnsupportedAction("Removing build types is not supported."));
    productFlavorContainer.whenObjectRemoved(
            new UnsupportedAction("Removing product flavors is not supported."));

    // create default Objects, signingConfig first as its used by the BuildTypes.
    variantFactory.createDefaultComponents(
            buildTypeContainer, productFlavorContainer, signingConfigContainer);
}

调用project.container方法创建了一个类型为NamedDomainObjectContainer< BuildType >的对象buildTypeContainer,查看Gradle的API文档,可以看到container方法的作用:

大概的意思是创建了一个容器来管理泛型中定义的类,这些类可以使用factory工厂来创建。
所以上面创建了buildTypeproductFlavorsigningConfigBaseVariantOutput这四个类的容器。

然后调用createExtension方法,该方法是抽象方法,具体在AppPlugin类中实现,如下所示:

protected BaseExtension createExtension(
        @NonNull Project project,
        @NonNull ProjectOptions projectOptions,
        @NonNull AndroidBuilder androidBuilder,
        @NonNull SdkHandler sdkHandler,
        @NonNull NamedDomainObjectContainer<BuildType> buildTypeContainer,
        @NonNull NamedDomainObjectContainer<ProductFlavor> productFlavorContainer,
        @NonNull NamedDomainObjectContainer<SigningConfig> signingConfigContainer,
        @NonNull NamedDomainObjectContainer<BaseVariantOutput> buildOutputs,
        @NonNull SourceSetManager sourceSetManager,
        @NonNull ExtraModelInfo extraModelInfo) {
    return project.getExtensions()
            .create(
                    "android",
                    AppExtension.class,
                    project,
                    projectOptions,
                    androidBuilder,
                    sdkHandler,
                    buildTypeContainer,
                    productFlavorContainer,
                    signingConfigContainer,
                    buildOutputs,
                    sourceSetManager,
                    extraModelInfo);
}

这里又创建了一个Extension,名为“android”,类型为AppExtension,传递的参数有project、androidBuildersourceSetManager、extraModleInfo和上面创建的四个Container对象。
如果我们再回过头来看最上面我们提到的build.gradle中的android标签,其实android标签就对应着这里的“android”的Extension,然后android标签中可以配置compileSdkVersion,并且嵌套了defaultConfig、buildTypes、signingConfigs等标签,这些标签就有上面提到的四个Container对象。

最后还创建了variantFactory、taskManager、variantManager,其中variantFactory为构建信息的工厂、taskManager为构建任务管理、variantManager构建方式与多渠道构建管理。

四、总结

本文讲述了Android Plugin中的Extension,通过源码追踪的方式讲述了android标签、标签中的嵌套标签是怎么实现的,让我们在看build.gradle的配置信息时不至于一脸懵逼。

接下来的一篇文章,我们将讲到Android Plugin中的createTasks回调方法的逻辑。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,442评论 25 707
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,596评论 18 139
  • afinalAfinal是一个android的ioc,orm框架 https://github.com/yangf...
    wgl0419阅读 6,263评论 1 9
  • 有时候一个人麻木久了,就会失去自己的理想。随波逐流久了,就不会对自己有太多的看法。安逸久了,就没有了上进的勇气。心...
    鹿鹿无畏阅读 618评论 0 51
  • 近期《中国新歌声》热播,除了各位选手用动听的歌声把各位观众的耳朵叫醒外,四大导师间互掐互损的抢人游戏也是一大笑点。...
    土豆鸡蛋阅读 223评论 3 1