Лучшая практика: расширение или переопределение класса проекта библиотеки Android - PullRequest
32 голосов
/ 31 марта 2012

Мы используем Android Library Project для совместного использования основных классов и ресурсов между различными сборками (целями) нашего приложения для Android. Проекты Android для каждой конкретной цели ссылаются на проект библиотеки Core (за кулисами Eclipse создает и ссылается на банку из ссылочного проекта библиотеки).

Переопределение ресурсов, таких как изображения и макеты XML, легко. Файлы ресурсов, помещенные в целевой проект, такие как значок приложения или макет XML, автоматически переопределяют ресурсы основной библиотеки с тем же именем при сборке приложения. Однако иногда необходимо переопределить класс, чтобы включить поведение, специфичное для цели. Например, экран целевых настроек Amazon не может содержать ссылку на страницу приложения Google Play, требующую изменения в файле настроек предпочтений и предпочтений класса Amazon для проекта Amazon.

Цель состоит в том, чтобы уменьшить количество дублирующегося кода среди целевых проектов, удаляя как можно больше целевого кода из библиотеки Core. Мы придумали несколько подходов для реализации логики, специфичной для разных целей:

  1. Написать специфичные для цели функции в классах библиотеки Core и использовать блоки if / switch для выбора поведения на основе SKU продукта. Этот подход не очень модульный и расширяет кодовую базу библиотеки Core.
  2. Расширить конкретный класс Core в целевом проекте и при необходимости переопределить функции базового (Core) класса. Затем сохраните ссылку на объект базового класса в библиотеке Core и создайте для него экземпляр с помощью расширенного объекта класса (из Как переопределить класс в проекте библиотеки Android? )

Существуют ли другие стратегии для переопределения или расширения класса проекта библиотеки Android? Каковы некоторые из лучших практик для совместного использования и расширения общих классов среди целей приложений Android?

Ответы [ 7 ]

17 голосов
/ 12 апреля 2012

Библиотечный проект упоминается как необработанная зависимость проекта (механизм на основе исходного кода), а не как зависимость от скомпилированного jar (механизм библиотеки на основе скомпилированного кода).

@ yorkw это не так для последних версий ADT Plugin для Eclipse http://developer.android.com/sdk/eclipse-adt.html

С версии 17 Журнал изменений

Новые функции сборки Добавлена ​​возможность автоматической настройки зависимостей JAR. Любые файлы .jar в папке / libs добавляются в конфигурацию сборки (аналогично тому, как работает система сборки Ant). Кроме того, файлы .jar, необходимые для библиотечных проектов, также автоматически добавляются в проекты, которые зависят от этих библиотечных проектов. (подробнее)

Подробнее http://tools.android.com/recent/dealingwithdependenciesinandroidprojects

До этого обновление обновления проекта Activity from Library было простым, просто исключите класс. Теперь библиотека включена в файл jar, и нет способа исключить файл класса из зависимости jar.

EDIT:

Мое решение переписать / расширить Activity из библиотеки jar:

Я создал простой класс утилит:

public class ActivityUtil {

private static Class getActivityClass(Class clazz) {

    // Check for extended activity
    String extClassName = clazz.getName() + "Extended";
    try {
        Class extClass = Class.forName(extClassName);
        return extClass;
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
        // Extended class is not found return base
        return clazz;
    }
}

public static Intent createIntent(Context context, Class clazz) {
    Class activityClass = getActivityClass(clazz);
    return new Intent(context, activityClass);
}
}

Чтобы переписать класс библиотеки «SampleActivity», это проект, который зависит от этой библиотеки, создайте новый класс с именем SampleActivityExtended в проекте в том же пакете и добавьте новое действие в свой AndroidManifest.xml.

ВАЖНО: все намерения, ссылающиеся на перезаписанные действия, должны создаваться через класс util следующим образом:

Intent intent = ActivityUtil.createIntent(MainActivity.this, SampleActivity.class);
...
startActivity(intent);
4 голосов
/ 03 апреля 2012

За кулисами Eclipse создает и ссылается на банку из ссылочного проекта библиотеки.

Это не совсем точно. Библиотечный проект упоминается как необработанная зависимость проекта (механизм на основе исходного кода), а не как зависимость от скомпилированного jar (механизм библиотеки на основе скомпилированного кода). В настоящее время Android SDK не поддерживает экспорт проекта библиотеки в автономный файл JAR. Проект библиотеки всегда должен компилироваться / создаваться косвенно, путем ссылки на библиотеку в зависимом приложении и построения этого приложения. При сборке зависимого проекта скомпилированный исходный код и исходные ресурсы, которые необходимо отфильтровать / объединить из проекта библиотеки, копируются и должным образом включаются в конечный файл apk. Обратите внимание, что команда Android начала реконструировать весь проект библиотечного проекта (перевести его из механизма на базе нашего процессора в механизм библиотеки на основе скомпилированного кода) начиная с r14, как упоминалось в в этом предыдущем сообщении в блоге .

Каковы некоторые из лучших практик для совместного использования и расширения общих классов среди целей приложений Android?

Решение, предоставляемое Android, - Библиотечный проект .
Решение, данное в Java: Наследование и Полиморфизм .
Собирайтесь вместе, лучшая практика IMO - это второй вариант, который вы упомянули в вопросе:

2. Расширить конкретный класс Core в целевом проекте и переопределить функции базового (Core) класса по мере необходимости. Затем сохраните ссылку на объект базового класса в библиотеке Core и создайте для него экземпляр с помощью расширенного объекта класса (из проекта библиотеки Android - Как перезаписать класс?)

Исходя из своего личного опыта, я всегда использую Android Library Project (иногда с Regular Java Project, для реализации / сборки common-lib.jar, который содержит только POJO), управляю общим кодом, например SuperActivity или SuperService, и расширяю / реализую собственно классы / интерфейсы в зависимом проекте для полиморфизма.

2 голосов
/ 08 января 2014

Решение на основе решения PoisoneR и Turbo.

public static Class<?> getExtendedClass(Context context, String clsName) {

    // Check for extended activity
    String pkgName = context.getPackageName();
    Logger.log("pkgName", pkgName);
    String extClassName = pkgName + "." + clsName + "Extended";
    Logger.log("extClassName", extClassName);

    try {
        Class<?> extClass = Class.forName(extClassName);
        return extClass;
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
        // Extended class is not found return base
        return null;
    }
}

Преимущества этого в том, что

  1. Расширенный класс может находиться в пакете проекта, а не в пакете библиотеки. Спасибо Turbo за эту часть.

  2. Принимая String в качестве аргумента вместо Class объекта, этот метод можно использовать даже с ProGuard. getName() - вот где проблема с ProGuard, так как он будет возвращать что-то вроде «a» вместо имени исходного класса. Таким образом, в исходном решении вместо поиска ClassExtended вместо этого он будет искать aExtended, то, чего не существует.

1 голос
/ 12 ноября 2013

Вы также можете использовать фабрику действий, если вам нужно предоставить расширенные действия для разных вариантов сборки и иметь в своей библиотеке только абстрактную фабрику.Это можно установить в ваших вариантах сборки Файл приложения.

1 голос
/ 05 марта 2013

Я был вдохновлен ответом PoinsoneR на создание класса Utility, чтобы сделать то же самое для фрагментов - переопределить фрагмент в библиотеке Android.Шаги похожи на его ответ, поэтому я не буду вдаваться в подробности, но вот класс:

package com.mysweetapp.utilities;

import android.support.v4.app.Fragment;

public class FragmentUtilities 
{
    private static Class getFragmentClass(Class clazz) 
    {
        // Check for extended fragment
        String extClassName = clazz.getName() + "Extended";
        try 
        {
            Class extClass = Class.forName(extClassName);
            return extClass;
        } 
        catch (ClassNotFoundException e) 
        {
            e.printStackTrace();
            // Extended class is not found return base
            return clazz;
        }
    }

    public static Fragment getFragment(Class clazz) 
    {
        Class fragmentClass = getFragmentClass(clazz);

        Fragment toRet = null;

        try 
        {
            toRet = (Fragment)fragmentClass.newInstance();

            return toRet;
        } 
        catch (InstantiationException e) 
        {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } 
        catch (IllegalAccessException e) 
        {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        return toRet;
    }
}

Использование:

FragmentUtilities.getFragment(MySpecialFragment.class)
1 голос
/ 09 апреля 2012

Не могли бы вы уточнить, чем отличается Kindle и обычный Android? Я думаю - они одинаковы. То, что вам нужно, это разные ресурсы для Kindle и других устройств. Тогда используйте соответствующий ресурс. Например я использую 2 ссылки для хранения:

<string name="appStore">&lt;a href=http://market.android.com/details?id=com.puzzle.jigsaw>Android Market&lt;/a> or &lt;a href=http://www.amazon.com/gp/mas/dl/android?p=com.puzzle.jigsaw>Amazon Appstore&lt;/a> &lt;br>http://market.android.com/details?id=com.puzzle.jigsaw &lt;br>href=http://www.amazon.com/gp/mas/dl/android?p=com.puzzle.jigsaw</string>
<string name="appStore_amazon">&lt;a href=http://www.amazon.com/gp/mas/dl/android?p=com.puzzle.jigsaw>Amazon Appstore&lt;/a> &lt;br>href=http://www.amazon.com/gp/mas/dl/android?p=com.puzzle.jigsaw</string>

и используйте appStore для всех продуктов Amazone и appStore_amazon для Kindle.

Как определить, где вы находитесь во время выполнения - это был бы еще один вопрос, на который здесь отвечали много раз.

1 голос
/ 02 апреля 2012

Как насчет использования подхода callback здесь?(Хорошо, обратный вызов немного вводит в заблуждение, но в настоящее время у меня нет другого слова для этого:

Вы можете объявить интерфейс в каждом действии, который должен / может быть расширен пользователем. Этот интерфейс будет иметь такие методы, как List<Preference> getPreferences(Activity activity) (передайте здесь любые параметры, которые вам нужны, я бы использовал Activity или, по крайней мере, Context, чтобы быть защищенным от будущего).

Этот подход может дать вам то, что вы хотите, если я правильно понял.Хотя я раньше этого не делал и не знаю, как другие люди справляются с этим, я бы попробовал и посмотреть, работает ли он.

...