Обходной путь для дублирующих классов в ароматах Gradle и основных

Проблема:

Я полагаю, что моя проблема довольно распространена. У меня довольно большая база кода градиента, из которой я создаю настраиваемые версии с использованием вкусов продукта. Этим товарам часто требуется индивидуальная версия одного или нескольких классов из src\main\java .

Я прочитал документацию Gradle и также столкнулся со следующими вопросами, рассматривая ту же проблему:
Использование Build Flavors – структурирование исходных папок и build.gradle правильно
Создавайте ароматы для разных версий того же класса

Я понимаю, почему вы не можете определить один и тот же класс в src\main\java а также в ваших вкусах, однако решение о перемещении класса из src\main\java в ваш вкус продукта имеет довольно серьезный недостаток. Когда вы переместите класс из src\main\java в свой последний вкус, чтобы настроить его, вам также нужно перенести копию оригинальной нестандартной версии этого класса в любой другой предыдущий продукт, или они больше не будут строить.

Вам может потребоваться только один раз изменить один или два разных класса из оригиналов (а затем перераспределить эти классы в каталогах вкусов), но со временем количество перемещенных классов будет построено, а число останется в src\main\java будет уменьшаться каждый раз, когда вы это сделаете. В конце концов большинство классов будут в ароматах (хотя большинство будет копиями оригиналов), а src\main\java будет почти пустым, что приведет к победе над целями всей структуры сборки Gradle.
Кроме того, вам нужно будет сохранить «по умолчанию» вкус, который вы можете клонировать каждый раз, когда вы начинаете новый вкус, поэтому вы знаете, что начинаете со всех классов в соответствии с исходной базой кода.

Мое начальное обходное решение:

Используйте поля в BuildConfig для определения использования пользовательского класса или нет:

 buildConfigField 'boolean', 'CUSTOM_ACTIVITY_X', 'true' 

Затем вы можете использовать код, например:

 final Intent intent = new Intent(); ... if (BuildConfig.CUSTOM_ACTIVITY_X) { intent.setClass(ThisActivity.this, CustomActivityX.class); } else { intent.setClass(ThisActivity.this, DefaultActivityX.class); } startActivity(intent); 

Каждому аромату все равно понадобится копия CustomActivityX, но она может быть просто пустым пустым классом в ароматах, где вы знаете, что он не будет использоваться. Это означает, что ваши версии по умолчанию для классов всегда сохраняются в src\main\java .

Улучшенный обходной путь:

Пытаясь избавиться от необходимости создания манекена CustomActivityX в любом другом вкусе, я посмотрел на использование Class.forName() .
Например:

 final Class activityX; if (BuildConfig.CUSTOM_ACTIVITY_X) { try { activityX = Class.forName("CustomActivityX"); } catch (ClassNotFoundException e) { e.printStackTrace(); } } else { activityX = DefaultActivityX.class; } final Intent intent = new Intent(); ... intent.setClass(ThisActivity.this, activityX); startActivity(intent); 

Однако это, очевидно, приводит к тому, что «activityX может быть не инициализирован» при попытке использовать его из-за блока try/catch .

Как это можно преодолеть ???

Solutions Collecting From Web of "Обходной путь для дублирующих классов в ароматах Gradle и основных"

Таким образом, здесь есть две проблемы: 1) ошибка кодирования в вашем основном обходном пути; 2) более широкая проблема, которую вы пытаетесь решить.

Я могу помочь больше с первой проблемой, чем с другой. Все, что вам нужно сделать, это инициализировать вашу переменную. Вы спросили: «Как это можно преодолеть?» Я считаю, что это сделает трюк:

 Class activityX = null; //remove 'final' and initialize this to null, then null-check it later if (BuildConfig.CUSTOM_ACTIVITY_X) { try { activityX = Class.forName("CustomActivityX"); } catch (ClassNotFoundException e) { e.printStackTrace(); } } else { activityX = DefaultActivityX.class; } final Intent intent = new Intent(); ... if(activityX != null) { intent.setClass(ThisActivity.this, activityX); startActivity(intent); } 

Теперь, в отношении более широкой проблемы, которую вы решаете, довольно ясно, что приведенный выше код имеет некоторые запахи кода, которые сигнализируют, что может быть лучший способ. Я использовал классы, специфичные для вкуса, без необходимости копировать их во все другие вкусы. В этих случаях другие ароматы не выполняли код, который полагался на эти классы. Например, представьте себе «платную» версию, где «свободная» версия просто никогда не загружает некоторые из классов, доступных для платных пользователей.

Поэтому я думаю, что проблема возникает только в том случае, если все вкусы пытаются загрузить класс, о котором идет речь. Трудно предложить альтернативу, не понимая общую кодовую базу. Однако я бы предложил попробовать использовать наследование, абстрактные классы или интерфейсы для решения вашей проблемы.

Первое, что я хотел бы исследовать: действительно ли Activity – наименьшая единица кода / поведения, которую вам нужно переопределить? Я подозреваю, что это не так. Возможно, вы можете создать BaseActivity, который имеет весь шаблонный код, а затем изолировать код, специфичный для конкретного вкуса, к точным компонентам, которые его требуют.

Например, частый шаблон, который я использовал, – обрабатывать подобные вещи через XML-файлы. Таким образом, действия и фрагменты всегда могут иметь одинаковое поведение по вкусам, но они загружают разные макеты. Эти макеты содержат настраиваемые компоненты представления (которые простираются от общего интерфейса или абстрактного родительского класса, что позволяет операции кодировать этот интерфейс). Теперь вкусы, которым не нужны определенные классы, никогда не будут пытаться загружать их, потому что они не существуют в макете, загруженной этим особым ароматом.

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

Я считаю, что расширение вашей деятельности (и других классов) является более чистым решением.

Например, если у вас есть HelpActivity в главном аромате, вы делаете этот абстрактный класс, и в аромате вы создаете FlavorHelpActivity, который расширяет HelpActivity. Здесь вы называете супер и добавляете все, что уникально для этого вкуса.

Вам нужно обновить манифест в каждом аромате, чтобы указать правильное название аромата (FlavorHelpActivity), а также ваши пункты меню должны указывать правильно, поэтому в расширенных действиях вы должны переопределить onOptionsItemSelected.

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

— Обновить —

Я пробовал свой предложенный подход, и он не идеален:

  • Программирование в деятельности, которая расширяет другую деятельность, немного странно. Половина вашей логики не в том классе, на который вы смотрите.
  • У вас есть довольно накладные расходы на то, чтобы все было правильно в манифесте и как деятельность связана с намерениями.

Это эффективный способ работы, но я, вероятно, вернусь к ситуации, описанной человеком, который задал вопрос.

Нет необходимости в жестком коде Названия действий.

Добавьте фильтр намерения для соответствующих действий, которые нужно загрузить в соответствии с вкусом.

Аромат A: ActivityA.java

Вкус B: ActivityB.java

Случай: Основной (общий): BaseActivity с кнопкой. При нажатии кнопки для аромата A следует перейти к ActivityA, а для аромата B следует перейти к ActivityB.

Проявите аромат A:

 <activity android:name="com.abc.ActivityA" android:screenOrientation="portrait" android:theme="@style/AppTheme.NoActionBar"> <intent-filter> <action android:name="com.abc.openDetailsActivity" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> </activity> 

Манифест для вкуса B:

 <activity android:name="com.abc.ActivityB" android:screenOrientation="portrait" android:theme="@style/AppTheme.NoActionBar"> <intent-filter> <action android:name="com.abc.openDetailsActivity" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> </activity> 

Оба должны иметь одно и то же действие в манифесте.

Теперь из BaseActivity вызывается следующее:

 Intent i = new Intent(); i.setAction("com.abc.openDetailsActivity"); startActivity(i); 

Если Build Variant равен A, ActivityA откроется из Flavor A, а если Build Variant – B, ActivityB откроется из Flavor B