Динамическое создание productFlavors и sourceSets с использованием списка имен (со свойствами) в файле CSV/TXT.

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

Я могу сгруппировать вкусы (имеющие общую конфигурацию) под sourceSets со следующим кодом:

(получил от гения в связанном вопросе выше)

import com.android.build.gradle.api.AndroidSourceSet
android {
    sourceSets {
        [flavor2, flavor4].each { AndroidSourceSet ss ->
            ss.assets.srcDirs = ['repo-assets/flavor2']
            ss.res.srcDirs = ['repo-res/flavor2']
        }
    }
}

Теперь мне было интересно, можно ли получить список [flavor2, flavor4] из любого из следующих:

  • XML-файл, по которому я могу выполнить итерацию, чтобы получить все варианты (которые я положу туда)
  • Файл CSV, по которому я могу перебирать и извлекать значения.
  • Пользовательский класс, который я могу написать в отдельном файле и получить данные от статических членов в классе.

В дополнение к имени аромата я намерен хранить во внешнем источнике следующее (один из приведенных выше):

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

ПОЛОЖЕНИЕ: я хочу написать общий фрагмент кода для итерации и динамического создания productFlavors и sourceSets. Я обобщил sourceSets почти до 90%, и теперь одного блока достаточно для всех вкусов.

Сейчас это выглядит примерно так:

sourceSets {
    [flavor1, flavor2, flavor3 ...].each { AndroidSourceSet ss ->
                ss.assets.srcDirs = ['repo-assets/' + ss.name.split('_')[0]]
                ss.res.srcDirs = ['repo-mipmap/' + ss.name.split('_')[0] , 'repo-strings/' + ss.name]
            }
}

Также хочу сделать то же самое для productFlavors, как указано выше.

ЗАСТРЕЛ НА: получение списка [flavor2, flavor4] в приведенном выше коде из внешнего источника (вместе с несколькими дополнительными полями для каждого варианта, как указано выше).

Я вижу такие методы, как

productFlavors.add()
productFlavors.addAll()

но я не совсем уверен, как их использовать. Поскольку методы доступны, я уверен, что можно сделать то, что я пытаюсь сделать.

Кто-нибудь сделал это и имеет несколько указателей?


person AndroidMechanic - Viral Patel    schedule 29.03.2016    source источник


Ответы (2)


Я, наконец, заставил это работать следующим образом:

Создал пользовательский класс MyFlavor в build.gradle и добавил каждый вариант из CSV-файла в ArrayList из MyFlavor.

class MyFlavor {
    public String flavorname;
    public String basename;
    public String appid;
    public String bannerid;
    public String interstitialid;
    public String category;
}

def ArrayList<MyFlavor> myFlavors = new ArrayList<>();

new File('app/flavors.csv').eachLine {
    String[] values = "$it".split(',');
    MyFlavor f = new MyFlavor();
    f.flavorname = values[0];
    f.basename = values[0].split('_')[0];
    f.appid = values[1];
    f.bannerid = values[2];
    f.interstitialid = values[3];
    if(values[0].contains('_')) f.category= "state"
    else f.category = "country";
    myFlavors.add(f);
}

Затем перебрал ArrayList для динамического создания productFlavors и sourceSets следующим образом:

productFlavors {
    myFlavors.each { MyFlavor f ->
        "$f.flavorname" {
            applicationId = "$f.appid"
            buildConfigField 'String', 'BANNER_ID', "\"" + "$f.bannerid" + "\""
            buildConfigField 'String', 'INTERSTITIAL_ID', "\"" + "$f.interstitialid" + "\""
            buildConfigField 'String', 'CATEGORY', "\"" + "$f.category" + "\""
        }
    }
}
sourceSets {
    myFlavors.each { MyFlavor f ->
        "$f.flavorname" {
            assets.srcDirs = ['repo-assets/' + "$f.basename"]
            res.srcDirs = ['repo-mipmap/' + "$f.basename" , 'repo-strings/' + "$f.flavorname"]
        }

    }
}

Надеюсь, это поможет кому-то. Мне удалось избавиться от 1000 строк кода (потому что у меня много вариантов) и сократить его до 10 строк, которые вы видите здесь.

person AndroidMechanic - Viral Patel    schedule 29.03.2016
comment
Я работаю над приложением с белой меткой, которое раздувает файл gradle. Это решение очень помогает. Спасибо - person muthuraj; 14.12.2017

Для чтения файлов XML и CSV у вас есть все возможности Groovy. Все сценарии Gradle должны быть написаны на Groovy. .each { Type var -> тоже часть этого. Первый результат: https://stackoverflow.com/a/2622965/253468:

Учитывая такой файл CSV:

#name,appId,ad1,ad2,category
flavor1,app.id.flavor1,adunit1324523,adunit2234234,messenger
flavor2,app.id.flavor2,adunit42346451,adunit4562,editor
flavor3,app.id.flavor2.gpe,adunit345351,adunit3545342,messenger

Groovy может загрузить его следующим образом:

import com.android.build.gradle.BaseExtension
import com.android.build.gradle.api.AndroidSourceSet
import com.android.build.gradle.internal.dsl.ProductFlavor
// TODO get a real CSV parser, this is hacky
new File("flavors.csv").splitEachLine(",") { fields ->
    if (fields[0].charAt(0) == '#' as char) return; // skip comments
    def flavorName = fields[0];
    def baseName = flavorName.split('_')[0];
    def appId = fields[1];
    BaseExtension android = project.android // project.getExtensions().getByName('android'); 
    // productFlavors is declared as Collection, but it is a NamedDomainObjectContainer
    // if [flavorName] doesn't work, try .maybeCreate(flavorName) or .create(flavorName)
    ProductFlavor flavor = android.productFlavors[flavorName];
    AndroidSourceSet sourceSet = android.sourceSets[flavorName];
    flavor.applicationId = appId;
    sourceSet.res.srcDirs = [] // clear
    sourceSet.res.srcDir 'repo-mipmap/' + baseName
    sourceSet.res.srcDir 'repo-strings/' + flavorName
}

Типы импортируются для удобочитаемости и завершения кода, вы можете заменить любой тип переменной на def, и он все равно будет работать. Именно эти типы используются при выполнении обычной конфигурации android { ... }. Внутренние типы могут измениться в любое время, на самом деле я работаю с 1.5, возможно, они уже изменились в 2.0.

person TWiStErRob    schedule 29.03.2016
comment
нужно будет создать экземпляры ProductFlavor (класс) и AndroidSourceSet (интерфейс), а затем добавить свойства и, наконец, добавить объект, используя android.productFlavors.add( и то же самое для исходных наборов. Но теперь проблема в том, что конструктор ProductFlavor запрашивает странные параметры, которые я не знаю, где взять. А интерфейс AndroidSourceSet запрашивает 10 секунд реализации метода. - person AndroidMechanic - Viral Patel; 29.03.2016
comment
также попытался создать фиктивный аромат в обычном режиме, а затем назначить его новому экземпляру ProductFlavor. Но это не позволит мне изменить свойства, говоря, что они доступны только для чтения. - person AndroidMechanic - Viral Patel; 29.03.2016
comment
Не нужно создавать их вручную, посмотрите на NamedDomainObjectContainer.maybeCreate вот что происходит, когда вы пишете productFlavors { flavor1 {}} - person TWiStErRob; 29.03.2016
comment
заставил его работать, используя другой подход (опубликовал другой ответ). Но большое спасибо за ваш вклад. Это дало мне направление. - person AndroidMechanic - Viral Patel; 29.03.2016