最佳实践:扩展或覆盖Android库项目类

时间:2012-03-30 23:32:48

标签: java android eclipse design-patterns architecture

我们正在使用Android Library Project在Android应用程序的不同构建(目标)之间共享核心类和资源。针对每个特定目标reference the Core library project的Android项目(在幕后,Eclipse创建并引用来自引用的库项目的jar)。

覆盖图像和XML布局等资源非常简单。放置在目标项目中的资源文件(例如应用程序图标或XML布局)会在构建应用程序时自动覆盖核心库的相同名称的资源。但是,有时需要重写类以启用特定于目标的行为。例如,亚马逊目标偏好设置屏幕不能包含指向Google Play应用页面的链接,需要更改亚马逊项目的preferences.xml和偏好设置活动类。

目标是减少目标项目中重复代码的数量,同时尽可能从Core库中删除尽可能多的特定于目标的代码。我们已经提出了几种方法来实现特定于不同目标的逻辑:

  1. 在Core库类中编写特定于目标的函数,并使用if / switch块根据产品SKU选择行为。这种方法不是非常模块化的,并且使核心库代码库膨胀。
  2. 扩展目标项目中的特定Core类,并根据需要覆盖基本(Core)类函数。然后保留对Core库中基类对象的引用,并使用扩展类对象(来自How to override a class within an Android library project?
  3. 对其进行实例化。

    是否有其他策略来覆盖或扩展Android库项目类?在Android应用目标中共享和扩展常见类的一些最佳做法是什么?

7 个答案:

答案 0 :(得分:17)

  

库项目被引用为原始项目依赖项(基于源的机制),而不是编译的jar依赖项(基于编译代码的库机制)。

@yorkw对于最新版本的ADT Plugin for Eclipse,情况并非如此 http://developer.android.com/sdk/eclipse-adt.html

从版本17更改日志

  

新构建功能   添加了自动设置JAR依赖项的功能。 / libs文件夹中的任何.jar文件都将添加到构建配置中(类似于Ant构建系统的工作方式)。此外,库项目所需的.jar文件也会自动添加到依赖于这些库项目的项目中。 (更多信息)

更多信息http://tools.android.com/recent/dealingwithdependenciesinandroidprojects

在此之前,从库项目更新覆盖活动很容易,只需排除该类。现在该库被包含为jar文件,并且无法从jar依赖项中排除类文件。

编辑:

我从库jar中覆盖/扩展Activity的解决方案:

我创建了一个简单的util类:

public class ActivityUtil {

private static Class getActivityClass(Class clazz) {

    // Check for extended activity
    String extClassName = clazz.getName() + "Extended";
    try {
        Class extClass = Class.forName(extClassName);
        return extClass;
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
        // Extended class is not found return base
        return clazz;
    }
}

public static Intent createIntent(Context context, Class clazz) {
    Class activityClass = getActivityClass(clazz);
    return new Intent(context, activityClass);
}
}

为了覆盖库的“SampleActivity”类,它依赖于该库,在同一个包中的项目中创建一个名为SampleActivityExtended的新类,并将新活动添加到AndroidManifest.xml。 / p>

重要提示:所有引用覆盖活动的意图都应该通过util类以下列方式创建:

Intent intent = ActivityUtil.createIntent(MainActivity.this, SampleActivity.class);
...
startActivity(intent);

答案 1 :(得分:4)

  在幕后,Eclipse从引用的库项目中创建并引用了一个jar。

这不太准确。库项目被引用为原始项目依赖项(基于源的机制),而不是编译的jar依赖项(基于编译代码的库机制)。目前,Android SDK不支持将库项目导出为自包含的JAR文件。必须始终通过引用依赖应用程序中的库并构建该应用程序来间接编译/构建库项目。当构建依赖项目时,需要从库项目中过滤/合并的已编译源和原始资源将被复制并正确包含在最终的apk文件中。请注意,Android团队已经开始改进整个库项目设计(从基于源的机制转移到基于编译代码的库机制)自r14以来,如this earlier blog post中所述。

  

在Android应用目标中共享和扩展常见类的一些最佳做法是什么?

Android提供的解决方案是Library Project Java提供的解决方案是InheritancePolymorphism 综合起来,最佳实践IMO是您在问题中提到的第二个选项:

  

2.在目标项目中扩展特定的Core类,并根据需要覆盖基本(Core)类函数。然后保留对Core库中基类对象的引用,并使用扩展类对象实例化它(来自Android库项目 - 如何覆盖类?)

根据我的个人经验,我总是使用Android Library Project(有时使用Regular Java Project,用于实现/构建仅包含POJO的common-lib.jar)管理公共代码,例如SuperActivity或SuperService,并且扩展/实现适当的多态性依赖项目中的类/接口。

答案 2 :(得分:2)

基于PoisoneR解决方案和Turbo解决方案的解决方案。

public static Class<?> getExtendedClass(Context context, String clsName) {

    // Check for extended activity
    String pkgName = context.getPackageName();
    Logger.log("pkgName", pkgName);
    String extClassName = pkgName + "." + clsName + "Extended";
    Logger.log("extClassName", extClassName);

    try {
        Class<?> extClass = Class.forName(extClassName);
        return extClass;
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
        // Extended class is not found return base
        return null;
    }
}

这样做的好处是

  1. 扩展类可以在项目的包中,而不是库的包中。感谢Turbo这一部分。

  2. 通过将String作为参数而不是Class对象,即使使用ProGuard,也可以使用此方法。 getName()是ProGuard的问题所在,因为它会返回类似“a”的内容,而不是原始类的名称。因此,在原始解决方案中,而不是寻找ClassExtended,它将寻找aExtended,而不是存在的东西。

答案 3 :(得分:1)

在这里使用callback方法怎么样? (好的,回调有点误导,但我目前没有其他的说法:

您可以在每个Activity中声明一个应该/可能由用户扩展的接口。此界面将包含List<Preference> getPreferences(Activity activity)等方法(在此处传递您需要的任何参数,我会使用Activity或至少Context来保护其未来。

当我正确理解它时,这种方法可以给你你想要的东西。虽然我之前没有这样做过,也不知道其他人如何处理这个问题,但我会尝试一下,看看它是否有效。

答案 4 :(得分:1)

请您澄清一下Kindle和普通Android的不同之处? 我想 - 他们是一样的。 您需要的是Kindle和其他设备的不同资源。然后使用适当的资源。 例如,我使用2个链接来存储:

<string name="appStore">&lt;a href=http://market.android.com/details?id=com.puzzle.jigsaw>Android Market&lt;/a> or &lt;a href=http://www.amazon.com/gp/mas/dl/android?p=com.puzzle.jigsaw>Amazon Appstore&lt;/a> &lt;br>http://market.android.com/details?id=com.puzzle.jigsaw &lt;br>href=http://www.amazon.com/gp/mas/dl/android?p=com.puzzle.jigsaw</string>
<string name="appStore_amazon">&lt;a href=http://www.amazon.com/gp/mas/dl/android?p=com.puzzle.jigsaw>Amazon Appstore&lt;/a> &lt;br>href=http://www.amazon.com/gp/mas/dl/android?p=com.puzzle.jigsaw</string>

并使用appStore用于所有无Amazone产品和appStore_amazon用于Kindle。

如何确定你的运行时间 - 这将是另一个在这里得到多次回答的问题。

答案 5 :(得分:1)

我的灵感来自PoinsoneR的答案,即创建一个Utility类来为Fragments做同样的事情 - 覆盖android库中的一个片段。这些步骤与他的答案类似,所以我不会详细介绍,但这是课程:

package com.mysweetapp.utilities;

import android.support.v4.app.Fragment;

public class FragmentUtilities 
{
    private static Class getFragmentClass(Class clazz) 
    {
        // Check for extended fragment
        String extClassName = clazz.getName() + "Extended";
        try 
        {
            Class extClass = Class.forName(extClassName);
            return extClass;
        } 
        catch (ClassNotFoundException e) 
        {
            e.printStackTrace();
            // Extended class is not found return base
            return clazz;
        }
    }

    public static Fragment getFragment(Class clazz) 
    {
        Class fragmentClass = getFragmentClass(clazz);

        Fragment toRet = null;

        try 
        {
            toRet = (Fragment)fragmentClass.newInstance();

            return toRet;
        } 
        catch (InstantiationException e) 
        {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } 
        catch (IllegalAccessException e) 
        {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        return toRet;
    }
}

用法:

FragmentUtilities.getFragment(MySpecialFragment.class)

答案 6 :(得分:1)

如果您需要为不同的构建变体提供扩展活动,并且您的库仅处理抽象工厂,您也可以使用Activity工厂。这可以在构建变体应用程序文件中设置。