Skip to content

Latest commit

 

History

History
283 lines (245 loc) · 14.1 KB

20170312_android.multi.flavors.md

File metadata and controls

283 lines (245 loc) · 14.1 KB

MultipleFlavor 實作思路

ProductFlavor 的問題

先確認了主要問題因為有用 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 設計成這個樣子。

探索一下 task 實作

由於 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 整組換掉。

其它後續的實作就依需求湊答案而已,我目前提供的版本只能視為其中一種實作。