Pull to refresh

Несколько проблем при разработке андроид приложений и способы их решения

Reading time5 min
Views9K
В этой статье я хотел бы привести несколько проблем с которыми я столкнулся при разработке андроид приложений и способы их решения.

Статичный контекст


Проблема: хочется иметь возможность вызова некоторых методов 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.

Рабочее приложение на андроид.маркете.

Спасибо за внимание!
Tags:
Hubs:
+22
Comments18

Articles

Change theme settings