Gradle flavors和main中重复类的解决方法

时间:2014-07-10 12:34:32

标签: android gradle

问题:

我想我的问题很常见。我有一个相当大的gradle代码库,我使用产品风格生成自定义版本。这些产品风格通常需要src\main\java中的一个或多个类的自定义版本。

我已阅读Gradle文档,并且在查看同一问题时也遇到了以下问题:
Using Build Flavors - Structuring source folders and build.gradle correctly
Build flavors for different version of same class

我理解为什么你不能在src\main\java和你的风格中定义相同的类,但是将类从src\main\java移动到你的产品风格的解决方案有一个相当大的缺点。当您将类从src\main\java移动到最新版本以进行自定义时,您还需要将该类的原始非自定义版本的副本移动到其他所有以前的产品风格中,否则它们将不再构建。

您可能只需要每次从原始文件中调整一个或两个不同的类(然后必须将这些类重新分配到flavor目录中),但随着时间的推移,将构建移动的类的数量以及剩余的数量。每次必须执行此操作时,src\main\java都会减少。最终,大多数课程都会有各种风格(即使大多数课程都是原件的副本),src\main\java几乎是空的,有点挫败整个Gradle构建结构的目的。
此外,您需要保持每次开始新口味时可以克隆的“默认”风格,因此您知道您从原始代码库开始使用所有类。

我的初步解决方法:

使用BuildConfig中的字段来定义是否应该使用自定义类:

buildConfigField 'boolean', 'CUSTOM_ACTIVITY_X', 'true'

然后您可以使用以下代码:

final Intent intent = new Intent();
...
if (BuildConfig.CUSTOM_ACTIVITY_X) {
   intent.setClass(ThisActivity.this, CustomActivityX.class); 
} else {
   intent.setClass(ThisActivity.this, DefaultActivityX.class);  
}
startActivity(intent);

每种风味仍然需要一个副本CustomActivityX,但它可以只是一个虚拟的空类,你知道它不会被使用。这意味着您的默认版本的类始终保留在src\main\java

改进的解决方法:

在试图摆脱对其他所有风格的假型CustomActivityX的需求时,我已经考虑过使用Class.forName()。 例如:

final Class activityX;
if (BuildConfig.CUSTOM_ACTIVITY_X) {
    try {
        activityX = Class.forName("CustomActivityX");
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    }
} else {
    activityX = DefaultActivityX.class;
}

final Intent intent = new Intent();
...
intent.setClass(ThisActivity.this, activityX); 
startActivity(intent);

然而,当您尝试使用“activityX可能尚未初始化”时,由于try/catch阻止,这显然会导致。

如何克服这一点?

3 个答案:

答案 0 :(得分:4)

所以这里有两个问题:1)主要解决方法中的编码错误2)您试图解决的更广泛的问题。

第一个问题比第二个问题更能帮到我。您需要做的就是初始化变量。你问“这怎么可以克服?”我相信这会解决问题:

Class activityX = null;  //remove 'final' and initialize this to null, then null-check it later
if (BuildConfig.CUSTOM_ACTIVITY_X) {
    try {
        activityX = Class.forName("CustomActivityX");
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    }
} else {
    activityX = DefaultActivityX.class;
}

final Intent intent = new Intent();
...
if(activityX != null) {
    intent.setClass(ThisActivity.this, activityX); 
    startActivity(intent);
}

现在,关于你正在解决的更广泛的问题,很明显上面的代码有一些代码味道,这表明可能有更好的方法。我使用了特定于风味的类,而不必将它们复制到所有其他类型。在这些情况下,其他风格不执行依赖于这些类的代码。例如,想象一个“付费”版本,其中“免费”版本根本不会加载一些可供付费用户使用的类。

所以我认为只有在所有版本尝试加载有问题的类时才会出现问题。如果不了解整体代码库,很难提出替代方案。但是,我建议您尝试使用继承,抽象类或接口来解决问题。

我要调查的第一件事是:Activity真的是你需要覆盖的最小代码/行为单元吗?我怀疑它不是。也许您可以创建一个包含所有样板代码的BaseActivity,然后将特定于风味的代码隔离到需要它的确切组件中。

例如,我经常使用的模式是通过XML文件处理这种事情。那样,活动&片段可以始终具有相同的行为,但它们加载不同的布局。这些布局包含自定义视图组件(从公共接口或抽象父类扩展,允许活动编码到该接口)。现在,不需要某些类的flavor将永远不会尝试加载它们,因为它们在由特定风格加载的布局中不存在

无论如何,我希望在某种程度上有所帮助。如果不了解代码库的细微差别,就很难解决更广泛的问题。我的建议是从根本上重新思考问题并尝试让类加载器远离需要加载特定于风味的类。

答案 1 :(得分:0)

我相信扩展您的活动(和其他课程)是一个更清洁的解决方案。

例如,如果您在主要风格中有一个HelpActivity,则将其设为一个抽象类,并在风格中创建一个扩展HelpActivity的FlavorHelpActivity。在这里,您可以调用super并添加此风格独有的所有内容。

您需要更新每种风味的清单以指向活动的正确风味名称(FlavorHelpActivity),并且您的菜单项必须正确指向,因此在扩展活动中您必须覆盖onOptionsItemSelected。

我将尝试这个解决方案,所以也许以后我可以判断是否有缺点。

- 更新 -

我一直在尝试我建议的方法,但这并不理想:

  • 扩展其他活动的活动编程有点奇怪。你的逻辑中有一半不在你正在看的课程中。
  • 你在清单中把所有事情都弄好了,以及如何将活动与意图联系在一起,你会有一些开销。

这是可行的工作方式,但我可能会回到提出问题的人所描述的情况。

答案 2 :(得分:0)

无需硬编码活动名称。

根据flavor添加要加载的相应活动的intent过滤器。

风味A:ActivityA.java

风味B:ActivityB.java

案例: Main(Common):带按钮的BaseActivity。 在按钮上单击风味A应该导航到ActivityA,风味B应该导航到ActivityB。

风味A的显示:

<activity
            android:name="com.abc.ActivityA"
            android:screenOrientation="portrait"
            android:theme="@style/AppTheme.NoActionBar">
            <intent-filter>
                <action android:name="com.abc.openDetailsActivity" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </activity>

香精B的清单:

<activity
            android:name="com.abc.ActivityB"
            android:screenOrientation="portrait"
            android:theme="@style/AppTheme.NoActionBar">
            <intent-filter>
                <action android:name="com.abc.openDetailsActivity" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </activity>

两者都应该在清单中有相同的操作。

现在从BaseActivity调用以下内容:

Intent i = new Intent();
            i.setAction("com.abc.openDetailsActivity");
            startActivity(i);

如果Build Variant为A,ActivityA将从Flavor A打开 如果Build Variant为B,则ActivityB将从Flavor B打开