不同机器人风格的通用代码

我正在构建4种不同风格的 Android 应用程序。

我有一个类 Customization.java,它对其中3个是相同的,对1个是不同的。

由于我不能把同一个类放在主文件夹和风味文件夹中,我现在必须为这3种风味维护3个完全相同的类的副本。

我是否可以只保留这个类的两个版本?

到目前为止我考虑的事情:

  1. 我查看了味道尺寸,但结果发现它们在这种情况下是不适用的。
  2. 只保留其中一种风格的一个文件,并通过我的构建脚本复制它。

我想知道是否有比盒子更干净的东西。

20028 次浏览

I would like to convert CommonsWare's comment to an answer. I'll then explain how the final directory setup should look like. I hope this helps out the people stumbling upon this question through search.

Well, you can override resources in flavors. So, have the common one in main/res/layout/ and the flavor-specific one in yourFlavorHere/res/layout/.

So, if the Customization activity's layout file is called activity_customization.xml, you'll leave its common copy shared among the three flavors under src/main/res/layout directory and place the modified layout xml to be used by, say flavorFour, under its corresponding source set directory src/flavorFour/res/layout.

The way this works is that since flavor one to three (unlike flavor four) haven't provided their own versions of activity_customization.xml, they'll inherit the one coming from the main source set.

It's the activity Java class that gets tricky. Another possibility for that is to configure the flavors with the same activity implementation to pull from two source directories: a flavor-specific one and a common one with the common class implementation.

Unlike resources, Java code files are not merged or overridden. So, you can't have Java files with the same fully qualified class name under main as well as in any of your flavor source sets. If you do, you'll receive a duplicate class error.

To resolve this issue, the simplest solution is to move Customization activity out of the main and into each flavor source set. This works because the flavor directories are mutually exclusive (with each other, not with main) hence avoiding the conflict.

But this means three out of the four flavors have a duplicate copy of the activity - a maintenance nightmare - just because one of the flavors required some changes to it. To resolve this issue we can introduce another source directory that keeps just the common code files shared between the three flavors.

So, the build.gradle script would look something like

android {
...
productFlavors {
flavorOne {
...
}
flavorTwo {
...
}
flavorThree {
...
}
flavorFour {
...
}
}
sourceSets {
flavorOne.java.srcDir 'src/common/java'
flavorTwo.java.srcDir 'src/common/java'
flavorThree.java.srcDir 'src/common/java'
}
}

Notice the use of java.srcDir (and not srcDirs) which adds another Java source directory to the already existing default src/flavorX/java.

Now all we need to do is to drop the common Customization activity file in src/common/java to make it available to the flavors one to three. The modified version required by flavorFour would go under its own source set at src/flavorFour/java.

So, the final project structure would look something like

+ App // module
|- src
|- common // shared srcDir
|- java
|- path/to/pkg
|- CustomizationActivity.java // inherited by flavors 1, 2, 3
+ flavorOne
+ flavorTwo
+ flavorThree
+ flavorFour
|- java
|- path/to/pkg
|- CustomizationActivity.java // per-flavor activity class
|- res
|- layout
|- activity_customization.xml // overrides src/main/res/layout
|- main
+ java
|- res
|- layout
|- activity_customization.xml // inherited by flavors 1, 2, 3

I use this to override codes for 5 years, just add a piece of code to your build.gradle.

For lastest gradle plugin(>=3.4.0):

android {
......
applicationVariants.configureEach { ApplicationVariant variant ->
AndroidSourceSet flavorSourceSet = android.sourceSets.findByName(variant.productFlavors[0].name);
if (flavorSourceSet != null) {
String flavorPath = flavorSourceSet.java.srcDirs[0].path;
variant.javaCompileProvider.configure { task ->
task.exclude { FileTreeElement elem ->
!elem.isDirectory() && !elem.file.parent.startsWith(flavorPath) &&
new File(flavorPath, elem.path).exists();
}
}
}
}

For older gradle plugin:

android {
......
applicationVariants.all { ApplicationVariant variant ->
AndroidSourceSet flavorSourceSet = android.sourceSets.findByName(variant.productFlavors[0].name);
if (flavorSourceSet != null) {
variant.javaCompiler.doFirst {
String flavorPath = flavorSourceSet.java.srcDirs[0].path;
variant.javaCompiler.exclude { FileTreeElement elem ->
!elem.isDirectory() && !elem.file.parent.startsWith(flavorPath) &&
new File(flavorPath, elem.path).exists();
}
}
}
}

It'll find duplicate classes in the Main sourceset and then exclude it during compile to avoid class duplicate error.

Ravi K Thapliyal's solution did not work for me with gradle 3.5.3

Here is an alternative solution that works:

Move the code common to all your flavors into a folder such as:

src/common/java

Then copy your generic flavors code into a generic src directory:

src/generic/java

That is where your generic version of the Customization.java class should be copied into. Then, create a copy of your flavor specific code into a specific src directory e.g.

src/specific/java

Your build.gradle script should then be updated as follows:

android {
...
productFlavors {
flavor1 {
...
}
flavor2 {
...
}
flavor3 {
...
}
flavor4 {
...
}
}
sourceSets {
flavor1.java.srcDirs('src/generic/java', 'src/common/java')
flavor2.java.srcDirs('src/generic/java', 'src/common/java')
flavor3.java.srcDirs('src/generic/java', 'src/common/java')
flavor4.java.srcDirs('src/specific/java', 'src/common/java')
}
}

Note: Your Customization.java class should be removed from src/common/java

This solution works also for activity classes