В этой статье я хотел бы привести несколько проблем с которыми я столкнулся при разработке андроид приложений и способы их решения.
Проблема: хочется иметь возможность вызова некоторых методов Context'а из статичного контекста (простите за игру слов).
Решение: я воспользовался решением со stackoverflow, которое заключается в создании класса статичного Application'а.
Здесь нужно быть аккуратным — и использовать его с умом. Например, для получения ресурсов — переводов, картинок, стилей.
Где его нельзя использовать: для работы с GUI элементами или, например, с LayoutInflator (будет выброшен Exception).
На практике это будет выглядеть так:
и
AndroidManifest.xml:
Проблема: хочется использовать фичи доступные в апи более высокого уровня (например, элементы GUI — Views).
Решение: андроид — платформа открытая, следовательно исходники доступны. Возьмём да и перенесём не доступные классы в свою библиотеку. Естественно, в таком подходе есть минусы: нужно проверить работоспособность перенесённых классов и отредактировать не поддерживаемые на данном уровне api зависимости (здесь уж как повезёт).
Пример: NumberPicker доступен только с 11 уровня, я же перенёс его в свой проект и использую с 4 уровнем: посмотреть на github'е
Проблема: часто требуется преобразовать java объект в xml представление (например, для передачи объекта между сервисами или для сохранения в persistance state).
Обычно в таких случаях используется JAXB, но JAXB не доступен на платформе андроид.
Решение: использование другой библиотеки. Я, например, использовал Simple (XML serialization) simple.sourceforge.net
Как пользоваться?
Сначала подключим библиотеку в проект:
Заметьте: выставлен exclusion для stax-api — иначе проект не собирётся (не поддерживается андроид api)
Помечаем объекты нужными аннотациями (@Root, Transient, Element):
Сохраняем объект в xml:
Получаем объект из xml:
Проблема: В андроиде есть механизм получения переводов по идентификатору поля R класса и везде, где хочется использовать данный перевод, приходится иметь зависимость на этот класс.
Решение: кеш переводов на уровне приложения, который хранит переводы по именам и языкам. Перевод берётся из статичного контекста.
Пример кода:
После этого можно использовать, например, следующим образом:
Всё описанное выше применялось на практике:
Исходный код доступен на github.com.
Рабочее приложение на андроид.маркете.
Спасибо за внимание!
Статичный контекст
Проблема: хочется иметь возможность вызова некоторых методов Context'а из статичного контекста (простите за игру слов).
Решение: я воспользовался решением со stackoverflow, которое заключается в создании класса статичного Application'а.
Здесь нужно быть аккуратным — и использовать его с умом. Например, для получения ресурсов — переводов, картинок, стилей.
Где его нельзя использовать: для работы с GUI элементами или, например, с LayoutInflator (будет выброшен Exception).
На практике это будет выглядеть так:
public class ApplicationContext extends android.app.Application { @NotNull private static ApplicationContext instance; public ApplicationContext() { instance = this; } @NotNull public static ApplicationContext getInstance() { return instance; } }
и
AndroidManifest.xml:
<manifest xmlns:a="http://schemas.android.com/apk/res/android"> <application a:name=".ApplicationContext"> // ... </application> </manifest>
Использование андроид api более высокого уровня в приложении с более низким уровнем
Проблема: хочется использовать фичи доступные в апи более высокого уровня (например, элементы GUI — Views).
Решение: андроид — платформа открытая, следовательно исходники доступны. Возьмём да и перенесём не доступные классы в свою библиотеку. Естественно, в таком подходе есть минусы: нужно проверить работоспособность перенесённых классов и отредактировать не поддерживаемые на данном уровне api зависимости (здесь уж как повезёт).
Пример: NumberPicker доступен только с 11 уровня, я же перенёс его в свой проект и использую с 4 уровнем: посмотреть на github'е
Использование XML API
Проблема: часто требуется преобразовать java объект в xml представление (например, для передачи объекта между сервисами или для сохранения в persistance state).
Обычно в таких случаях используется JAXB, но JAXB не доступен на платформе андроид.
Решение: использование другой библиотеки. Я, например, использовал Simple (XML serialization) simple.sourceforge.net
Как пользоваться?
Сначала подключим библиотеку в проект:
<dependency> <groupId>org.simpleframework</groupId> <artifactId>simple-xml</artifactId> <version>2.6.1</version> <exclusions> <exclusion> <artifactId>stax-api</artifactId> <groupId>stax</groupId> </exclusion> </exclusions> </dependency>
Заметьте: выставлен exclusion для stax-api — иначе проект не собирётся (не поддерживается андроид api)
Помечаем объекты нужными аннотациями (@Root, Transient, Element):
@Root public class Var implements IConstant { @Transient private Integer id; @Element @NotNull private String name; @Element(required = false) @Nullable private String value; //... private Var() { } }
Сохраняем объект в xml:
final StringWriter sw = new StringWriter(); final Serializer serializer = new Persister(); try { serializer.write(vars, sw); } catch (Exception e) { throw new RuntimeException(e); }
Получаем объект из xml:
final String value = getVarString(); final Serializer serializer = new Persister(); try { final Vars vars = serializer.read(Vars.class, value); // ... } catch (Exception e) { throw new RuntimeException(e); }
Хранение переводов (уход от зависимости R класса)
Проблема: В андроиде есть механизм получения переводов по идентификатору поля R класса и везде, где хочется использовать данный перевод, приходится иметь зависимость на этот класс.
Решение: кеш переводов на уровне приложения, который хранит переводы по именам и языкам. Перевод берётся из статичного контекста.
Пример кода:
public enum TranslationsCache { instance; // first map: key: language id, value: map of captions and translations // second mal: key: caption id, value: translation private final Map<String, Map<String, String>> captions = new HashMap<String, Map<String, String>>(); private Class<?> resourceClass; private Context context; /** * Method load captions for default locale using android R class * @param context STATIC CONTEXT * @param resourceClass class of captions in android (SUBCLASS of R class) */ public void initCaptions(@NotNull Context context, @NotNull Class<?> resourceClass) { initCaptions(context, resourceClass, Locale.getDefault()); } /** * Method load captions for specified locale using android R class * @param context STATIC CONTEXT * @param resourceClass class of captions in android (SUBCLASS of R class) * @param locale language to be used for translation */ public void initCaptions(@NotNull Context context, @NotNull Class<?> resourceClass, @NotNull Locale locale) { assert this.resourceClass == null || this.resourceClass.equals(resourceClass); this.context = context; this.resourceClass = resourceClass; if (!initialized(locale)) { final Map<String, String> captionsByLanguage = new HashMap<String, String>(); for (Field field : resourceClass.getDeclaredFields()) { int modifiers = field.getModifiers(); if (Modifier.isFinal(modifiers) && Modifier.isStatic(modifiers)) { try { int captionId = field.getInt(resourceClass); captionsByLanguage.put(field.getName(), context.getString(captionId)); } catch (IllegalAccessException e) { Log.e(ResourceCache.class.getName(), e.getMessage()); } } } captions.put(locale.getLanguage(), captionsByLanguage); } } private boolean initialized(@NotNull Locale locale) { return captions.containsKey(locale.getLanguage()); } /** * @param captionId id of caption to be translated * @return translation by caption id in default language, null if no translation in default language present */ @Nullable public String getCaption(@NotNull String captionId) { return getCaption(captionId, Locale.getDefault()); } /** * @param captionId id of caption to be translated * @param locale language to be used for translation * @return translation by caption id in specified language, null if no translation in specified language present */ @Nullable public String getCaption(@NotNull String captionId, @NotNull final Locale locale) { Map<String, String> captionsByLanguage = captions.get(locale.getLanguage()); if (captionsByLanguage != null) { return captionsByLanguage.get(captionId); } else { assert resourceClass != null && context != null; initCaptions(context, resourceClass, locale); captionsByLanguage = captions.get(locale.getLanguage()); if (captionsByLanguage != null) { return captionsByLanguage.get(captionId); } } return null; } }
После этого можно использовать, например, следующим образом:
try{ //... } catch ( SomeException e ) { TranslationsCache.instance.getCaption(e.getMesageId()); }
Заключение
Всё описанное выше применялось на практике:
Исходный код доступен на github.com.
Рабочее приложение на андроид.маркете.
Спасибо за внимание!
