4

我需要开发一个 Android 应用程序,该应用程序从其主 APK之外的外部资源加载本地化文本。
这样做的原因是使第三方能够独立地提供应用程序的翻译。该应用程序目前有一个带有大量字符串(~2,000)的单一英语本地化。

我不希望脱离 Android 的资源系统;例如,我想像strings.xml在任何 Android 应用程序中一样提供主要语言本地化字符串。

为此,我创建了一个扩展 android.content.res.Resources 的类,覆盖了这三个getText方法。覆盖的实现将尽可能从外部本地化源返回资源,否则将请求转发给super.getText()实现。

资源包装器:

public class IntegratedResources extends Resources {

    private ResourceIntegrator ri;

    public IntegratedResources(AssetManager assets, DisplayMetrics metrics, Configuration config, ResourceIntegrator ri) {
        super(assets, metrics, config);
        this.ri = ri;
    }

    @Override
    public CharSequence getText(int id)
            throws NotFoundException {
        return ri == null ? super.getText(id) : ri.getText(id);
    }

    @Override
    public CharSequence getText(int id, CharSequence def)
            throws NotFoundException {
        return ri == null ? super.getText(id, def) : ri.getText(id, def);
    }

    @Override
    public CharSequence[] getTextArray(int id) 
            throws NotFoundException {
        return ri == null ? super.getTextArray(id) : ri.getTextArray(id);
    }
}

然后我创建了一个 ContextWrapper 实现来包装一个 Activity 的上下文。上下文包装器的 getResources() 方法将返回上面的 IntegratedResources 对象。

上下文包装器:

public class IntegratedResourceContext extends ContextWrapper {

    private IntegratedResources integratedResources;

    public IntegratedResourceContext(Activity activity, String packageName) 
    throws NameNotFoundException {
        super(activity);

        ResourceIntegrator ri = packageName == null ? null : new ResourceIntegrator(activity, packageName);

        DisplayMetrics displayMetrics = new DisplayMetrics();
        activity.getWindow().getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);

        integratedResources = new IntegratedResources(activity.getAssets(), displayMetrics, 
                activity.getResources().getConfiguration(), ri);
    }

    @Override
    public Resources getResources() {
        return integratedResources;
    }
}

最后,我们有了“ResourceIntegrator”类,它从指定的已安装第三方本地化 APK 中挑选资源。
如果需要,可以创建不同的实现以从 XML 或属性文件中提取它们。

资源整合器:

public class ResourceIntegrator {

    private Resources rBase;
    private Resources rExternal;
    private String externalPackageName;

    private Map<Integer, Integer> baseIdToExternalId = new HashMap<Integer, Integer>();

    public ResourceIntegrator(Context context, String externalPackageName) 
    throws NameNotFoundException {
        super();
        rBase = context.getResources();

        this.externalPackageName = externalPackageName;

        if (externalPackageName != null) {
            PackageManager pm = context.getPackageManager();
            rExternal = pm.getResourcesForApplication(externalPackageName);
        }
    }

    public CharSequence getText(int id, CharSequence def) {
        if (rExternal == null) {
            return rBase.getText(id, def);
        }

        Integer externalId = baseIdToExternalId.get(id);
        if (externalId == null) {
            // Not loaded yet.
            externalId = loadExternal(id);
        }

        if (externalId == 0) {
            // Resource does not exist in external resources, return from base.
            return rBase.getText(id, def);
        } else {
            // Resource has a value in external resources, return it.
            return rExternal.getText(externalId);
        }
    }

    public CharSequence getText(int id)
    throws NotFoundException {
        if (rExternal == null) {
            return rBase.getText(id);
        }

        Integer externalId = baseIdToExternalId.get(id);
        if (externalId == null) {
            // Not loaded yet.
            externalId = loadExternal(id);
        }

        if (externalId == 0) {
            // Resource does not exist in external resources, return from base.
            return rBase.getText(id);
        } else {
            // Resource has a value in external resources, return it.
            return rExternal.getText(externalId);
        }
    }

    public CharSequence[] getTextArray(int id)
    throws NotFoundException {
        if (rExternal == null) {
            return rBase.getTextArray(id);
        }

        Integer externalId = baseIdToExternalId.get(id);
        if (externalId == null) {
            // Not loaded yet.
            externalId = loadExternal(id);
        }

        if (externalId == 0) {
            // Resource does not exist in external resources, return from base.
            return rBase.getTextArray(id);
        } else {
            // Resource has a value in external resources, return it.
            return rExternal.getTextArray(externalId);
        }
    }

    private int loadExternal(int baseId) {
        int externalId;

        try {
            String entryName = rBase.getResourceEntryName(baseId);
            String typeName = rBase.getResourceTypeName(baseId);
            externalId = rExternal.getIdentifier(entryName, typeName, externalPackageName);
        } catch (NotFoundException ex) {
            externalId = 0;
        }

        baseIdToExternalId.put(baseId, externalId);
        return externalId;
    }
}

我对 stackoverflow 的问题是,上述实现是否是一个好主意,它是否正确使用了 API,以及它的设计是否能够抵御未来未知的 Android 版本。
我以前没有见过任何人这样做,并且似乎无法在文档或网络上找到任何有关解决此问题的信息。

允许独立第三方翻译的基本要求相当关键。目前在内部维护此应用程序的数十个翻译是不可行的,而且我没有能力审查用户提供的翻译的质量。
如果这种设计是一个非常糟糕的想法并且没有类似的替代方案可用,那么本地化可能必须在完全没有 Android 的资源管理系统的情况下完成。

如果这是一个好主意,请随时使用和改进上述代码。

4

2 回答 2

1

我对stackoverflow的问题是以上是否是个好主意

如果它是一个完整的解决方案,我会感到震惊,因为您不能强制 Android 使用您的自定义ContextWrapper. getString()它应该适用于您手动调用,等的任何地方getText(),但我看不出它在Android访问您的一项活动之外的这些资源的任何地方如何工作。有很多地方不能自己打电话getText()等等,比如:

  • 主屏幕启动器
  • 设置中您的应用程序的信息
  • 最近的任务列表

此外,除非您打算永远不添加新字符串,否则您将遇到永久的版本控制问题。您无法强制第三方翻译支持新字符串,因此您最终会得到一个混合了已翻译和未翻译字符串的应用程序。

是否正确使用 API

这似乎没问题。

它的设计是否能够抵御未来未知的 Android 版本

Android 将访问资源本身的地方的数量可能会增加,而不是减少。

目前在内部维护这个应用程序的几十个翻译是不可行的,而且我没有能力审查用户提供的翻译的质量。

然后只支持您愿意自己管理的语言。首先,正如我所指出的,在几个方面,我看不到这将是一个完整的解决方案。其次,您似乎认为您的方法不会因为翻译不佳而受到指责,虽然它可能会减少您受到的指责,但不会消除这种指责。在这方面,我同意 Squonk。

我钦佩您提供更多翻译的热情,但就我个人而言,我不会走这条特殊的道路。

于 2012-06-15T10:12:24.823 回答
1

我认为您的技术不适用于布局中的资源引用。这些由 LayoutInflater 解决,最终由 TypedArray 实现解决,后者调用私有方法来加载资源。

于 2012-09-23T23:03:53.177 回答