Chapter 7
Platform & Performance
Background Work · App Startup · Memory Leaks · Battery · ANR · Performance Profiling
Ready to practise interactively?
Explore this chapter with quizzes, diagrams, and real-world examples in the full interactive experience.
App Startup Optimization
App startup is a top interview topic at Google. Know all three start types and the modern tools.
- Baseline Profiles — pre-compiles critical code paths at install time. Reduces cold start 20–40% on average, typically toward the lower end for most apps. Generated with Macrobenchmark. Baseline Profiles are a critical L6-level performance optimization. Performance optimization is heavily assessed at L6+, and Baseline Profiles represent the current state of the art.
- App Startup library — replaces ContentProvider-based initialization. Declare initializers, control startup order, enable lazy init.
- Lazy initialization — defer non-critical SDKs (analytics, crash reporting) until after first frame
- Main thread work — profile Application.onCreate() with Android Profiler; move any I/O or heavy computation off main thread immediately
- Splash Screen API — use SplashScreen API (API 31+) for smooth startup animation; avoid custom splash Activities
| Start Type | What Happens | Optimization Target |
|---|---|---|
| Cold start | Process created, Application.onCreate(), first Activity. Slowest. | < 2 seconds. Biggest impact opportunity. |
| Warm start | Process alive, Activity recreated from back stack. | < 1 second. |
| Hot start | App in memory, Activity brought to foreground. | < 500ms. Usually fast already. |
Recommended Libraries
- App Startup — Jetpack library for initializing components at app startup. Replaces ContentProvider chains. Controls init order and supports lazy initialization.
- ProfileInstaller — Installs Baseline Profiles at app install time. Required to get cold start benefits from Baseline Profiles on pre-Android 9 devices.
Memory Management & Leaks
Memory leaks are a classic L6 question. Know causes, detection, and fixes.
- LeakCanary — add to debug builds only; automatically detects and reports Activity/Fragment/ViewModel leaks
- Memory Profiler — in Android Studio; take heap dumps; look for Activity instances that should be GC'd
| Leak Cause | Example | Fix |
|---|---|---|
| Context leak via static reference | static Activity reference in a singleton | Use Application context for singletons; never store Activity in static field |
| Anonymous inner class / lambda | Handler(Looper.main) { } inside Activity — holds implicit reference | Use WeakReference or move to ViewModel |
| Unregistered listener | Registering BroadcastReceiver in onCreate, never unregistering | Unregister in onDestroy or use lifecycle-aware components |
| ViewModel holding View reference | Passing a View or Activity into ViewModel constructor | ViewModels must never hold View/Activity refs. Use Application context if needed. |
| Coroutine not cancelled | GlobalScope.launch { } inside Activity — outlives Activity | Use lifecycleScope or viewModelScope exclusively |
| Bitmap not recycled | Holding large Bitmap in memory past its use | Use image loading libraries (Coil/Glide) that manage bitmap recycling automatically |
Recommended Libraries
- LeakCanary — Automatic memory leak detection for Android debug builds. Detects Activity, Fragment, ViewModel, and custom object leaks. Zero config.
Background Processing
Know when to use each background processing API.
- WorkManager constraints — setRequiresNetworkType(CONNECTED), setRequiresBatteryNotLow(true), setRequiresCharging(true)
- Chained work — WorkManager.beginWith(a).then(b).then(c).enqueue() — compress → upload → notify
- Doze mode — Android restricts background work during Doze. WorkManager is Doze-safe. FCM high-priority messages bypass Doze.
| API | Use When | Guaranteed? |
|---|---|---|
| WorkManager | Deferrable, guaranteed background work (sync, uploads, analytics) | Yes — survives process death and reboots |
| Foreground Service | User-visible long-running work (media, navigation, download) | Yes — shown in notification tray |
| Coroutine (lifecycleScope) | Short background work within a lifecycle | No — cancelled with lifecycle |
| AlarmManager | Exact-time alarms (calendar reminders) | Yes — but not for network work |
Recommended Libraries
- WorkManager — Jetpack's background work scheduler. Deferrable, guaranteed, survives reboots. Standard for Android background tasks.
- WorkManager KTX — Kotlin extensions for WorkManager. CoroutineWorker for suspend functions.
ANR — App Not Responding
ANR is triggered when the main thread is blocked. Know the specific timeouts.
- StrictMode — enable in debug builds to catch accidental main thread I/O before it causes ANRs
- Avoid — SharedPreferences.apply() (async) is safe; .commit() (sync) on main thread causes ANRs
| ANR Type | Timeout | Common Cause |
|---|---|---|
| Input dispatch timeout | 5 seconds | Blocking main thread during touch/key event handling |
| Broadcast receiver timeout | 10 seconds (foreground), 60s (background) | Doing I/O in BroadcastReceiver.onReceive() |
| Service start timeout | 20 seconds (foreground), 200s (background) | Long-running work in Service.onCreate() |
| Content provider timeout | 10 seconds | Slow query in ContentProvider |
Battery & Network Efficiency
Mobile apps must be good citizens regarding battery and network usage.
- App Standby Buckets — Android limits background work based on app usage. Active > Working Set > Frequent > Rare > Restricted.
- Battery Historian — analyse battery drain; identify wakelock abuse and unnecessary wake-ups
- Wakelocks — avoid partial wakelocks; use WorkManager instead. Never hold a wakelock indefinitely.
- Network scheduling — batch non-urgent requests. Use WiFi-only constraint for large uploads.
- Adaptive polling — increase polling interval under low battery or poor connectivity
- Push over pull — FCM push is far more battery-efficient than polling every N seconds
WorkManager + Outbox + Room — End-to-End
How background sync, local persistence, and guaranteed delivery fit together as one system.
- Worker types — CoroutineWorker (use this, suspend doWork()); ListenableWorker (legacy Java/callback interop); avoid plain Worker for new code
- Input/output data — pass via workDataOf(); limited to 10KB. For larger data, pass a Room row ID, not the object itself.
- Result.retry() — triggers backoff. Set BackoffPolicy.EXPONENTIAL on the WorkRequest. Implement your own retry counter in Room — WorkManager has no built-in max retry.
- ExistingWorkPolicy.KEEP — prevents duplicate workers when user action triggers sync multiple times before the first worker finishes
| Outbox Entry Status | Meaning | Action |
|---|---|---|
| PENDING | Written locally, not sent yet | WorkManager will pick up and send |
| IN_FLIGHT | Worker is currently sending | Skip — do not retry to avoid duplicates |
| SENT | Server confirmed, server ID assigned | Safe to clean up after TTL |
| FAILED | Exceeded max retries | Surface error in UI, allow manual retry |
Streaming API Responses — SSE + Flow + Room
How to wire a streaming API response through the Android stack correctly.
- OkHttp streaming — read response.body().source() line by line in a loop; each SSE event is a data: line
- callbackFlow { } — wraps the OkHttp callback into a Kotlin Flow; use trySend() for non-blocking emit; awaitClose { call.cancel() } for cleanup
- ViewModel collects the Flow — updates StateFlow on each token; UI recomposes incrementally showing tokens as they arrive
- Persist on completion — buffer tokens in memory during streaming; write the full assembled response to Room only when stream ends. Room Flow then re-emits history list automatically.
- Error mid-stream — catch IOException in Flow.catch { }; update UiState with partial response + error indicator; allow user to retry from last position if API supports cursor resumption
| Strategy | When to Use |
|---|---|
| Buffer in memory, write on completion | Default for chat/LLM. Simplest. Risk: crash mid-stream loses response. |
| Write chunks to Room as they arrive | Long document/code generation where interruption is costly. Resumable. |
| Stream to UI only, never persist | Ephemeral data (live prices, scores). No history needed. |
Recommended Libraries
- okhttp-eventsource — SSE client by LaunchDarkly built on OkHttp. Production-grade, auto-reconnect, backoff.
- Ktor SSE — Kotlin-first SSE client with coroutines. Part of Ktor framework. Good for KMP.
Video Streaming — ExoPlayer / Media3
Netflix, YouTube, TikTok roles will ask about this. Know the architecture, not just the API.
- Media3 / ExoPlayer — the standard Android media player. Supports HLS, DASH, SmoothStreaming, progressive MP4.
- Adaptive bitrate (ABR) — HLS/DASH splits video into segments at multiple quality levels. Player monitors bandwidth and switches quality per segment. No visible pause.
- Buffering strategy — configure minBufferMs, maxBufferMs, bufferForPlaybackMs. Longer buffer = smoother playback but more memory and data usage.
- Background playback — requires a Foreground Service + MediaSession. Media3 MediaSessionService handles this with minimal boilerplate.
- Offline download — DownloadManager in Media3 segments and caches video chunks to disk. Requires a Download Service (ForegroundService).
- DRM (Widevine) — ExoPlayer handles L1/L3 Widevine via DefaultDrmSessionManager. L1 = hardware-backed (device-level capability). Devices with L1 support can stream Netflix HD+ (1080p+); L3-only devices are capped at SD (480p). Apps use ExoPlayer's DRM integration — the device's Widevine level determines available quality.
Recommended Libraries
- Media3 (ExoPlayer) — Jetpack's official media player. Successor to standalone ExoPlayer. Supports HLS, DASH, DRM, background playback.
- media3-exoplayer — Core ExoPlayer module in Media3. Start here for basic playback.
- media3-ui — Pre-built player UI components (PlayerView). Customizable controls, picture-in-picture support.
- media3-session — MediaSession integration for background playback, notification controls, and Android Auto.
File Upload & Download Manager
Asked at Google (Drive), Dropbox, any media-heavy app. The key is resumability.
- Chunked upload — split file into chunks (e.g. 5MB each); upload sequentially; track which chunks succeeded in Room. On failure, resume from last successful chunk.
- HTTP Range requests — for resumable downloads, server must support Range header. Client sends Range: bytes=X- to resume from byte X after interruption.
- Progress tracking — emit progress via Flow from the Worker; ViewModel observes WorkInfo.progress for WorkManager-based downloads
- Pause / Resume — store byte offset in Room; cancel the Worker; on resume, create a new Worker that reads the offset and sends the Range header
- Storage management — check available space before starting; use getExternalFilesDir() or MediaStore for downloads; clean up incomplete chunks on cancel
- Foreground Service — large downloads need a ForegroundService with a notification showing progress so Android doesn't kill the process
Performance Regression Prevention in CI
Shipping a performance regression is easy. Finding it 3 sprints later is expensive. The modern approach: measure in CI, fail the build on regressions, and block the PR before it merges.
- Macrobenchmark in CI — add a :benchmark module; run on a physical device or Firebase Test Lab; FrameTimingMetric catches jank regressions
- StartupTimingMetric — measures TTID (Time to Initial Display) and TTFD (Time to Full Display) for cold/warm/hot starts
- FrameTimingMetric — counts frames >16ms (jank); catches scroll performance regressions in RecyclerView or LazyColumn
- Benchmarks require a profileable build variant — add android { buildTypes { benchmark { ... } } } to your build.gradle
- Firebase Test Lab — run benchmarks on real devices in CI; Robo test can catch startup crashes; custom Instrumentation runs Macrobenchmarks
- Fail threshold — set a baseline JSON file; if p50 startup increases by >10%, fail the CI step
Recommended Libraries
- Macrobenchmark — Jetpack library for measuring startup, scroll jank, and animation in CI. FrameTimingMetric, StartupTimingMetric. Run on real devices.
- Firebase Test Lab — Cloud device farm for running automated tests and benchmarks on real Android devices. Integrates with CI pipelines.
Interview tip: When discussing performance tooling, mention the full chain: Recomposition Highlighter (dev) → Perfetto trace (profiling) → Macrobenchmark (CI prevention) → Baseline Profiles (shipped optimisation). This shows you think in systems, not one-off fixes.
Real-Time Location Tracking
Uber, DoorDash, Lyft core topic. Three distinct problems: getting location, sending it, doing it efficiently.
- FusedLocationProviderClient — always use this, never raw GPS/Network. Google Play Services fuses GPS, WiFi, cell to give best accuracy at lowest battery cost.
- Background location (Android 10+) — requires ACCESS_BACKGROUND_LOCATION permission. Must run inside a Foreground Service with location-type notification.
- Location accuracy tiers — PRIORITY_HIGH_ACCURACY (GPS, drains battery); PRIORITY_BALANCED_POWER (WiFi/cell, good for most); PRIORITY_LOW_POWER (city-level, minimal battery)
- Sending updates to server — buffer location updates locally; send in batches via WebSocket or HTTP POST every N seconds to reduce network overhead
- Geofencing — use Geofencing API for enter/exit triggers; far more battery efficient than polling location against a boundary
| Scenario | Strategy |
|---|---|
| Active ride tracking (Uber driver) | Foreground Service + HIGH_ACCURACY + WebSocket send every 2–5s |
| Passive location (delivery ETA) | Background BALANCED_POWER + batch HTTP send every 30s |
| Geofence trigger (arrive at store) | Geofencing API — zero battery when outside fence radius |
Recommended Libraries
- FusedLocationProviderClient — Google Play Services location API. Fuses GPS, WiFi, cell for best accuracy at lowest battery. Always use this over raw GPS.
- play-services-location — The dependency for FusedLocationProvider. Required for any location work on Android.