先確認了主要問題因為有用 flavor,所以 class 放的地方要變成在各別 flavor 的目錄。那麼在 src/main
有放相同的 class 會發生什麼事呢?
Executing tasks: [:app:generateProdDebugSources, :app:generateProdDebugAndroidTestSources, :app:prepareProdDebugUnitTestDependencies, :app:mockableAndroidJar, :app:compileProdDebugSources, :app:compileProdDebugAndroidTestSources, :app:compileProdDebugUnitTestSources]
Configuration on demand is an incubating feature.
NDK is missing a "platforms" directory.
If you are using NDK, verify the ndk.dir is set to a valid NDK directory. It is currently set to /Users/qrtt1/Library/Android/sdk/ndk-bundle.
If you are not using NDK, unset the NDK variable from ANDROID_NDK_HOME or local.properties to remove this warning.
:app:preBuild UP-TO-DATE
:app:preProdDebugBuild UP-TO-DATE
:app:checkProdDebugManifest
:app:preDevDebugBuild UP-TO-DATE
:app:preDevReleaseBuild UP-TO-DATE
:app:preMockDebugBuild UP-TO-DATE
:app:preMockReleaseBuild UP-TO-DATE
:app:preProdReleaseBuild UP-TO-DATE
:app:prepareComAndroidSupportAnimatedVectorDrawable2520Library
:app:prepareComAndroidSupportAppcompatV72520Library
:app:prepareComAndroidSupportConstraintConstraintLayout100Alpha8Library
:app:prepareComAndroidSupportSupportCompat2520Library
:app:prepareComAndroidSupportSupportCoreUi2520Library
:app:prepareComAndroidSupportSupportCoreUtils2520Library
:app:prepareComAndroidSupportSupportFragment2520Library
:app:prepareComAndroidSupportSupportMediaCompat2520Library
:app:prepareComAndroidSupportSupportV42520Library
:app:prepareComAndroidSupportSupportVectorDrawable2520Library
:app:prepareProdDebugDependencies
:app:compileProdDebugAidl UP-TO-DATE
:app:compileProdDebugRenderscript UP-TO-DATE
:app:generateProdDebugBuildConfig UP-TO-DATE
:app:generateProdDebugResValues UP-TO-DATE
:app:generateProdDebugResources UP-TO-DATE
:app:mergeProdDebugResources UP-TO-DATE
:app:processProdDebugManifest UP-TO-DATE
:app:processProdDebugResources UP-TO-DATE
:app:generateProdDebugSources UP-TO-DATE
:app:preProdDebugAndroidTestBuild UP-TO-DATE
:app:preDevDebugAndroidTestBuild UP-TO-DATE
:app:preMockDebugAndroidTestBuild UP-TO-DATE
:app:prepareComAndroidSupportTestEspressoEspressoCore222Library
:app:prepareComAndroidSupportTestEspressoEspressoIdlingResource222Library
:app:prepareComAndroidSupportTestExposedInstrumentationApiPublish05Library
:app:prepareComAndroidSupportTestRules05Library
:app:prepareComAndroidSupportTestRunner05Library
:app:prepareProdDebugAndroidTestDependencies
:app:compileProdDebugAndroidTestAidl UP-TO-DATE
:app:processProdDebugAndroidTestManifest UP-TO-DATE
:app:compileProdDebugAndroidTestRenderscript UP-TO-DATE
:app:generateProdDebugAndroidTestBuildConfig UP-TO-DATE
:app:generateProdDebugAndroidTestResValues UP-TO-DATE
:app:generateProdDebugAndroidTestResources UP-TO-DATE
:app:mergeProdDebugAndroidTestResources UP-TO-DATE
:app:processProdDebugAndroidTestResources UP-TO-DATE
:app:generateProdDebugAndroidTestSources UP-TO-DATE
:app:preProdDebugUnitTestBuild UP-TO-DATE
:app:prepareProdDebugUnitTestDependencies
:app:mockableAndroidJar UP-TO-DATE
:app:incrementalProdDebugJavaCompilationSafeguard UP-TO-DATE
:app:javaPreCompileProdDebug
:app:compileProdDebugJavaWithJavac
/Users/qrtt1/AndroidStudioProjects/MultipleFlavor/app/src/prod/java/tw/andyang/multipleflavor/FlavorObject.java:5: error: duplicate class: tw.andyang.multipleflavor.FlavorObject
class FlavorObject {
^
1 error
FAILED
FAILURE: Build failed with an exception.
* What went wrong:
Execution failed for task ':app:compileProdDebugJavaWithJavac'.
> Compilation failed; see the compiler error output for details.
* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.
BUILD FAILED
Total time: 2.529 secs
看錯誤訊息是 duplicate class: tw.andyang.multipleflavor.FlavorObject
發生在 compileProdDebugJavaWithJavac
,目前就只有這些資訊。該怎麼處理還沒有頭緒,那先研究一下這個錯誤是由誰產生的,先寫個簡單的 Plugin 印出該 Task 的類別。在 app 下的 build.gradle 下方加上:
class ExtraTask extends RuleSource {
@Mutate void patchSourceSet(ModelMap<Task> tasks) {
tasks.each {
println " $it => ${it.class.name}"
}
}
}
apply plugin: ExtraTask
可以看到它印出來是類別 com.android.build.gradle.tasks.factory.AndroidJavaCompile
:
task ':app:compileProdDebugJavaWithJavac' => com.android.build.gradle.tasks.factory.AndroidJavaCompile_Decorated
直接 google 一下,查一下它 可能的長像:
public class AndroidJavaCompile extends JavaCompile {
InstantRunBuildContext mBuildContext;
@Override
protected void compile(IncrementalTaskInputs inputs) {
getLogger().info(
"Compiling with source level {} and target level {}.",
getSourceCompatibility(),
getTargetCompatibility());
mBuildContext.startRecording(InstantRunBuildContext.TaskType.JAVAC);
super.compile(inputs);
mBuildContext.stopRecording(InstantRunBuildContext.TaskType.JAVAC);
}
}
看起來大部分的行為是來自 JavaCompile,所以它本質上不是 Android Plugin 的限制而是來自 Gradle 的 org.gradle.api.tasks.compile.JavaCompile
,只是不確定是否因為這個限制而導致目前的 product flavor 設計成這個樣子。
由於 Groovy Object 很 nice 有不懂的事就問它就行了:
class ExtraTask extends RuleSource {
@Mutate void patchSourceSet(ModelMap<Task> tasks) {
tasks.each {
if (it.class.name.contains("AndroidJavaCompile")) {
println it.properties
}
}
}
}
把結果稍為排版一下:
[fileOperations:org.gradle.api.internal.file.DefaultFileOperations@5b734be,
toolChain:JDK 8 (1.8),
taskActions:[org.gradle.api.internal.AbstractTask$TaskActionWrapper@4b11284e,
org.gradle.api.internal.AbstractTask$TaskActionWrapper@30f26e5e,
org.gradle.api.internal.project.taskfactory.DefaultTaskClassInfoStore$IncrementalTaskAction@77b8eeb7],
convention:org.gradle.api.internal.plugins.DefaultConvention@27cd528f,
inputs:org.gradle.api.internal.tasks.DefaultTaskInputs@6bc50128,
cacheRepository:org.gradle.cache.internal.DefaultCacheRepository@692f1762,
conventionMapping:org.gradle.api.internal.ConventionAwareHelper@29541746,
sourceCompatibility:1.7,
extensions:org.gradle.api.internal.plugins.DefaultConvention@27cd528f,
temporaryDirFactory:org.gradle.api.internal.AbstractTask$17@1929898d,
generalCompileCaches:org.gradle.api.internal.tasks.compile.incremental.cache.DefaultGeneralCompileCaches@97bab07,
outputs:org.gradle.api.internal.tasks.DefaultTaskOutputs@543de90d,
cachingFileHasher:org.gradle.api.internal.changedetection.state.CachingFileHasher@650e174c,
path::app:compileProdReleaseUnitTestJavaWithJavac,
logging:org.gradle.internal.logging.compatbridge.LoggingManagerInternalCompatibilityBridge@17327ee3,
group:null,
shouldRunAfter:org.gradle.api.internal.tasks.DefaultTaskDependency@1e55b564,
platform:Java SE 8,
includes:[],
destinationDir:/Users/qrtt1/AndroidStudioProjects/MultipleFlavor/app/build/intermediates/classes/test/prod/release,
class:class com.android.build.gradle.tasks.factory.AndroidJavaCompile_Decorated,
actions:[org.gradle.api.internal.AbstractTask$TaskActionWrapper@4b11284e,
org.gradle.api.internal.AbstractTask$TaskActionWrapper@30f26e5e,
org.gradle.api.internal.project.taskfactory.DefaultTaskClassInfoStore$IncrementalTaskAction@77b8eeb7],
options:org.gradle.api.tasks.compile.CompileOptions@4be522c2,
asDynamicObject:DynamicObject for task ':app:compileProdReleaseUnitTestJavaWithJavac',
temporaryDir:/Users/qrtt1/AndroidStudioProjects/MultipleFlavor/app/build/tmp/compileProdReleaseUnitTestJavaWithJavac,
patternSetFactory:org.gradle.api.tasks.util.internal.PatternSets$PatternSetFactory@69aea297,
classpath:file collection,
identityPath::app:compileProdReleaseUnitTestJavaWithJavac,
antBuilderFactory:org.gradle.api.internal.project.DefaultAntBuilderFactory@3b4d289e,
executer:org.gradle.api.internal.tasks.execution.CatchExceptionTaskExecuter@19a2b3d8,
validators:[org.gradle.api.internal.project.taskfactory.TaskClassValidator@30f26e5e],
onlyIf:org.gradle.api.specs.AndSpec@14b31b37,
ant:org.gradle.api.internal.project.DefaultAntBuilder@1b92b563,
state:org.gradle.api.internal.tasks.TaskStateInternal@74ea72a2,
source:file collection,
finalizedBy:org.gradle.api.internal.tasks.DefaultTaskDependency@472707ce,
didWork:false,
excludes:[],
standardOutputCapture:org.gradle.internal.logging.compatbridge.LoggingManagerInternalCompatibilityBridge@17327ee3,
dependencyCacheDir:/Users/qrtt1/AndroidStudioProjects/MultipleFlavor/app/build/dependency-cache,
actionClassLoaders:[org.gradle.internal.classloader.VisitableURLClassLoader@1ddc4ec2,
org.gradle.initialization.MixInLegacyTypesClassLoader@5db250b4],
name:compileProdReleaseUnitTestJavaWithJavac,
taskDependencies:org.gradle.api.internal.tasks.DefaultTaskDependency@25b6deda,
dependsOn:[task 'compileProdReleaseUnitTestJavaWithJavac' input files,
incrementalProdReleaseUnitTestJavaCompilationSafeguard,
org.gradle.api.internal.tasks.TaskDependencies$1@56d082d3,
javaPreCompileProdReleaseUnitTest,
prepareProdReleaseUnitTestDependencies,
compileProdReleaseJavaWithJavac],
impliesSubProjects:false,
hasCustomActions:false,
enabled:true,
description:null,
project:project ':app',
mustRunAfter:org.gradle.api.internal.tasks.DefaultTaskDependency@bcad711,
targetCompatibility:1.7,
logger:org.gradle.internal.logging.slf4j.OutputEventListenerBackedLogger@2410f1ab,
services:ProjectScopeServices]
其中 source:file collection
就是它吃的 input 資料,我們可以試著把它印出來看一下:
@Mutate void patchSourceSet(ModelMap<Task> tasks) {
tasks.each {
if (it.class.name.contains("AndroidJavaCompile")) {
println it
it.source.each {
println it
}
}
}
}
task ':app:compileProdDebugJavaWithJavac'
/Users/qrtt1/AndroidStudioProjects/MultipleFlavor/app/src/main/java/tw/andyang/multipleflavor/FlavorObject.java
/Users/qrtt1/AndroidStudioProjects/MultipleFlavor/app/src/main/java/tw/andyang/multipleflavor/MainActivity.java
/Users/qrtt1/AndroidStudioProjects/MultipleFlavor/app/src/prod/java/tw/andyang/multipleflavor/FlavorObject.java
/Users/qrtt1/AndroidStudioProjects/MultipleFlavor/app/build/generated/source/r/prod/debug/android/support/compat/R.java
/Users/qrtt1/AndroidStudioProjects/MultipleFlavor/app/build/generated/source/r/prod/debug/android/support/constraint/R.java
/Users/qrtt1/AndroidStudioProjects/MultipleFlavor/app/build/generated/source/r/prod/debug/android/support/coreui/R.java
/Users/qrtt1/AndroidStudioProjects/MultipleFlavor/app/build/generated/source/r/prod/debug/android/support/coreutils/R.java
/Users/qrtt1/AndroidStudioProjects/MultipleFlavor/app/build/generated/source/r/prod/debug/android/support/fragment/R.java
/Users/qrtt1/AndroidStudioProjects/MultipleFlavor/app/build/generated/source/r/prod/debug/android/support/graphics/drawable/animated/R.java
/Users/qrtt1/AndroidStudioProjects/MultipleFlavor/app/build/generated/source/r/prod/debug/android/support/graphics/drawable/R.java
/Users/qrtt1/AndroidStudioProjects/MultipleFlavor/app/build/generated/source/r/prod/debug/android/support/mediacompat/R.java
/Users/qrtt1/AndroidStudioProjects/MultipleFlavor/app/build/generated/source/r/prod/debug/android/support/v4/R.java
/Users/qrtt1/AndroidStudioProjects/MultipleFlavor/app/build/generated/source/r/prod/debug/android/support/v7/appcompat/R.java
/Users/qrtt1/AndroidStudioProjects/MultipleFlavor/app/build/generated/source/r/prod/debug/tw/andyang/multipleflavor/R.java
/Users/qrtt1/AndroidStudioProjects/MultipleFlavor/app/build/generated/source/buildConfig/prod/debug/tw/andyang/multipleflavor/BuildConfig.java
會發現有 2 個不同路徑的 FlavorObject.java
。所以,我們可以『猜』一下,如果它只出現一次應該是會過 compile 的:
- 試著把其中一個刪除,確定 compiler 不會報錯,這個 Proof Of Concept
- 實作決定刪哪一個重複的 Object 讓 compiler 不會報錯,這個是 Product Ready 的實作
先來實作 POC,那麼來查一下 source 是什麼東西:
@Mutate void patchSourceSet(ModelMap<Task> tasks) {
tasks.each {
if (it.class.name.contains("AndroidJavaCompile")) {
println it.source.class
}
}
}
class org.gradle.api.internal.file.CompositeFileTree$FilteredFileTree
google 起來是某種 FileCollection 的 實作,我們可以利用 Project 的 ConfigurableFileCollection files(Object... paths) 建出 FileCollection,並把原來的 source 換掉:
@Mutate void patchSourceSet(ModelMap<Task> tasks) {
tasks.each {
if (it.class.name.contains("AndroidJavaCompile")) {
it.source -= it.project.files("/Users/qrtt1/AndroidStudioProjects/MultipleFlavor/app/src/main/java/tw/andyang/multipleflavor/FlavorObject.java")
}
}
}
結果它就編譯成功了(因為我們偷偷刪掉了其中一個 FlavorObject),另外你可以發現我們是使用 -=
運算子,因為 FileCollection 有 overload minus
方法,加上它的元素變動都是產生新的 FileCollection,所以我們只能撰擇把 source 整組換掉。
其它後續的實作就依需求湊答案而已,我目前提供的版本只能視為其中一種實作。