The Challenge
Managing performance across a portfolio of 15+ production Android apps spanning FinTech, MLM, Crypto, and Enterprise domains presented a unique challenge. Each app had its own architecture, dependencies, and performance profile. Some were built with Java, others with Kotlin, and a few were mid-migration. Target devices ranged from flagship phones to budget hardware common in India's tier-2 and tier-3 cities.
The team was shipping features fast but accumulating performance debt. APK sizes had ballooned due to unused resources and duplicated dependencies. Cold-start times crept up as initialization logic grew. ANRs (Application Not Responding) spiked during complex data operations that blocked the main thread.
The Approach
Phase 1: Measurement Framework
You cannot optimize what you don't measure. I established a performance baseline framework across all apps:
- Integrated Firebase Performance Monitoring for real-time cold-start, screen rendering, and network latency tracking
- Set up Macrobenchmark tests for critical user journeys (app launch, first content display, transaction completion)
- Created a shared Gradle plugin that automatically ran APK analysis and flagged size regressions on every PR
- Built a team dashboard displaying performance KPIs per app, updated daily from CI pipelines
Phase 2: Bundle Size Optimization
Achieving the 40% bundle size reduction was a combination of multiple techniques applied systematically:
- R8 full mode with aggressive shrinking — Configured proguard rules per module to maximize dead code elimination while preserving reflection-dependent code paths
- Resource shrinking and WebP migration — Converted all PNG/JPEG assets to WebP, removed unused resources with
shrinkResources true, and replaced complex vector drawables with optimized SVGs - Dynamic feature modules — Moved rarely-used features (admin panels, advanced reports, onboarding tutorials) into on-demand modules downloaded only when needed
- Dependency audit — Replaced heavyweight libraries with lighter alternatives. Swapped Gson for Kotlin Serialization, removed unused Jetpack libraries, and consolidated duplicate utility functions into a shared core module
- App Bundles (AAB) — Migrated all apps from APK to AAB distribution, enabling Google Play to deliver optimized APKs per device configuration
Phase 3: Cold-Start Optimization
Reducing cold-start time by 25% required understanding what happens between the user tapping the icon and seeing useful content:
- Implemented the App Startup library to replace scattered
ContentProviderinitializations with a single, ordered startup sequence - Deferred non-critical SDK initializations (analytics, crash reporting, ad networks) to a background thread after first frame render
- Generated Baseline Profiles for critical user paths, allowing ART to pre-compile hot methods during installation rather than at runtime
- Replaced splash screen animations with static themed windows to eliminate blank-screen perception during process creation
Phase 4: ANR Elimination
ANR reduction required a disciplined approach to threading:
- Audited every database query and moved all Room operations to
Dispatchers.IOwith Kotlin Coroutines - Replaced synchronous SharedPreferences with DataStore for non-blocking preference reads
- Implemented RecyclerView optimization patterns:
DiffUtilfor minimal UI updates,setHasFixedSize, and view type pooling for complex heterogeneous lists - Introduced a strict mode wrapper in debug builds that flagged any disk or network I/O on the main thread, with automatic Slack alerts to the responsible developer
Results
- Average APK size across the portfolio dropped from 28MB to 17MB (40% reduction)
- Median cold-start time improved from 1.8s to 1.35s (25% faster)
- ANR rate dropped by 60%, from 0.47% to 0.19% — well below Google's bad behavior threshold
- Crash-free session rate stabilized above 99.3% across all production apps
- The performance framework became a standard part of the CI/CD pipeline, preventing regressions on every merge
Key Learnings
Performance optimization at portfolio scale is fundamentally a process problem, not a technical one. The individual techniques — R8, Baseline Profiles, coroutines — are well-documented. The hard part is creating systems that apply them consistently across 15+ apps maintained by multiple developers with varying experience levels.
The most impactful decision was making performance a CI gate rather than a periodic audit. When every PR automatically reports APK size delta and startup time impact, performance becomes everyone's responsibility rather than a quarterly fire drill.