Android Gradle學(xué)習(xí)(五):Extension詳解

前面我們已經(jīng)詳細(xì)講解了 Gradle 的 Task、Project 等基本用法,現(xiàn)在我們還要學(xué)習(xí)一個(gè)很重要的概念 Extension,它在 Gradle 中幾乎隨處可見(jiàn),特別是在 Android 打包配置中。

1. 什么是Extension

我們先來(lái)看一段 Android 應(yīng)用的 Gradle 配置代碼:

android {
    compileSdkVersion 26
    defaultConfig {
        applicationId "com.hm.iou.thinapk.demo"
        minSdkVersion 19
        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'
        }
    }
}

相信做 Android 應(yīng)用開(kāi)發(fā)的同學(xué),對(duì)這段代碼都快看吐了吧。記得當(dāng)初剛從 eclipse 轉(zhuǎn)到 Android Studio 的時(shí)候,看這些配置就像看天書一樣,只知道按規(guī)定配置就可以了。但是為什么要這樣配置?除此外還支持哪些配置?為什么一定要在 android 這個(gè)命名空間下配置呢?可以不可以定義自己的特殊配置呢?

上面這個(gè) android 打包配置,就是 Gradle 的 Extension,翻譯成中文意思就叫擴(kuò)展。它的作用就是通過(guò)實(shí)現(xiàn)自定義的 Extension,可以在 Gradle 腳本中增加類似 android 這樣命名空間的配置,Gradle 可以識(shí)別這種配置,并讀取里面的配置內(nèi)容。

2. 怎么定義Extension

2.1 ExtensionContainer

一般我們通過(guò) ExtensionContainer 來(lái)創(chuàng)建 Extension,這個(gè)類與 TaskContainer 命名有點(diǎn)類似,TaskContainer 是用來(lái)創(chuàng)建并管理 Task 的,而 ExtensionContainer 則是用來(lái)創(chuàng)建并管理 Extension 的。通過(guò) Project 的以下 API 可以獲取到 ExtensionContainer 對(duì)象:

ExtensionContainer getExtensions()
2.2 簡(jiǎn)單的Extension
//先定義一個(gè)普通的java類,包含2個(gè)屬性
class Foo {
    int age
    String username

    String toString() {
        return "name = ${username}, age = ${age}"
    }
}
//創(chuàng)建一個(gè)名為 foo 的Extension
getExtensions().create("foo", Foo)

//配置Extension
foo {
    age = 30
    username = "hjy"
}

task testExt << {
    //能直接通過(guò) project 獲取到自定義的 Extension
    println project.foo
}

上面這個(gè)例子中,foo 就是我們自定義的 Extension 了,它里面能配置的屬性與類 Foo 中的字段是一致的,在 build.gradle 中可以直接通過(guò) project.foo 來(lái)訪問(wèn)。每個(gè) Extension 實(shí)際上與某個(gè)類是相關(guān)聯(lián)的,在 build.gradle 中通過(guò) DSL 來(lái)定義,Gradle 會(huì)識(shí)別解析并生成一個(gè)對(duì)象實(shí)例,通過(guò)該類可以獲取我們所配置的信息。

之前有講過(guò) Project 有個(gè)擴(kuò)展屬性是通過(guò) ext 命名空間配置的,可以看到 ext 與這里是類似的,不同的是 ext 可以配置任何鍵值對(duì)的屬性值,而這里只能識(shí)別我們定義的 Java 類里的屬性值。

2.3 ExtensionContainer主要API功能及用法
2.3.1 創(chuàng)建Extension
<T> T create(String name, Class<T> type, Object... constructionArguments)
<T> T create(Class<T> publicType, String name, Class<? extends T> instanceType, Object... constructionArguments)

先來(lái)看看后面這個(gè) API 所有參數(shù)的含義。

  • publicType:創(chuàng)建的 Extension 實(shí)例暴露出來(lái)的類類型;
  • name:要?jiǎng)?chuàng)建的Extension的名字,可以是任意符合命名規(guī)則的字符串,不能與已有的重復(fù),否則會(huì)拋異常;
  • instanceType:該Extension的類類型;
  • constructionArguments:類的構(gòu)造函數(shù)參數(shù)值

官方文檔里還說(shuō)明了一個(gè)特性,創(chuàng)建的 Extension 對(duì)象都默認(rèn)實(shí)現(xiàn)了 ExtensionAware 接口,

The new instance will have been dynamically made ExtensionAware, which means that you can cast it to ExtensionAware.

我們來(lái)看一個(gè)具體的實(shí)例,包含了上面2個(gè) API 的使用:

//父類
class Animal {
    
    String username
    int legs

    Animal(String name) {
        username = name
    }
    
    void setLegs(int c) {
        legs = c
    }

    String toString() {
        return "This animal is $username, it has ${legs} legs."
    }
}

//子類
class Pig extends Animal {
    
    int age
    String owner

    Pig(int age, String owner) {
        super("Pig")
        this.age = age
        this.owner = owner
    }

    String toString() {
        return super.toString() + " Its age is $age, its owner is $owner."
    }

}

//創(chuàng)建的Extension是 Animal 類型
Animal aAnimal = getExtensions().create(Animal, "animal", Pig, 3, "hjy")
//創(chuàng)建的Extension是 Pig 類型
Pig aPig = getExtensions().create("pig", Pig, 5, "kobe")

animal {
    legs = 4    //配置屬性
}

pig {
    setLegs 2   //這個(gè)是方法調(diào)用,也就是 setLegs(2)
}

task testExt << {
    println aAnimal
    println aPig
    //驗(yàn)證 aPig 對(duì)象是 ExtensionAware 類型的
    println "aPig is a instance of ExtensionAware : ${aPig instanceof ExtensionAware}"
}

運(yùn)行 testExt 這個(gè)任務(wù),查看結(jié)果如下:

This animal is Pig, it has 4 legs. Its age is 3, its owner is hjy.
This animal is Pig, it has 2 legs. Its age is 5, its owner is kobe.
aPig is a instance of ExtensionAware : true
2.3.2 增加Extension

前面的 create() 方法會(huì)創(chuàng)建并返回一個(gè) Extension 對(duì)象,與之相似的還有一個(gè) add() 方法,唯一的差別是它并不會(huì)返回一個(gè) Extension 對(duì)象。

void add(Class<T> publicType, String name, T extension)
void add(String name, T extension)

基于前面的這個(gè)實(shí)例,我們可以換一種寫法如下:

getExtensions().add(Pig, "mypig", new Pig(5, "kobe"))
mypig {
    username = "MyPig"
    legs = 4
    age = 1
}
task testExt << {
    def aPig = project.getExtensions().getByName("mypig")
    println aPig
}
2.3.3 查找Extension
Object findByName(String name)
<T> T findByType(Class<T> type)
Object getByName(String name)       //找不到會(huì)拋異常
<T> T getByType(Class<T> type)  //找不到會(huì)拋異常

這幾個(gè) API 很好理解,一個(gè)是通過(guò)名字去查找,一個(gè)是通過(guò)類類型去查找。

2.4 嵌套Extension

類似下面這樣的配置應(yīng)該隨處可見(jiàn):

outer {
    
    outerName "outer"
    msg "this is a outer message."

    inner {
        innerName "inner"
        msg "This is a inner message."
    }
    
}

形式上就是外面的 Extension 里面定義了另一個(gè) Extension,這種叫做 nested Extension,也就是嵌套的 Extension。本文開(kāi)頭的 Android 打包配置,就是采用的這種方式。

那怎么創(chuàng)建上面這種 Extension 呢?

class OuterExt {
    
    String outerName
    String msg
    InnerExt innerExt = new InnerExt()

    void outerName(String name) {
        outerName = name
    }

    void msg(String msg) {
        this.msg = msg
    }
    
    //創(chuàng)建內(nèi)部Extension,名稱為方法名 inner
    void inner(Action<InnerExt> action) {
        action.execute(inner)
    }

    //創(chuàng)建內(nèi)部Extension,名稱為方法名 inner
    void inner(Closure c) {
        org.gradle.util.ConfigureUtil.configure(c, innerExt) 
    }

    String toString() {
        return "OuterExt[ name = ${outerName}, msg = ${msg}] " + innerExt
    }

}


class InnerExt {
    
    String innerName
    String msg

    void innerName(String name) {
        innerName = name
    }

    void msg(String msg) {
        this.msg = msg
    }

    String toString() {
        return "InnerExt[ name = ${innerName}, msg = ${msg}]"
    }

}

def outExt = getExtensions().create("outer", OuterExt)

outer {
    
    outerName "outer"
    msg "this is a outer message."

    inner {
        innerName "inner"
        msg "This is a inner message."
    }

}

task testExt << {
    println outExt
}

運(yùn)行結(jié)果如下:

OuterExt[ name = outer, msg = this is a outer message.] InnerExt[ name = inner, msg = This is a inner message.]

這里的關(guān)鍵點(diǎn)在于下面這2個(gè)方法的定義,只需要定義任意一個(gè)即可:

void inner(Action<InnerExt> action)
void inner(Closure c)

定義在 outer 內(nèi)部的 inner ,Gradle 解析時(shí)實(shí)質(zhì)上會(huì)進(jìn)行方法調(diào)用,也就是會(huì)執(zhí)行 outer.inner(...) 方法,而該方法的參數(shù)是一個(gè)閉包(俗稱 Script Block),所以在類 OuterExt 中必須定義 inner(...) 方法。

此外,前面說(shuō)到創(chuàng)建的 Extension 對(duì)象都是實(shí)現(xiàn)了 ExtensionAware 接口的,ExtensionAware 接口很簡(jiǎn)單,只包含一個(gè)方法:

ExtensionContainer getExtensions()

所以還有一種方式來(lái)創(chuàng)建嵌套的 Extension,只不過(guò)這種方式?jīng)]法自動(dòng)賦值到 OuterExt 類里的 innerExt 對(duì)象:

def innerExt = outExt.getExtensions().create("inner", InnerExt)

3. Android的Extension

先看個(gè) Android 的常規(guī)配置,以下是我的項(xiàng)目配置,截圖如下所示:

Android 配置

我們重點(diǎn)看看 defaultConfig、productFlavors、signingConfigs、buildTypes 這4個(gè)內(nèi)部 Extension對(duì)象是怎么定義的,通過(guò)查看源碼可以找到一個(gè)叫 BaseExtension 的類,里面的相關(guān)代碼如下:

    private final DefaultConfig defaultConfig;
    private final NamedDomainObjectContainer<ProductFlavor> productFlavors;
    private final NamedDomainObjectContainer<BuildType> buildTypes;
    private final NamedDomainObjectContainer<SigningConfig> signingConfigs;

    public void defaultConfig(Action<DefaultConfig> action) {
        this.checkWritability();
        action.execute(this.defaultConfig);
    }
    
     public void buildTypes(Action<? super NamedDomainObjectContainer<BuildType>> action) {
        this.checkWritability();
        action.execute(this.buildTypes);
    }

    public void productFlavors(Action<? super NamedDomainObjectContainer<ProductFlavor>> action) {
        this.checkWritability();
        action.execute(this.productFlavors);
    }

    public void signingConfigs(Action<? super NamedDomainObjectContainer<SigningConfig>> action) {
        this.checkWritability();
        action.execute(this.signingConfigs);
    }

這與前面介紹的嵌套 Extension 的定義是一致的,這里名為 android 的 Extension 是通過(guò)插件來(lái)創(chuàng)建的,關(guān)于插件的創(chuàng)建在我另一篇文章也有介紹:怎么創(chuàng)建Gradle插件。

在 app 的 build.gradle 里我們通常會(huì)采用插件 apply plugin: 'com.android.application' ,而在 library module 中則采用插件 apply plugin: 'com.android.library',先來(lái)看一張截圖:

plugin.jpg

圖中類 AppPlugin 就是插件 com.android.application 的實(shí)現(xiàn)類,LibraryPlugin 則是插件 com.android.library 的實(shí)現(xiàn)類,接著再看看 AppPlugin 里是怎樣創(chuàng)建 Extension 的:

public class AppPlugin extends BasePlugin implements Plugin<Project> {
    @Inject
    public AppPlugin(Instantiator instantiator, ToolingModelBuilderRegistry registry) {
        super(instantiator, registry);
    }

    protected BaseExtension createExtension(Project project, ProjectOptions projectOptions, Instantiator instantiator, AndroidBuilder androidBuilder, SdkHandler sdkHandler, NamedDomainObjectContainer<BuildType> buildTypeContainer, NamedDomainObjectContainer<ProductFlavor> productFlavorContainer, NamedDomainObjectContainer<SigningConfig> signingConfigContainer, NamedDomainObjectContainer<BaseVariantOutput> buildOutputs, ExtraModelInfo extraModelInfo) {
        return (BaseExtension)project.getExtensions().create("android", AppExtension.class, new Object[]{project, projectOptions, instantiator, androidBuilder, sdkHandler, buildTypeContainer, productFlavorContainer, signingConfigContainer, buildOutputs, extraModelInfo});
    }

    public void apply(Project project) {
        super.apply(project);
    }

    //省略...
}

在 createExtension() 方法中,可以看到創(chuàng)建了一個(gè)名為 android 的 Extension,該 Extension 的類型為 AppExtension,而 AppExtension 的繼承結(jié)構(gòu)為 AppExtension -> TestedExtension -> BaseExtension,所以它的實(shí)現(xiàn)邏輯大部分都是在 BaseExtension 里實(shí)現(xiàn)的。

LibraryExtension 的繼承結(jié)構(gòu)與 AppExtension 基本是一致的,有興趣的可以自己看源碼研究研究。

以后當(dāng)我們不知道 android 里有哪些配置時(shí),除了查看 API 文檔以外,還可以直接翻看 BaseExtension 源碼,基本上就能清楚了。

系列文章

Android Gradle學(xué)習(xí)(一):Gradle基礎(chǔ)入門
Android Gradle學(xué)習(xí)(二):如何創(chuàng)建Task
Android Gradle學(xué)習(xí)(三):Task進(jìn)階學(xué)習(xí)
Android Gradle學(xué)習(xí)(四):Project詳解
Android Gradle學(xué)習(xí)(五):Extension詳解
Android Gradle學(xué)習(xí)(六):NamedDomainObjectContainer詳解
Android Gradle學(xué)習(xí)(七):Gradle構(gòu)建生命周期
Android Gradle學(xué)習(xí)(八):統(tǒng)計(jì)Task執(zhí)行時(shí)長(zhǎng)
Android Gradle學(xué)習(xí)(九):一些有用的小技巧

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • afinalAfinal是一個(gè)android的ioc,orm框架 https://github.com/yangf...
    wgl0419閱讀 6,566評(píng)論 1 9
  • 1、通過(guò)CocoaPods安裝項(xiàng)目名稱項(xiàng)目信息 AFNetworking網(wǎng)絡(luò)請(qǐng)求組件 FMDB本地?cái)?shù)據(jù)庫(kù)組件 SD...
    陽(yáng)明AI閱讀 16,171評(píng)論 3 119
  • 人與人的差距根本不在智商,而是在思維的高度上。整天糾纏于雞毛蒜皮,就會(huì)忽略那些大的人生命題。決定人生上限的,往往不...
    東方新材粉末彩涂板閱讀 271評(píng)論 0 0
  • 讀了好多雞湯文說(shuō)掙錢得重要性,有錢得重要性,大多說(shuō)得非?,F(xiàn)實(shí), 無(wú)非未來(lái)和意外不知道哪一個(gè)先來(lái),無(wú)非有...
    靜水凌波閱讀 208評(píng)論 0 0
  • 阿菱:這多大的海灘啊,怎么沒(méi)有人啊! 阿球:我們不是來(lái)了嗎?你是在緬懷夏日的海灘吧? 阿菱:對(duì),夏日的海灘,嘈雜喧...
    云水坡頭閱讀 564評(píng)論 0 0

友情鏈接更多精彩內(nèi)容