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

Posted

技术标签:

【中文标题】最佳实践:扩展或覆盖 Android 库项目类【英文标题】:Best practice: Extending or overriding an Android library project class 【发布时间】:2012-04-14 15:33:39 【问题描述】:

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

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

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

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

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

【问题讨论】:

(在幕后,Eclipse 从引用的库项目中创建并引用了一个 jar) - 你能解释一下你是如何设法完成这项工作的吗,这不是官方支持的根据之前发布的this blog,由 SDK 提供。在最新的 SDK r17 发行说明中,我没有看到任何部分提到这种功能缺失已得到解决。 覆盖 Android 库项目资产文件:***.com/questions/62115925/… 【参考方案1】:

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

您可以在每个应该/可能由用户扩展的 Activity 中声明一个接口。该接口将具有List<Preference> getPreferences(Activity activity) 之类的方法(在此处传递您需要的任何参数,我会使用Activity 或至少使用Context 以防万一)。

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

【讨论】:

【参考方案2】:

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

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

在 Android 应用目标之间共享和扩展通用类的一些最佳做法是什么?

Android给出的解决方案是Library Project。 Java给出的解决方案是Inheritance和Polymorphism。 一起来吧,最佳实践 IMO 是您在问题中提到的第二个选项:

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

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

【讨论】:

【参考方案3】:

能否请您澄清一下 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,对 Kindle 使用 appStore_amazon。

如何确定您在运行时的位置 - 这将是另一个问题,在这里已多次回答。

【讨论】:

【参考方案4】:

库项目被引用为原始项目依赖项(基于源的机制),而不是编译的 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。

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

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

【讨论】:

这是一个很好的解决方案。我做了一些更改以允许扩展类驻留在Application Project 的包中,而不是Library Project 包中。如果您认为这是一种改进,您可以将编辑包括在内。我将您获取extClassName 的方式更改为:String pkgName = context.getPackageName(); String extClassName = pkgName + "." + clazz.getSimpleName() + "Extended"; 您还需要将上下文传递给getActivityClass() 不幸的是,这不适用于 proguard。也许这个系统可以用于开发,然后可以在发布构建之前在源代码上运行搜索/替换脚本。 工厂模式是我相信的解决方案【参考方案5】:

我受到 PoinsoneR 的回答的启发,创建了一个实用程序类来为片段做同样的事情 - 覆盖 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】:

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

【讨论】:

【参考方案7】:

基于 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;
    

这样做的好处是

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

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

【讨论】:

以上是关于最佳实践:扩展或覆盖 Android 库项目类的主要内容,如果未能解决你的问题,请参考以下文章

Android 兼容性库。最佳实践

在objective-c或swift中覆盖pod文件方法的最佳实践是啥?

最佳实践:覆盖组件上的 OnDispose(bool disposing) 与 Disposed 事件

Xcode项目组结构的最佳实践?

在 .Net 中使用扩展方法的最佳实践是啥?

经验丨Android开发最佳实践