Habilitar a terceros independientes para localizar aplicaciones de Android; Datos de localización fuera del APK de la aplicación

Tengo el requisito de desarrollar una aplicación de Android que carga texto localizado de recursos externos fuera de su APK principal .
La razón de esto es permitir que terceros proporcionen independientemente traducciones de la aplicación. La aplicación tiene actualmente una única localización en inglés con un número bastante grande de cadenas (~ 2.000).

Yo preferiría no romper con el sistema de recursos de Android; Por ejemplo, me gustaría proporcionar las cadenas localizadas de lenguaje principal en strings.xml como en cualquier aplicación de Android.

Para lograr esto, he creado una clase que extiende android.content.res.Resources, reemplazando los tres métodos getText . Las implementaciones primarias devolverán recursos de la fuente de localización externa cuando sea posible y, de lo contrario, reenviarán la solicitud a la implementación super.getText() .

Envoltorio de recursos:

 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); } } 

A continuación, creé una implementación ContextWrapper para envolver el contexto de una actividad. El método getResources () del contenedor de contexto devolverá el objeto IntegratedResources anterior.

ContextWrapper:

 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; } } 

Y finalmente tenemos la clase "ResourceIntegrator", que recoge recursos de un APK de localización de terceros instalado especificado.
Se podría crear una implementación diferente para extraerlos de un archivo XML o de propiedades si se desea.

ResourceIntegrator:

 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; } } 

Mi pregunta a stackoverflow es si la implementación anterior es una buena idea , ya sea utilizando la API correctamente, y si su diseño es a prueba de futuro contra las versiones desconocidas de Android de mañana.
No he visto a nadie haciendo esto antes, y no puedo encontrar nada acerca de resolver este problema en los documentos o en la web.

El requisito subyacente de permitir traducciones independientes de terceros es bastante crítico. Actualmente, no es factible mantener internamente docenas de traducciones para esta aplicación, y no tengo capacidad para revisar las traducciones proporcionadas por el usuario para la calidad.
En el caso de que este diseño sea una idea muy mala y no haya otra alternativa similar, entonces la localización puede tener que hacerse sin el sistema de administración de recursos de Android por completo.

En caso de que esto sea una buena idea, por favor siéntase libre de usar y mejorar el código anterior.

Mi pregunta a stackoverflow es si lo anterior es una buena idea

Estaré atónito si es una solución completa, ya que no puedes obligar a Android a usar tu ContextWrapper personalizado. Debería funcionar para cualquier lugar en el que llamas getString() , getText() , y getText() el estilo, pero no veo cómo funcionará en cualquier lugar en que Android acceda a esos recursos más allá de una de tus actividades. Hay muchos lugares donde no se puede llamar a getText() y así sucesivamente, tales como:

  • El iniciador de la pantalla de inicio
  • La información de su aplicación en Configuración
  • La lista de tareas recientes

Por otra parte, tendrá problemas de versiones perpetuas, a menos que planee nunca agregar nuevas cadenas. No tiene ninguna forma de forzar traducciones de terceros para soportar nuevas cadenas, por lo que terminará con una aplicación con una mezcla de cadenas traducidas y no traducidas.

Si está utilizando la API correctamente

Eso parece bien.

Si su diseño es a prueba de futuro contra las versiones desconocidas de Android de mañana

El número de lugares donde Android tendrá acceso a los propios recursos es probable que aumente, en lugar de disminuir.

Actualmente, no es factible mantener internamente docenas de traducciones para esta aplicación, y no tengo capacidad para revisar las traducciones proporcionadas por el usuario para la calidad.

Entonces solo soporta lenguajes que estés dispuesto a administrar tú mismo. En primer lugar, como he señalado, no veo cómo será una solución completa, en un par de frentes. En segundo lugar, usted parece pensar que su acercamiento hará que usted no se culpará de traducciones pobres, y mientras que probablemente reducirá cuánta culpa usted consigue, no eliminará tal culpa. Estoy de acuerdo con Squonk en este sentido.

Admiro tu celo por ofrecer más traducciones, pero, personalmente, no voy a seguir este camino en particular.

No creo que su técnica funcione para las referencias de recursos en los diseños. Estos son resueltos por el LayoutInflater y, en última instancia, por la aplicación TypedArray, que invoca métodos privados para cargar los recursos.

FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.