Wstęp
System budowania aplikacji Android
kompiluje zasoby, kod źródłowy oraz pakiety do pliku w formacie APK
(Android Application Package
), który może być testowany, podpisany i dystrybuowany. W tym celu Android Studio
używa zaawansowanego zestawu narzędzi Gradle
wraz z dedykowanym Android Gradle plugin
pozwalających na zarządzanie i automatyzację tego procesu dzięki czemu każda konfiguracja może definiować własne zasady budowania projektu. Gradle
oraz plugin
działają niezależnie od Android Studio
co pozwala na kompilacje bez udziału środowiska programistycznego, np. przez wiersz poleceń.
Proces
Budowanie aplikacji angażuje wiele narzędzi i uruchamia różne procesy umożliwiające konwersje projektu do APK
. Typowy proces przebiega następująco. Na początku kompilator konwertuje kod źródłowy do plików DEX
(Dalvik Executable
) zawierających kod bajtowy, a pozostałe pliki i zależności do skompilowanych zasobów. Następnie APK Packager
łączy i optymalizuje pliki DEX
i skompilowane zasoby do jednego pliku APK
podpisując go kluczem debug
lub release
z keystore
. Powstały plik APK
jest gotowy do instalacji, debugowania czy testowania.
Konfiguracja
Dokonując konfiguracji budowania projektu można wyróżnić kilka aspektów wpływających na wyjściowy rezultat. buildTypes
definiuje właściwości dla wydań (np. zaciemnienie release), productFlavors
reprezentuje różne wersje aplikacji (np. płatna, darmowa, demo). buildVariants
jest konkretnym wariantem budowania wynikającym z połączenia wybranych buildTypes
i productFlavors
, które mogą używać współdzielonych jak i prywatnych zasobów. Wartości wpisów w AndroidManifest
mogą się różnić w zależności od wariantu budowania (np. inna nazwa czy różne minSdk). System budowania zarządza także wpisami lokalnych i zdalnych zależności dependencies
dzięki czemu nie ma potrzeby ręcznego szukania, pobierania i kopiowania pakietów zależności do projektu. Ponadto umożliwia ustawienie podpisu autentykacyjnego i zasad bezpieczeństwa ProGuard
oraz wspiera budowanie wielu APK
.
Pliki
Informacje nt konfiguracji znajdują sie w kilku plikach projektu należących do danego modułu. Używają one DSL
(Domain Specific Language
) do opisania i manipulowania logiką przy pomocy Groovy
(dynamicznego języka dla JVM
). Android Gradle plugin
dostarcza większość potrzebnych elementów DSL
w związku z czym nie jest wymagana wiedza programowania w Groovy
.
settings.gradle
deklaruje moduły, które powinny wziąć udział w procesie budowania projektu
include ':app', ':module1', ':module2'
build.gradle
znajdujący się w głównym katalogu definuje konfigurację wspólną dla wszystkich modułów w projekcie, np. repozytoria i wersja Sdk
// Configure the repositories and dependencies needed for Gradle itself in buildscript block
buildscript {
//Define some shared properties
ext {
compileSdkVersion = 28
supportLibVersion = "28.0.0"
kotlin_version = '1.3.31'
}
// Pass repositories for Gradle to search and download dependencies
repositories {
google()
jcenter()
}
// Pass dependencies for Gradle to build project
dependencies {
classpath 'com.android.tools.build:gradle:3.4.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
// Configure the global repositories and depenendencies used by all modules
allprojects {
repositories {
google()
jcenter()
}
}
// Create some tasks if needed
task clean(type: Delete) {
delete rootProject.buildDir
}
build.gradle
znajdujący się w każdym module pozwala na specyficzną konfiguracje dla danego modułu uzupełniając lub nadpisując definicję build.gradle
projektu, np. zależności, wtyczki czy konfiguracja wersji i wariantów budowanej paczki APK
//apply plugins to build and makes android block available to build options
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
// Configure Android specific build options
android {
// Define some compile and build properties
compileSdkVersion 28
// Specify default settings and entries for all build variant
defaultConfig {
applicationId "pl.androidcode.app"
minSdkVersion 21
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
// Configure multiple build types like apply Proguard for release and make debug debuggable
buildTypes {
release {
minifyEnabled true
debuggable false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
debug {
minifyEnabled false
debuggable true
signingConfig signingConfigs.debug
}
}
// Configure multiple product flavors, override defaultConfig block settings
// At least one flavorDimensions must be declared, they combine multiple flavors
flavorDimensions "version"
productFlavors {
free {
dimension "version"
applicationIdSuffix ".free"
versionNameSuffix "-free"
}
paid {
dimension "version"
applicationIdSuffix ".paid"
versionNameSuffix "-paid"
}
}
// Use filter to disable some build variant
variantFilter { variant ->
def flavors = variant.flavors*.name
def types = variant.buildType*.name
if (types.contains("debug") && flavors.contains("paid")) {
setIgnore(true)
}
}
}
// Provide dependencies needed only for module itself
dependencies {
//local binaries
implementation fileTree(dir: 'libs', include: ['*.jar'])
//remote binaries
implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version'
implementation 'androidx.appcompat:appcompat:1.0.2'
implementation 'androidx.core:core-ktx:1.0.2'
//add build type or flavor prefix to use implementation only for this variant
paidImplementation 'com.android.billingclient:billing:2.0.1'
//unit and instrumental tests
//notice that test and androidTest are source sets like any other build variant
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
}
gradle.properties
określa ustawienia Gradle
dla całego projektu, np. maksymalna wielkość stosu deamona czy użycie artefaktów AndroidX
org.gradle.jvmargs=-Xmx1536m
android.useAndroidX=true
android.enableJetifier=true
kotlin.code.style=official
local.properties
konfiguruje lokalne właściwości środowiska dla systemu kompilacji, np. ścieżka instalacji Sdk
sdk.dir=C\:\\Android\\Sdk
Źródła
Aby budowane warianty aplikacji zadeklarowane w build.gradle rzeczywiście różniły się implementacją należy stworzyć dla nich lokalizacje odpowiadające nazwie wariantu oraz umieścić tam specyficzny kod źródłowy i zasoby. Dla zdefiniowanych powyżej wariantów ich zbiory źródeł mogłyby znajdować się w: src/debug
, src/release
, src/debugFree
, src/debugPaid
, src/releaseFree
, src/releasePaid
. Zawartość src/main
jest traktowana jako domyślna i współdzielona przez wszystkie warianty kompilacji natomiast źródła konkretnych wariantów nadpisują implementację bazową zgodnie z zasadą priorytetów (build variant > build type > build flavor > main > library
).
Optymalizacja
Tworząc konfiguracje budowania aplikacji należy rozważyć optymalizację czasu procesu kompilacji oraz rozmiaru pliku wyjściowego. Budowanie wielu APK
dedykowanych pod konkretne architektury czy gęstości ekranów pozwala zmniejszyć rozmiar poprzez załączenie tylko wymaganych zasobów. Jednakże taki proces znacząco wydłuża czas całkowitej kompilacji w związku z czym warto wyłączyć niepotrzebne wersje oraz zawężyć wariant deweloperski. Ponadto zastosowanie zasad ProGuard
także umożliwia redukcje rozmiaru przy jednoczesnym spowolnieniu kompilacji. Użycie statycznych zależności, trybu offline, cache czy Instant Run
przyśpiesza budowanie projektu. W przypadku wersji produkcyjnej przeważnie dąży się przede wszystkim do optymalizacji rozmiaru natomiast w wersji deweloperskiej do optymalizacji czasu kompilacji.
android {
//...
flavorDimensions "stage", "version"
productFlavors {
//...
//add developer and production flavor with new dimension
dev {
//...
dimension "stage"
resConfigs "pl", "xxhdpi" //attach only pl resources
}
prod {
//...
dimension "stage"
}
}
// Configure different APK builds that each contains only needed code and resources for density and abi
// Notice that every build must have unique version code for store
splits {
density {
enable true
exclude "ldpi"
}
abi {
enable gradle.startParameter.taskNames.any { it.contains("release") }
universalApk false
}
}
}