Managing Build and Deployment
1. Creating Signed APK
Example: Signing an APK with Gradle
android {
signingConfigs {
release {
storeFile file('my-release-key.keystore')
storePassword 'password'
keyAlias 'my-key-alias'
keyPassword 'password'
}
}
buildTypes {
release {
signingConfig signingConfigs.release
}
}
}
| Property | Description | Values |
|---|---|---|
| storeFile | Path to the keystore file | file('path/to/keystore') |
| storePassword | Password to access the keystore | 'password' |
| keyAlias | Alias of the key in the keystore | 'my-key-alias' |
| keyPassword | Password for the specific key | 'password' |
Note: Keystore files should be kept secure and never committed to version control.
Warning: Losing the keystore or passwords means you cannot update your app on the Play Store.
Steps to Create a Signed APK
- Create or obtain a keystore file
- Configure signing in
build.gradle - Run
./gradlew assembleRelease - Find the signed APK in
app/build/outputs/apk/release/app-release.apk
2. Building Release Builds
| Property | Value | Description |
|---|---|---|
| android.buildTypes.release | buildType |
Defines the release build configuration |
| android.buildTypes.release.minifyEnabled | true |
Enables code shrinking and obfuscation |
| android.buildTypes.release.proguardFiles | proguard-rules.pro |
Specifies ProGuard configuration file |
| android.buildTypes.release.signingConfig | signingConfigs.release |
Applies signing configuration for release builds |
Example: Configure Release Build
android {
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.release
}
}
}
Example: Signing Config
signingConfigs {
release {
storeFile file('my-release-key.jks')
storePassword 'password'
keyAlias 'my-key-alias'
keyPassword 'password'
}
}
3. Using App Bundles
App Bundles are a new publishing format that includes all your app's compiled code and resources, but allows Google Play to generate and serve optimized APKs for each user's device.
| Feature | Description | Notes |
|---|---|---|
| Base Module | Contains shared code and resources used across all variants. | Required for every app bundle |
| Feature Modules | Optional modules that can be included in specific variants. | Supports dynamic delivery |
| Split by ABI | Generates APKs for specific CPU architectures. | Reduces app size |
| Split by Language | Generates APKs for specific locales. | Supports localization |
| Split by Screen Density | Generates APKs for specific screen densities. | Optimizes resource usage |
Example: Configuring App Bundle in Gradle
android {
bundle {
language {
enableSplit = true
}
density {
enableSplit = true
}
abi {
enableSplit = true
}
}
}
Note: App Bundles are required for publishing to Google Play as of August 2021.
Warning: Ensure all feature modules are properly configured to avoid runtime errors.
Benefits of App Bundles
- Smaller app sizes for users
- Reduced storage usage on devices
- Improved performance through optimized delivery
- Support for dynamic feature modules
4. Configuring Versioning
| Property | Value | Description |
|---|---|---|
| versionCode | 1 |
Integer value used by Google Play to identify the version of the app. Must be incremented for each release. |
| versionName | "1.0" |
String value displayed to users in the Play Store and app info. Should follow semantic versioning (e.g., "1.0.0"). |
Example: Setting version in build.gradle.kts
android {
compileSdk = 34
defaultConfig {
applicationId = "com.example.myapp"
minSdk = 21
targetSdk = 34
versionCode = 1
versionName = "1.0"
}
}
Note:
versionCode must be an integer and strictly increasing for each release, while versionName is a user-facing string that can include labels like "beta" or "rc1".5. Setting Up Continuous Integration
Example: GitHub Actions Workflow
name: Android CI
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: "17"
distribution: "temurin"
- name: Build with Gradle
run: ./gradlew build
- name: Run tests
run: ./gradlew test
| Step | Description | Notes |
|---|---|---|
| checkout | actions/checkout@v3 |
Checks out the repository code |
| setup-java | actions/setup-java@v3 |
Sets up the Java environment (JDK 17) |
| gradle build | ./gradlew build |
Builds the Android project |
| test | ./gradlew test |
Runs unit and instrumentation tests |
Note: Ensure
gradle.properties has org.gradle.daemon=false for CI environments.
CI Tools Comparison
- GitHub Actions: Integrated with GitHub, easy to set up, good for open-source projects
- GitLab CI: Built-in with GitLab, good for private repositories
- CircleCI: Fast builds, good for complex workflows
- Jenkins: Highly customizable, self-hosted, more complex setup
6. Using Gradle Tasks
| Task | Description | Usage |
|---|---|---|
| assemble | Assembles the outputs of a project | ./gradlew assemble |
| build | Runs the assemble and test tasks | ./gradlew build |
| clean | Deletes the build directory | ./gradlew clean |
| test | Runs the unit tests | ./gradlew test |
| lint | Runs the lint checks | ./gradlew lint |
Example: Task with Dependencies
task customTask {
dependsOn 'clean', 'assemble'
doLast {
println 'Custom task executed after clean and assemble'
}
}
7. Optimizing APK Size
Example: Enable ProGuard/R8
android {
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
| Option | Values | Notes |
|---|---|---|
| minifyEnabled | true |
Enables code shrinking and obfuscation |
| shrinkResources | true |
Removes unused resources from the APK |
| useProguard | false |
Use R8 instead of ProGuard (default since AGP 3.4) |
Note: ProGuard/R8 can significantly reduce APK size by removing unused code and resources.
Example: Exclude Dependencies from Shrinking
-keep class com.example.library.** { *; }
-keepnames class com.example.library.**
APK Size Optimization Tips
- Use
minifyEnabledandshrinkResources - Enable resource crunching for PNGs
- Use vector drawables where possible
- Avoid including large libraries or assets
- Use
buildTypesto configure optimizations per environment
8. Configuring Multi-APK Support
Example: Basic Multi-APK Configuration
android {
bundle {
language {
enableSplit = true
}
density {
enableSplit = true
}
abi {
enableSplit = true
}
}
}
| Property | Values | Description |
|---|---|---|
| language.enableSplit | true / false |
Enables or disables language splits |
| density.enableSplit | true / false |
Enables or disables density splits |
| abi.enableSplit | true / false |
Enables or disables ABI splits |
Note: Multi-APK support is configured via the
bundle block in Gradle.Warning: Enabling splits can increase app size if not managed properly.
Key Considerations
- Language splits reduce app size by delivering only the necessary language resources.
- Density splits deliver optimized graphics for different screen densities.
- ABI splits allow targeting specific processor architectures (e.g., arm64-v8a, armeabi-v7a).
9. Testing Release Builds
Testing release builds ensures your app functions correctly in a production-like environment before publishing. Android provides several tools and approaches to validate release builds.
| Aspect | Description | Tools/Methods |
|---|---|---|
| Build Type | Release builds are optimized for performance and size. | release build variant in Gradle |
| ProGuard/R8 | Code shrinking, optimization, and obfuscation. | Enabled by default in release builds |
| Signing | Release builds must be signed with a keystore. | signingConfig in Gradle |
Example: Configure Release Build Signing
android {
buildTypes {
release {
signingConfig signingConfigs.release
}
}
signingConfigs {
release {
storeFile file('my-release-key.keystore')
storePassword 'password'
keyAlias 'my-key-alias'
keyPassword 'password'
}
}
}
Note: Release builds should be tested on physical devices to ensure compatibility with various hardware configurations and Android versions.
Workflow: Testing Release Builds
- Build the release APK or AAB using
./gradlew assembleRelease - Install the release build on a physical device or emulator
- Run functional and performance tests using Android Test Orchestrator or Espresso
- Analyze crash reports using Firebase Crashlytics or similar tools
Comparison: Debug vs Release Builds
- Debug: Includes debugging symbols, logs, and is not optimized
- Release: Optimized for size and performance, no debug symbols
10. Publishing to Play Store
| Step | Description | Action |
|---|---|---|
| 1 | Create a signing key | keytool -genkey -v -keystore my-release-key.keystore -alias my-key-alias -keyalg RSA -keysize 2048 -validity 10000 |
| 2 | Configure gradle.properties | |
| 3 | Update build.gradle (app level) | |
| 4 | Build the release APK | ./gradlew assembleRelease |
| 5 | Upload to Google Play Console | Go to Google Play Console, create a new app, upload the APK or AAB file |
Note: Ensure the app version code is incremented before each release.
Warning: Never share your keystore file or passwords publicly.
11. Using Play Feature Delivery
Play Feature Delivery (PFD) allows you to split your Android app into dynamic features and deliver them on-demand to users. This helps reduce the initial app size and improves download times.
| Feature | Description | Notes |
|---|---|---|
| Base Module | Contains core app functionality and is always downloaded. | Required for all apps using PFD |
| Dynamic Feature Module | Optional module containing additional features delivered on-demand. | Can be installed at runtime |
| Delivery Type | Defines how the feature is delivered to the user. | Can be install-time or on-demand |
Example: Using Feature Module in Base App
val featureModule = FeatureManager.getInstance(this)
featureModule.loadFeature("feature-module")
Note: Ensure that the feature module is declared in
build.gradle under dynamicFeatures for it to be recognized by the Play Store.
Warning: On-demand features must be declared in the app's manifest or they will not be available for download.