An end-to-end Android Software Development Lifecycle (SDLC) and release management specification covering build automation, App Bundle compilation, cryptographic signing, and Google Play Store deployment pipelines.
This repository documents the production release pipelines for three distinct Android applications: Branching Realms, Scratchboard Art Studio, and Homophones. These projects represent two distinct mobile compilation ecosystems:
- Native Kotlin / Modern Android Stack (
BranchingRealms): A native application built with Jetpack Compose, Kotlin Coroutines, Dagger Hilt, and Room, managed by Android Gradle Plugin (AGP) 8.9.3. - Game Engine Native Export (
scratchboard-art): A 2D/3D graphics application built on Godot Engine 4.6 (Mobile Vulkan renderer), compiling through a custom Godot Android Gradle template (AGP 8.6.1) that binds C++ engine libraries and Play Asset Delivery modules. - Cryptographic Key Management Pipeline (
Homophones): An isolated release signing infrastructure maintaining production keystores and signing identity controls separated from active application codebases.
The deployment architecture standardizes build automation, target SDK alignment, permission scoping, and store ingestion pipelines across both native JVM and native C++/NDK targets.
+-----------------------------------------------------------------------------------+
| Source Code Repositories |
| |
| +---------------------------+ +------------------------+ +------------------+ |
| | BranchingRealms | | scratchboard-art | | Homophones | |
| | (Native Kotlin + Compose) | | (Godot 4.6 Engine) | | (Isolated Keys) | |
| +-------------+-------------+ +-----------+------------+ +--------+---------+ |
+----------------|----------------------------|------------------------|------------+
| | |
v v |
+----------------------------------------------------------+ |
| Compilation & Build Engine | |
| | |
| - AGP 8.9.3 / Kotlin 2.0.0 - AGP 8.6.1 / NDK 28 | |
| - KSP Schema Processing - Play Asset Delivery | |
| - Compose BOM Integration - C++ ABI Filtering | |
| - R8 Code & Resource Shrink - Resource Optimization | |
+-----------------------------+----------------------------+ |
| |
v v
+-----------------------------------------------------------------------------------+
| Cryptographic Signing Layer |
| |
| - PKCS12 Production Keystores (homophones_keystore.jks, Release Keystores) |
| - Environment Variable Credential Injection (Air-Gapped from Version Control) |
| - R8 De-obfuscation Mapping Generation (mapping.txt) |
+-------------------------------------+---------------------------------------------+
|
v
+-----------------------------------------------------------------------------------+
| Google Play Release Progression |
| |
| 1. Internal App Sharing --> 2. Closed Testing Track --> 3. Staged Production |
| (Smoke & IAP Sandbox) (20 Testers / 14 Days) (10% -> 100% Roll) |
| |
| - Google Play App Signing (Split APK Generation per ABI / Density / Language) |
| - Android Vitals Monitoring (Crash Rate < 1.09%, ANR Rate < 0.47%) |
| - Firebase Crashlytics Real-Time Telemetry & De-obfuscated Stack Traces |
+-----------------------------------------------------------------------------------+
- Build Systems & Compilers: Android Gradle Plugin (AGP 8.9.3, AGP 8.6.1), Gradle 8.11+ / 8.7, Kotlin Compiler (2.0.0, 2.1.20), Java Development Kit (JDK 17 LTS), Android NDK (28.1.13356709), Kotlin Symbol Processing (KSP 2.0.0-1.0.24), KAPT.
- Android SDK & Runtime: Android SDK Platform 35 (Android 15), Build Tools 35.0.1, AndroidX Jetpack Compose BOM (2025.05.01), Material 3 (1.3.2), Room Persistence Library (2.7.1), Android Core Splashscreen (1.0.1).
- Packaging & Delivery: Android App Bundle (AAB), Play Asset Delivery (
com.android.asset-packinstall-time delivery), ABI Filters (arm64-v8a), Legacy Native Library Packaging controls. - Security & Shrinking: Java KeyStore (PKCS12 standard, RSA 2048/4096-bit asymmetric keys), ProGuard / R8 code shrinking and resource stripping, signature-protected custom permissions.
- Store Compliance & Telemetry: Google Play Console, Google User Messaging Platform (UMP SDK 3.2.0), Google Mobile Ads SDK (24.3.0), Google Play In-App Billing Client, Firebase BoM (33.14.0), Firebase Crashlytics (19.4.3), Google Play Android Vitals.
Google Play requires target API level compliance to ensure applications operate under modern Android security and execution sandboxes. The build configurations across this repository target Android 15 (API level 35).
| Application | Project Type | Compile SDK | Target SDK | Min SDK | AGP Version | Language Runtime |
|---|---|---|---|---|---|---|
| Branching Realms | Native Android | 35 |
35 |
26 (Android 8.0) |
8.9.3 |
Kotlin 2.0.0 / Java 17 |
| Scratchboard Art Studio | Godot Engine Port | 35 |
35 |
24 (Android 7.0) |
8.6.1 |
Kotlin 2.1.20 / NDK 28 / Java 17 |
| Homophones | Production Keystore | 35 |
35 |
26 (Android 8.0) |
8.x |
JVM Bytecode / Java 17 |
- Minimum SDK 26 (Branching Realms): Establishes baseline support for Java 8 language APIs natively, adaptive icons, and background execution limits without multidex complexity or backward compatibility overhead.
- Minimum SDK 24 (Scratchboard Art Studio): Accommodates low-level Vulkan graphics drivers and OpenGL ES 3.0 requirements across older Android devices while supporting modern 64-bit ARM architectures.
- Target SDK 35: Complies with Google Play's policy requiring new apps and updates to target Android 14 (API level 34) or Android 15 (API level 35), enforcing strict runtime permissions, foreground service restrictions, and package visibility limits.
Dependency graphs can cause compilation failures when mixing modern compiler plugins, annotation processors, and third-party SDKs. The native and engine build scripts resolve these issues via strict dependency governance.
BranchingRealms/app/build.gradle.kts uses Gradle Version Catalogs (gradle/libs.versions.toml) combined with Google BOMs to prevent version drift:
dependencies {
// Firebase BoM controls versions across Auth, Firestore, Analytics, Crashlytics, and Messaging
implementation(platform(libs.firebase.bom)) // 33.14.0
implementation(libs.firebase.auth.ktx)
implementation(libs.firebase.firestore)
implementation(libs.firebase.analytics.ktx)
implementation(libs.firebase.crashlytics.ktx)
implementation(libs.firebase.messaging)
// Compose BoM standardizes UI library versions
implementation(platform(libs.androidx.compose.bom)) // 2025.05.01
implementation(libs.androidx.ui)
implementation(libs.androidx.material3)
implementation(libs.androidx.navigation.compose)
}To prevent metadata mismatch crashes between KAPT (used by Dagger Hilt 2.49) and KSP (used by Room 2.7.1), the Gradle configuration enforces a unified Kotlin compiler version across all transitive dependencies:
configurations.all {
resolutionStrategy.eachDependency {
if (requested.group == "org.jetbrains.kotlin") {
useVersion("2.0.0")
because("Avoid kapt breaking due to Kotlin metadata version mismatch")
}
}
}scratchboard-art/android/build/build.gradle compiles native C++ libraries alongside Java wrappers. To resolve collisions when third-party libraries ship duplicate C++ runtimes, the build configuration enforces a strict resolution rule:
packagingOptions {
pickFirst 'lib/x86/libc++_shared.so'
pickFirst 'lib/x86_64/libc++_shared.so'
pickFirst 'lib/armeabi-v7a/libc++_shared.so'
pickFirst 'lib/arm64-v8a/libc++_shared.so'
}The script cleans conflicting vendor native libraries during the MergeNativeLibsTask execution phase, removing redundant OpenXR vendor binaries (libopenxr_loader.so) before packaging.
Scratchboard Art Studio isolates large graphic assets from the main application binary through a dedicated Gradle asset pack module (assetPackInstallTime/build.gradle):
plugins {
id 'com.android.asset-pack'
}
assetPack {
packName = "assetPackInstallTime"
// Install-time asset delivery configuration
deliveryType = "install-time"
}This configuration packages the game assets directly into the Android App Bundle (AAB), downloading them automatically during initial store installation without requiring post-install HTTP downloads or violating Play Store size limits.
The release pipeline enforces a separation between local development artifacts and store deployment packages:
[Local Development]
./gradlew assembleDebug
│
▼
Universal APK (android_debug.apk) ──> adb install (Local Test Devices / Emulators)
[Production Deployment]
./gradlew bundleRelease
│
▼
Android App Bundle (.aab) ──> Google Play Console Ingestion
│
▼
Google Play App Signing (Store Key)
│
┌─────────────────────────┼─────────────────────────┐
▼ ▼ ▼
Base Split APK Config Split APKs Asset Pack Split
(arm64-v8a native code) (xxhdpi / en-US / etc.) (assetPackInstallTime)
- Local Development (APKs): Running
gradlew assembleDebugorassembleStandardDebugproduces unoptimized, debug-signed, universal APKs (android_debug.apk). These contain all ABIs, resource densities, and unstripped symbols, facilitating fast deployment via ADB to local hardware. - Production Compilation (AABs): Executing
gradlew bundleReleaseor exporting via Godot's export pipeline produces a single.aabfile (Scratchboard Art.aab,app-release.aab). - Play Feature Delivery & Hardware-Targeted Splits: Google Play ingests the AAB, verifies its cryptographic signature, and generates optimized split APKs tailored to each user's specific hardware:
- ABI Split: Distributes only
arm64-v8anative code to 64-bit devices, excluding unusedarmeabi-v7aorx86_64binaries. - Screen Density Split: Serves only the target device's drawable bucket (e.g.,
xxhdpi), stripping unneeded densities. - Language Split: Delivers strings matching the user's system locale.
- Install-Time Asset Packs: Delivers the game assets packaged in
assetPackInstallTimedirectly alongside the base APK during installation.
- ABI Split: Distributes only
This compilation strategy reduces the user download footprint by 30% to 60% compared to monolithic APKs.
Android requires all installed packages to carry a valid cryptographic signature. The repository separates debug development certificates from production release keys.
+------------------------------------------------------------------------------------+
| Keystore Architecture |
| |
| [Development Environments] [Production Release Pipeline] |
| ~/.android/debug.keystore C:\Dev\Android\Homophones\ |
| - Insecure default password homophones_keystore.jks |
| - Auto-generated RSA certificate - Air-gapped PKCS12 production keystore |
| - Binds only to debug builds - SHA-256 certificate fingerprint |
| - Credentials injected via environment |
| - Applied to release bundles exclusively |
+------------------------------------------------------------------------------------+
- Debug Signing: Development builds utilize standard debug keystores automatically created by AGP, using known, public credentials (
android / androiddebugkey). These keys never touch production release builds. - Production Keystores (
homophones_keystore.jks): Release signing relies on dedicated PKCS12 key stores holding 2048-bit or 4096-bit RSA private keys with SHA-256 signatures. TheHomophonesproject isolates its production keystore from open version control. - Credential Protection: Build scripts avoid hardcoding passwords or file locations. Release signing configurations query environment variables or local, git-ignored properties files at build time:
signingConfigs { release { File keystoreFile = new File(getReleaseKeystoreFile()) if (keystoreFile.isFile()) { storeFile keystoreFile storePassword getReleaseKeystorePassword() keyAlias getReleaseKeyAlias() keyPassword getReleaseKeystorePassword() } } } - Google Play App Signing: The production pipeline uses Google Play App Signing. The local keystore signs the bundle as an Upload Key. Upon ingestion, Google Play validates the upload certificate, strips the upload signature, and signs the distributed split APKs with the master app signing key stored within Google's cloud key management infrastructure.
Release builds activate R8 to shrink code, strip unused resources, and obfuscate identifiers.
In BranchingRealms/app/build.gradle.kts:
buildTypes {
release {
isMinifyEnabled = true
isShrinkResources = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}In scratchboard-art/android/build/gradle.properties:
android.enableResourceOptimizations=true- Dead Code Elimination (Tree Shaking): Traverses the entry points declared in
AndroidManifest.xml(Activities, Services, Receivers) and prunes unreachable classes, interfaces, methods, and fields introduced by large dependencies like Firebase, Hilt, and Room. - Resource Pruning: Removes unreferenced resources (XML files, drawables, strings) from packaged binaries after code shrinking identifies unused identifiers.
- Identifier Obfuscation: Renames remaining classes, methods, and member variables to short, non-descriptive names (
a,b,c), reducing DEX file size and preventing decompilation.
Because R8 obfuscates method names and line numbers, stack traces received from field crashes are unreadable without the corresponding mapping file. The build pipeline integrates the Firebase Crashlytics Gradle plugin (com.google.firebase.crashlytics):
- During
./gradlew bundleRelease, R8 outputs an obfuscation translation map tobuild/outputs/mapping/release/mapping.txt. - The Crashlytics plugin reads
mapping.txtand uploads it to Firebase and Google Play Console. - Incoming crash reports are automatically re-traced to original Kotlin source files and line numbers in real time.
Google Play developer policies strictly regulate the permissions apps can request. Unnecessary access to sensitive data (location, contacts, storage, SMS, camera) leads to store rejection. The manifests across both native and Godot projects enforce a least-privilege architecture.
In scratchboard-art/export_presets.cfg, every single dangerous Android permission is explicitly set to false. The application requests zero access to:
- Location (
access_fine_location=false,access_coarse_location=false) - External Storage (
read_external_storage=false,write_external_storage=false,manage_external_storage=false) - Camera & Microphone (
camera=false,record_audio=false) - Personal Data (
read_contacts=false,read_calendar=false,read_sms=false)
The resulting merged manifest (scratchboard-art/android/build/build/intermediates/merged_manifests/standardDebug/processStandardDebugManifest/AndroidManifest.xml) permits only network communication required for Google Mobile Ads and Google Play Billing:
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />In BranchingRealms/app/src/main/AndroidManifest.xml and its merged release manifest:
- Notification Scoping (Android 13+ / API 33): Declares
android.permission.POST_NOTIFICATIONSexplicitly, requesting runtime consent before posting Firebase Cloud Messaging alerts. - Broadcast Isolation (Android 14+ / API 34): Declares a custom signature-protected permission to prevent unauthorized inter-app communication with internal receivers:
<permission android:name="com.example.branchingrealms.RUNTIME_RECEIVER_NOT_EXPORTED_PERMISSION" android:protectionLevel="signature" /> <uses-permission android:name="com.example.branchingrealms.RUNTIME_RECEIVER_NOT_EXPORTED_PERMISSION" />
- Haptic Feedback: Requests
android.permission.VIBRATEto support tactile UI interactions without touching user privacy boundaries. - Package Visibility (
<queries>): Adheres to Android 11+ package visibility rules by explicitly declaring intent queries for HTTPS browsing, Custom Tabs, and basic telephony, preventing broad package enumeration.
Neither application requests legacy external storage permissions.
- In
scratchboard-art:android:requestLegacyExternalStorage="false" android:allowBackup="false" android:hasFragileUserData="false"
- In
BranchingRealms: Data persistence is restricted to internal sandboxed directories using Room and Jetpack DataStore. Backup behavior is controlled via XML declarations:This setup prevents sensitive authentication tokens, database encryption keys, or private user preferences from leaking into unencrypted cloud backups or device-to-device migration transfers.android:fullBackupContent="@xml/backup_rules" android:dataExtractionRules="@xml/data_extraction_rules"
Exported activities represent exposed entry points that other applications can invoke via explicit or implicit intents.
- In
BranchingRealms, entry points (SplashActivity,MainActivity,ConsentActivity) declare explicitandroid:exportedvalues, satisfying Android 12+ security requirements. Internal services likeMyFirebaseMessagingServicesetandroid:exported="false". - In
scratchboard-art, the main engine activity (.GodotApp) setsandroid:exported="false". Launch execution is routed through an explicit activity alias:This pattern encapsulates internal engine states while exposing only a controlled launcher interface to the Android OS.<activity android:name=".GodotApp" android:exported="false" android:screenOrientation="landscape" /> <activity-alias android:name=".GodotAppLauncher" android:targetActivity=".GodotApp" android:exported="true"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity-alias>
To comply with Google Play's Families Policy and European Economic Area (EEA) ePrivacy / GDPR mandates, apps displaying ads must capture user consent via a Google-certified Consent Management Platform (CMP).
BranchingRealms implements this directly in com.example.branchingrealms.auth.UMPManager:
- Initializes Google's User Messaging Platform SDK (
user-messaging-platform:3.2.0). - Gathers user consent before loading or initializing Google Mobile Ads (
com.google.android.gms.ads.APPLICATION_ID). - Restricts test debug geography settings strictly to development builds:
if (BuildConfig.DEBUG) { val debugSettings = ConsentDebugSettings.Builder(context) .setDebugGeography(ConsentDebugSettings.DebugGeography.DEBUG_GEOGRAPHY_EEA) .addTestDeviceHashedId("3487F2022C8042A6F7B8A8C672F4F770") .build() builder.setConsentDebugSettings(debugSettings) }
- Gates telemetry collection:
CrashlyticsHelper.isCrashReportingEnabled(context)verifies user consent flags from local preferences before transmitting operational crash reports to Firebase.
Shipping release bundles to production requires a structured progression across Google Play Console distribution tracks to isolate defects and protect user experience.
Google Play Release Progression
+--------------------------------------------------------------------------+
| Track 1: Internal App Sharing & Testing |
| - Fast turnaround builds for development teams |
| - Target: Smoke tests, Play Asset Delivery validation, IAP sandbox checks|
| - Audience: Up to 100 internal accounts |
+------------------------------------+-------------------------------------+
|
v
+--------------------------------------------------------------------------+
| Track 2: Closed Testing Track (Google Play Compliance Gate) |
| - Mandatory requirement for personal developer accounts created post-2023|
| - Target: Maintain 20+ opt-in testers continuously for 14+ days |
| - Feedback: Android Vitals evaluation, device fragmentation, crash checks|
+------------------------------------+-------------------------------------+
|
v
+--------------------------------------------------------------------------+
| Track 3: Staged Production Rollout |
| - Phase 1: 10% rollout -> Monitor real-time Vitals & Crashlytics |
| - Phase 2: 25% rollout -> Verify ANR / Crash thresholds remain clear |
| - Phase 3: 50% rollout -> Expand hardware / OS demographic distribution |
| - Phase 4: 100% rollout -> Full production release |
+--------------------------------------------------------------------------+
- Purpose: Rapid distribution for immediate smoke testing.
- Validation Scope:
- Verifies that the signed
.aabinstalls correctly via the Play Store client engine. - Validates that
assetPackInstallTimedelivers all required binary game assets on launch without missing asset exceptions. - Tests Google Play Billing responses using licensed test accounts in sandbox mode.
- Tests Google Sign-In and Firebase Authentication against production SHA-256 certificate fingerprints.
- Verifies that the signed
Google requires all personal developer accounts created after November 13, 2023, to run a closed test with a minimum of 20 opt-in testers who remain actively opted in for at least 14 consecutive days before applying for production access.
- Operational Management:
- Creates dedicated email list groups in Google Play Console.
- Distributes the closed testing web and Android opt-in URLs.
- Enforces daily active testing across 20+ diverse hardware profiles (differing screen sizes, RAM tiers, Android OS versions 8.0 through 15).
- Collects non-public bug reports, UI clipping issues, and performance logs via Play Console feedback forms.
- Monitors the Closed Testing dashboard daily to ensure the opt-in count remains consistently at or above the 20-tester minimum threshold for the full 14-day duration.
Once Google Play approves the application for production, updates are deployed using phased percentage releases:
- 10% Staged Rollout: Deploys to a random 10% sample of production users. The release halts immediately if unexpected crashes emerge.
- 25% Staged Rollout: Increases distribution after 24 hours of stable telemetry.
- 50% Staged Rollout: Broadens geographic and device distribution after 48 hours.
- 100% Full Rollout: Completes the deployment once all operational gates pass.
If critical defects appear during any stage, the rollout is halted directly from the Play Console, preventing further distribution while engineers prepare an expedited patch release.
Production health is governed by continuous telemetry streaming into Google Play Android Vitals and Firebase Crashlytics.
Google Play algorithms penalize or remove apps that exceed standard "bad behavior" thresholds:
| Metric | Google Play Bad Behavior Threshold | Production Target | Action on Violation |
|---|---|---|---|
| User-Perceived Crash Rate | > 1.09% |
< 0.20% |
Immediate Rollout Halt; R8 de-obfuscation root-cause analysis |
| User-Perceived ANR Rate | > 0.47% |
< 0.10% |
Main thread audit; profiling background coroutines & Room I/O |
| Crash-Free Users (Crashlytics) | < 99.0% |
> 99.8% |
Block staged rollout progression to next percentage tier |
- Application Not Responding (ANR) Prevention:
- In
BranchingRealms, heavy disk I/O and database operations (Room queries, DataStore updates) are offloaded from the main UI thread ontoDispatchers.IOvia Kotlin Coroutines. - In
scratchboard-art, resource loading is executed asynchronously to ensure the rendering loop maintains consistent framerates and avoids triggering the 5-second Android input dispatch timeout.
- In
- Firebase Crashlytics Breadcrumb Logging:
BranchingRealmslogs key state transitions intoCrashlyticsHelper(e.g., registration attempts, authentication events, UMP consent state).- When an unhandled exception or non-fatal error occurs, the recorded breadcrumbs accompany the stack trace, exposing the exact user interaction sequence leading up to the failure.
- Symbol Table De-obfuscation:
- Automated mapping uploads during bundle compilation ensure that production stack traces appear fully de-obfuscated in the developer console, identifying the precise Kotlin class and source line number responsible for the error.