В одну из последних рассылок Android Weekly попала статья, в которой упомянули интересные особенности организации сборки проекта. После ее прочтения мне захотелось поделиться кое-чем из того, что использую я для настройки сборки Android проекта.


Избавляемся от дублирования кода в ваших build.gradle файлах


Казалось бы простая идея, однако такой подход используется довольно редко.

Предположим, у вас есть несколько модулей в приложении, в каждом из которых необходимо прописать buildToolsVersion. Часто эту задачу решают путем вынесения конкретной версии в ext-переменную.
Помимо этого, можно оптимизировать код, задав это значение только в одном месте.

Напомню о возможности применения кода из другого gradle файла в вашем build.gradle:

apply plugin: 'com.android.application'
apply from: "$buildSystemDir/application.gradle"

А в файле application.gradle уже можно указать нужные версии:

android {
   compileSdkVersion 24
   buildToolsVersion "24.0.1"

   defaultConfig {
       minSdkVersion 15
       targetSdkVersion 24
       testInstrumentationRunner 'android.support.test.runner.AndroidJUnitRunner'
   }


   compileOptions {
       sourceCompatibility JavaVersion.VERSION_1_7
       targetCompatibility JavaVersion.VERSION_1_7
   }
}

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

В отдельный gradle файл можно вынести не только конфигурации android — extension, но и тестовые зависимости, настройку jacoco, findbugs, pmd и т.д. и т.п.

Добавляем свойства в проект


Возможно вы уже заметили переменную “$buildSystemDir”. Она задана в build.gradle файле корневого проекта:

allprojects {
   it.extensions.add("buildSystemDir", "$rootProject.projectDir/buildSystem/")
}

После этого в каждом модуле можно использовать это свойство без всяких префиксов. Что, опять же, чуть-чуть сокращает код ;)

Подпись apk


Тема безопасного хранения ключей и паролей от сертификата — очень интересная, и я буду очень рад, если кто-то подскажет хорошее решение (с ci, безопасное), а пока я приведу свой вариант.

Информация о каждом сертификате хранится в *.properties файле. Он может как присутствовать на машине сборки проекта так и отсутствовать.

sign.gradle:
Properties signProp = new Properties()
try {
   signProp.load(rootProject.file('signForTest.properties').newDataInputStream())

   project.ext {
       forTest = new Object() {
           def storeFile = signProp.get("forTest.storeFile")
           def storePassword = signProp.get("forTest.storePassword")
           def keyAlias = signProp.get("forTest.keyAlias")
           def keyPassword = signProp.get("forTest.keyPassword")
       }
   }
} catch (IOException e) {
   project.ext {
       forTest = new Object() {
           def storeFile = "/"
           def storePassword = ""
           def keyAlias = ""
           def keyPassword = ""
       }
   }
}

android {
   signingConfigs {
       forTest {
           storeFile file(project.ext.forTest.storeFile)
           storePassword project.ext.forTest.storePassword
           keyAlias project.ext.forTest.keyAlias
           keyPassword project.ext.forTest.keyPassword
       }
   }
}

Вот и всё. Осталось применить этот файл в build.gradle нашего приложения и использовать сконфигурированный signingConfig в нашем buildType.

buildSrc


Что же делать, когда в системе сборки нужен полноценный код, а свой плагин писать нет времени? Для этого можно использовать директорию buildSrc.

В корневом каталоге вашего проекта (там где лежит gradlew) создаем новый модуль buildSrc. Этот модуль должен быть обычным java проектом (или groovy или чем-либо ещё), со следующим build.gradle файлом:

apply plugin: "groovy"

repositories {
   mavenCentral()
}

dependencies {
   compile localGroovy()
   compile gradleApi()
}

// START SNIPPET addToRootProject
rootProject.dependencies {
   runtime project(path)
}
// END SNIPPET addToRootProject

Теперь в этом проекте можно создать какой-либо класс Awesome.groovy, а в build.gradle Android модуля этот класс импортировать и использовать.

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

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

Flavor dimensions


Эта возможность для конфигурирования проекта хорошо описана в доках (// tools.android.com/tech-docs/new-build-system/user-guide#TOC-Multi-flavor-variants
). Но, либо о ней не все знают, либо не понимают, в каких случаях ее можно использовать, поэтому я решил об этом рассказать.

Предположим, вам необходимо создать платное и бесплатное приложение. При этом ваше приложение выпускается для телевизоров, планшетов и телефонов. Более того, ваше приложение публикуется в разных маркетах.

Добавим в свой build.gradle следующий код:

android {
   flavorDimensions "device", "paid", "market"

   productFlavors {
       tv {
           dimension 'device'
       }
       tablet {
           dimension 'device'
       }
       phone {
           dimension 'device'
       }

       free {
           dimension 'paid'
       }
       premium {
           dimension 'paid'
       }

       google {
           dimension 'market'
       }

       amazon {
           dimension 'market'
       }
   }
}

Объявив 7 flavor-ов в трех разных группах вы получили целых 12 различных вариантов приложения. Добавим сюда еще buildTypes, и мы получим огромное количество apk-шек.

phoneFreeGoogleDebug
phoneFreeGoogleRelease
phoneFreeAmazonDebug
phoneFreeAmazonDebug
phonePremiumGoogleDebug
…. и т.д.

Для каждого объявленного вами flavor создаются свои sourceSet. К примеру, если вы выбрали флавор phoneFreeAmazonDebug будут использованы следующие sourceSets:

src/phoneFreeAmazon
src/phoneFree
src/phoneAmazon
src/freeAmazon
src/phone
src/free
src/amazon

Таким образом, открываются широкие возможности для кастомизации сборок.

Указываем minSdkVersion для buildType


Для ускорения сборки приложения на этапе разработки часто рекомендуют создать отдельный flavor “develop” и указать для него minSdkVersion = 21. Однако, это не очень удобно, и часто хочется указать этот параметр в buildType debug. Изначально, плагин сборки не позволяет этого сделать, однако, при необходимости, эту проблему можно решить следующим хайком.

Для нужного buildType необходимо добавить ext переменную:

…
buildTypes {
...
   debug {
... 
       ext.minSdkVersion = 21
   }
}

Ниже необходимо добавить следующий код:

preBuild.doFirst {
   android.applicationVariants.all {
       if (it.buildType.hasProperty("minSdkVersion")) {
           int i = it.buildType.ext.minSdkVersion;
           it.mergedFlavor.setMinSdkVersion(new com.android.builder.core.DefaultApiVersion(i))
       }
   }
}

Теперь для всех ваших flavor в debug сборке minSdkVersion будет 21. Однако, тут есть жесткая завязка на внутренности плагина, поэтому при обновлении версии плагина что-то может поломаться. Поэтому, не могу рекомендовать использовать этот хак — выбор за вами.

Вместо заключения хочу отметить, что Gradle — очень мощный инструмент для сборки проекта. Если вы уделяете много внимания качеству кода вашего приложения, то не забывайте приводить в порядок и код в build.gradle файлах.
Поделиться с друзьями
-->

Комментарии (7)


  1. petrovichtim
    20.10.2016 14:38
    +1

    Спасибо, ничего нового не узнал.


  1. slartus
    20.10.2016 15:17
    +1

    именуем релизный апк:

    applicationVariants.all { variant ->
            variant.outputs.each { output ->
                if (variant.buildType.name == "release") {
                    def vCode = variant.versionCode
                    if (vCode < 10)
                        vCode = "0" + vCode
    
                    def date = new Date()
                    def formattedDate = date.format('yyyyMMddHHmm')
    
                    output.outputFile = new File(
                            output.outputFile.parent,
                            variant.productFlavors[0].name + "_c${vCode}_" + formattedDate + ".apk")
                }
            }
        }
    


  1. mr-cpp
    20.10.2016 18:36

    Статья неплохая. Но мне кажется мало кто будет искать справку по gradle на хабре. Более перспективным для этого документация на stackoverflow. Вам не кажется?
    P.S. у меня там больше рейтинг, я бы вам обязательно плюс поставил)


    1. HotIceCream
      20.10.2016 20:50

      Спасибо, не знал об этом новом разделе. К сожалению, я не настолько уверен в своем английском, что бы писать англоязычные статьи.


  1. tolkkv
    20.10.2016 22:52

    allprojects {
       it.extensions.add("buildSystemDir", "$rootProject.projectDir/buildSystem/")
    }
    


    it.extentsions.add? То ли я ничего не понимаю то ли это делается куда проще — project.ext, как советуют в документации

    ext {
       buildSystemDir = "$rootProject.projectDir/buildSystem/"
    }
    
    task getMyProp << { logger.quiet "i am prop  buildSystemDir: $buildSystemDir"  }
    


    И это распространяется на подпроекты тоже…
    Откуда вы взяли такую страшненькую конструкцию?


    1. HotIceCream
      20.10.2016 23:16

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

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


  1. hyperax
    27.10.2016 22:37
    +1

    Применяю следующий способ подписи APK файлов

    Создаем в папочку
    project/app/scripts

    Туда кладем дополнительные gradle-скрипты и, в частности, скрипт для подписи приложения:
    release.gradle

    android {
        signingConfigs {
            release {
                storeFile file("my_keystore.jks")
                storePassword "some_pass"
                keyAlias "alias_name"
                keyPassword "another_pass"
            }
        }
        buildTypes {
            release {
                signingConfig signingConfigs.release
            }
        }
    }
    


    Далее, подключаем скрипты из папочки scripts в build.gradle для приложения (/app/build.gradle)
    fileTree('scripts').each { apply from: "${it}" }
    


    Для безопасности добавляем соотв. скрипт и jks файл в git исключения:
    # release sign info
    *.jks
    app/scripts/release.gradle