Gradle 小課堂系列
Gradle 小課堂第1課 - 入門篇
Gradle 小課堂第2課 - SourceSet
什么是 SourceSet?
SourceSet 就是源碼的集合。用來告訴 gradle 我的 java 代碼在哪里,我的 resource 目錄在哪里等等。默認(rèn)情況下,只有一個 SourceSet : main,也不用做任何設(shè)置就能正常編譯項(xiàng)目,實(shí)際上是因?yàn)?SourceSet 規(guī)定了一些默認(rèn)值,如果按照默認(rèn)值配置項(xiàng)目(Android Studio 新建項(xiàng)目就按照默認(rèn)值創(chuàng)建)就不用額外去寫配置代碼了。
SourceSet 規(guī)定的那些默認(rèn)值是什么?
這個可以通過運(yùn)行一個 task 來查看,這個 task 就叫 sourceSets,可以在 Android Studio 的 gradle 面板上雙擊執(zhí)行,也可以用命令行執(zhí)行。見下圖:

執(zhí)行結(jié)果片段:
main
----
Compile configuration: compile
build.gradle name: android.sourceSets.main
Java sources: [app\src\main\java]
Manifest file: app\src\main\AndroidManifest.xml
Android resources: [app\src\main\res]
Assets: [app\src\main\assets]
AIDL sources: [app\src\main\aidl]
RenderScript sources: [app\src\main\rs]
JNI sources: [app\src\main\jni]
JNI libraries: [app\src\main\jniLibs]
Java-style resources: [app\src\main\resources]
上面列出的是 main 的默認(rèn)值,默認(rèn)情況下還應(yīng)該打印出這幾個的默認(rèn)值: debug、release、test、testDebug、testRelease、androidTest、androidTestDebug。帶 test 字符串的是與測試相關(guān)的。debug 和 release 也是默認(rèn)就創(chuàng)建好的兩個 SourceSet。
多個 SourceSet 之間的關(guān)系是什么?
先說 main 這個 SourceSet,它是一個共用的 SourceSet,可以認(rèn)為是所有 SourceSet 的“基類”,任何一個其他的 SourceSet 編譯時都會將 main 中的代碼包括進(jìn)去,也就是說 main 中的代碼肯定是會參與編譯的。
再說 debug 和 release 這兩個 SourceSet,他倆是默認(rèn)的兩個 buildType 生成的 SourceSet。buildType 會影響 SourceSet,而且 buildType 與 SourceSet 是一一對應(yīng)的,只要有一個 buildType 就會有一個 SourceSet 默默地創(chuàng)建出來。可以將這種 SourceSet 記為 buildType 維度的 SourceSet。
如果定義了 productFlavor,如下面代碼所示:
android {
productFlavors {
huoguo {
}
malatang {
}
}
}
就會生成兩個名為 huoguo 和 malatang 的 SourceSet。productFlavor 也是與 SourceSet 一一對應(yīng)的??梢詫⑦@種 SourceSet 記為 productFlavor 維度的 SourceSet。
那么最終編譯的時候到底應(yīng)該使用哪個 SourceSet?這就要提到 Build Variant,在 Android Studio 中可以在 Build Variants 界面選擇,如下圖所示:

可以看到兩個不同維度的 SourceSet 自由組合了起來,生成了 2 x 2 = 4 個 Build Variant。每次編譯只能選擇其中一種,也就是說,最終編譯使用哪些 SourceSet 是由 buildType 和 productFlavor 共同決定的,而 SourceSet 就起到了區(qū)分這些變量的作用,通過 SourceSet 的配置,使得不同的 buildType 和不同的 productFlavor 有機(jī)會使用不同的代碼來編譯。
回到 SourceSet 之間的關(guān)系的問題,同一維度下的幾個 SourceSet 之間可見是互斥的關(guān)系,而不同維度之間的 SourceSet 就是(可能產(chǎn)生的)組合的關(guān)系。因此同一維度下的不同 SourceSet 可以使用路徑完全相同的類文件,而不用擔(dān)心類重復(fù)的沖突。而不同維度下的 SourceSet 必須考慮到?jīng)_突的問題,不能使用路徑相同的類文件。
SourceSet 自定義路徑
上文提到的 sourceSets task 會打印出所有的目錄默認(rèn)配置,列出的就是所有 SourceSet 可配置的目錄,對應(yīng)的代碼如下摘自 DSL 文檔的圖:

下面舉幾個常用的例子:
例1:添加一個 java 源碼目錄的設(shè)置
android {
sourceSets {
main { // main source set
java {
def anotherDir = "......"
srcDirs anotherDir // 1 添加
srcDir anotherDir // 2 添加
setSrcDirs([anotherDir]) // 3 重置
srcDirs = [anotherDir] // 4 重置
srcDirs += [anotherDir] // 5 添加
}
}
}
}
注釋 1 與 2 位置的語句是調(diào)用了 srcDirs(dir) 和 srcDir(dir) 這兩個方法,雖然方法名有單數(shù)和復(fù)數(shù)的區(qū)別,實(shí)際上這兩個方法效果是完全一樣的,都是添加一個源碼目錄。而注釋 3 和 4 的語句是等價(jià)的,是設(shè)置屬性 srcDirs,會覆蓋之前的值,也就是說設(shè)置完成后,只剩下新添加的 anotherDir 了。注釋 5 的語句相當(dāng)于調(diào)用 setSrcDirs(getSrcDirs() + [anotherDir]),使用了運(yùn)算符重載(operator overload),重載了 + 運(yùn)算,與 1 和 2 是效果相同的。
例2:將 SourceSet 的路徑修改為另一套
android {
sourceSets {
main {
def dependSrc = 'juanbing'
java.srcDirs = ["${dependSrc}/java"]
assets.srcDirs = ["${dependSrc}/assets"]
res.srcDirs = ["${dependSrc}/res"]
manifest.srcFile "${dependSrc}/AndroidManifest.xml"
}
}
}