В этой статье я хотел бы привести несколько проблем с которыми я столкнулся при разработке андроид приложений и способы их решения.
Проблема: хочется иметь возможность вызова некоторых методов 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.
Рабочее приложение на андроид.маркете.
Спасибо за внимание!