Performance, Memory & Optimization
Optimize Android app performance, profile memory usage, detect leaks, optimize layout rendering, and decrease APK size.
Module 17: Performance, Memory & Optimization
Learning Objectives
By the end of this module, you’ll understand:
- Android memory architecture
- Heap vs Stack
- Object allocation
- Garbage Collection (GC)
- Memory leaks
- Context leaks
- Activity leaks
- LeakCanary
- ANRs (Application Not Responding)
- StrictMode
- Rendering pipeline
- Choreographer
- VSync
- Frame rendering
- Jank
- RecyclerView/Compose performance
- Startup optimization
- R8 & ProGuard
- Baseline Profiles
- Profiling tools
- Battery optimization
- Performance best practices
Part 1 — What is Performance?
Performance isn’t one thing.
It’s a combination of:
Performance
│
├── Startup Time
├── UI Smoothness
├── Memory Usage
├── CPU Usage
├── Battery Usage
├── Network Efficiency
└── Storage Efficiency
An app that loads fast but drains the battery is not a performant app.
Part 2 — Memory Architecture
Every Android app receives a memory space called the Heap.
Conceptually:
Android App
│
└── JVM / ART Heap
│
├── User Objects
├── Strings
├── Lists
├── Images
└── ViewModels
Whenever you write:
val user = User()
Memory is allocated on the heap.
Stack vs Heap
One of the most important computer science concepts.
Stack
Stores:
- Function calls
- Local primitive values
- References to objects
Example:
fun login() {
val name = "Alice"
}
During execution:
Stack
login()
↓
name reference
Very fast.
Automatically cleaned when the function returns.
Heap
Stores:
User()
Bitmap()
ArrayList()
Repository()
RoomDatabase()
Objects stay alive until nothing references them.
Analogy
Imagine a library.
Stack:
Reading Desk
↓
Temporary Books
↓
Return When Finished
Heap:
Book Storage
↓
Books Stay Until Removed
Part 3 — Object Allocation
Every object requires memory.
Example:
repeat(100000) {
User()
}
This creates:
100,000 Objects
Allocation isn’t free.
Modern devices are fast, but excessive allocations:
- Increase GC pressure
- Reduce performance
- Increase battery usage
Part 4 — Garbage Collection (GC)
Who frees unused objects?
Not you.
Android (ART runtime) uses Garbage Collection.
Example:
User
↓
No References
↓
Garbage Collector
↓
Memory Reclaimed
You never manually call free() like in C.
How GC Works
Imagine:
Activity
↓
ViewModel
↓
Repository
↓
User
If:
Repository
↓
null
and nothing else references User:
GC eventually removes it.
Stop-the-World
Some GC phases pause application threads briefly.
Conceptually:
App Running
↓
GC Starts
↓
Pause
↓
Cleanup
↓
Resume
Modern ART has concurrent and generational collectors that minimize pauses, but frequent allocations can still contribute to frame drops (jank).
Part 5 — Memory Leaks
A memory leak occurs when:
Objects are no longer needed but are still referenced, preventing the GC from reclaiming them.
Example:
Activity Destroyed
↓
Still Referenced
↓
GC Cannot Remove It
Memory usage grows.
Eventually:
OutOfMemoryError
Common Leak Example
Imagine:
Activity
↓
Static Variable
↓
Activity
The static reference keeps the Activity alive.
Even after rotation.
Leak.
Context Leaks
One of the most common Android interview topics.
Suppose:
object ImageLoader {
lateinit var context: Context
}
Then:
context = activity
Now:
Singleton
↓
Activity Context
Activity cannot be garbage collected.
Huge leak.
Correct:
Singleton
↓
Application Context
Application context lives for the lifetime of the app and is safe for long-lived objects when appropriate.
Activity Leaks
Imagine rotation:
Activity A
↓
Destroyed
↓
Activity B Created
If A is still referenced:
Now both exist.
Rotate 20 times.
Twenty leaked Activities.
Fragment Leaks
A common mistake:
Fragment
↓
View Binding
↓
View Destroyed
↓
Binding Still Referenced
The Fragment outlives its view.
Always clear view bindings in onDestroyView() when using the View system.
Part 6 — LeakCanary
Finding leaks manually is difficult.
LeakCanary automatically detects:
Destroyed Activity
↓
Still Alive
↓
Leak Report
It provides:
- Leak trace
- Reference chain
- Suspected cause
It’s one of the most valuable debugging tools for Android.
Part 7 — ANR (Application Not Responding)
One of Android’s worst user experiences.
Imagine:
User Clicks
↓
Nothing Happens
↓
5 Seconds
↓
ANR Dialog
Typically caused by blocking the main thread.
Examples:
- Database query
- Network request
- Large file read
- Heavy computation
Main Thread Rule
The UI thread should:
Handle Input
↓
Layout
↓
Draw
↓
Animations
Not:
Download 500 MB
↓
Parse Huge JSON
↓
Compress Images
Those belong on background threads or coroutines.
Part 8 — StrictMode
StrictMode helps detect bad behavior during development.
Examples:
- Disk access on the main thread
- Network access on the main thread
- Resource leaks
Think of it as a development-time safety net.
Part 9 — Rendering Pipeline
One of the most important Android concepts.
Suppose:
User scrolls.
Pipeline:
Input
↓
Measure
↓
Layout
↓
Draw
↓
GPU
↓
Display
This repeats for every frame.
VSync
Displays refresh at fixed intervals.
For a 60 Hz display:
Every 16.67 ms
↓
New Frame
120 Hz?
Every 8.33 ms
Miss the deadline:
Frame skipped.
User notices stutter.
Choreographer
The Android Choreographer coordinates frame rendering with VSync.
Conceptually:
VSync Signal
↓
Choreographer
↓
UI Updates
↓
Render Frame
It schedules rendering work so frames align with the display refresh.
Part 10 — Frame Budget
At 60 Hz:
You have approximately:
16.67 ms
to:
- Run animations
- Process input
- Measure
- Layout
- Draw
Finish within the budget:
16 ms
↓
Smooth
Take:
40 ms
Dropped frame.
Jank
Jank is:
Noticeable stuttering caused by missing frame deadlines.
Reasons:
- Heavy recomposition
- Expensive layout
- Large bitmap decoding
- Main-thread database work
- Excessive allocations triggering GC
Part 11 — RecyclerView Performance
RecyclerView is fast because it:
Recycles Views
Instead of:
1000 Items
↓
1000 Views
It keeps only the visible views (plus a small cache) and reuses them.
Optimization tips:
- Use
DiffUtil - Stable IDs when appropriate
- Avoid expensive work in
onBindViewHolder() - Load images asynchronously
Compose Performance
Compose doesn’t recycle views.
Instead it relies on:
State
↓
Recomposition
↓
UI Update
Performance depends on minimizing unnecessary recompositions.
Key concepts:
- Stable state
- Immutable models
rememberderivedStateOf- Lazy layouts
- Correct use of keys
Part 12 — Startup Optimization
Users judge apps quickly.
Cold start:
Tap Icon
↓
Process Starts
↓
Application Created
↓
Activity Created
↓
First Frame
Avoid:
- Heavy work in
Application - Large synchronous initialization
- Blocking I/O during startup
Initialize expensive features lazily where possible.
Baseline Profiles
One of the biggest modern Android performance improvements.
Normally:
Install App
↓
Runtime Optimizes Code
↓
Eventually Faster
With Baseline Profiles:
Install App
↓
Critical Code Already Optimized
↓
Fast Immediately
They improve startup and common user journeys by guiding ahead-of-time compilation.
Part 13 — R8 & ProGuard
Release builds differ from debug builds.
R8 performs:
- Code shrinking
- Dead code removal
- Optimization
- Obfuscation
Result:
Smaller APK
↓
Less Download
↓
Faster Install
Obfuscation also makes reverse engineering more difficult.
Part 14 — Images
Large bitmaps consume a lot of memory.
Example:
4000 × 3000 Image
Huge allocation.
Best practices:
- Resize to display size
- Prefer image loading libraries (Coil, Glide)
- Avoid loading full-resolution images unnecessarily
Part 15 — Profiling
Android Studio Profiler helps inspect:
CPU
Memory
Network
Energy
Instead of guessing, you measure.
Performance engineering is driven by evidence.
CPU Profiler
Shows:
Functions
↓
Execution Time
Useful for identifying expensive operations.
Memory Profiler
Shows:
Heap
↓
Allocations
↓
Leaks
Helps detect:
- Growing memory
- Excessive allocations
- Leak patterns
Network Profiler
Shows:
- Requests
- Response sizes
- Timing
- Frequency
Useful for spotting unnecessary API calls.
Energy Considerations
Battery usage depends on:
- CPU
- GPS
- Camera
- Sensors
- Network
- Wake locks
Efficient apps:
- Batch work
- Avoid unnecessary polling
- Respect WorkManager constraints
- Minimize background activity
Part 16 — Performance Mindset
Imagine loading a feed.
Bad:
Button
↓
Network
↓
Huge JSON
↓
Parse on Main Thread
↓
Freeze UI
Better:
Network
↓
Background Parsing
↓
Room
↓
Flow
↓
Compose
Notice how everything you’ve learned contributes to performance.
Architecture influences efficiency.
Complete Performance Pipeline
User Scrolls
│
▼
Input Event
│
▼
Compose / RecyclerView
│
▼
Measure
│
▼
Layout
│
▼
Draw
│
▼
GPU
│
▼
Display
│
▼
16.67 ms Deadline
Every stage matters.
Common Mistakes
❌ Performing network requests on the main thread
Always use coroutines or asynchronous APIs.
❌ Keeping Activity references in singletons
Prefer the Application context when a long-lived context is required.
❌ Loading full-size images unnecessarily
Scale images to the display size and use an image loading library.
❌ Allocating objects inside tight loops or frequently called rendering code
Unnecessary allocations increase GC activity.
❌ Ignoring profiling tools
Measure first.
Optimize second.
❌ Optimizing too early
Follow the classic advice:
Make it correct → Make it measurable → Make it fast.
Premature optimization often complicates code without delivering meaningful improvements.
Mental Model
Imagine a restaurant kitchen.
Customer Order
│
▼
Chef
│
▼
Cook
│
▼
Plate Food
│
▼
Serve
If one station becomes slow:
Everyone waits.
Android rendering works similarly.
A bottleneck in any stage—layout, drawing, image decoding, or GC—can delay the entire frame.
Best Practices
- Keep the main thread responsive.
- Minimize unnecessary object allocations.
- Avoid memory leaks by respecting lifecycle boundaries.
- Profile before optimizing.
- Use LeakCanary during development.
- Enable StrictMode to catch bad practices early.
- Keep startup work minimal.
- Use Baseline Profiles and R8 in release builds.
- Load and cache images efficiently.
- Design for smooth rendering, not just correct rendering.
Interview Questions
- What is the difference between the stack and the heap?
- How does Garbage Collection work in Android?
- What is a memory leak?
- Why can storing an
Activityin a singleton cause a leak? - What is an ANR, and what typically causes it?
- What is the 16.67 ms frame budget?
- What is jank?
- How does RecyclerView achieve good performance?
- What are Baseline Profiles, and why are they useful?
- How would you investigate a slow or memory-hungry Android app?
Module 17 Summary
You now understand the foundations of Android performance engineering:
- Heap stores objects; the stack stores execution state.
- Garbage Collection automatically reclaims unreachable objects.
- Memory leaks occur when objects remain referenced after they should be released.
- LeakCanary and StrictMode help detect common performance issues.
- ANRs usually result from blocking the main thread.
- The rendering pipeline has a strict frame budget (16.67 ms at 60 Hz).
- RecyclerView and Compose optimize rendering using different models.
- R8 and Baseline Profiles improve release performance.
- Profilers provide the data needed to make informed optimizations.
Most importantly, you’ve learned that performance isn’t a single feature—it’s the outcome of good architecture, efficient memory management, responsive UI design, and evidence-based optimization.
Next Module: Security (Android Security, Authentication, Encryption & Secure App Design)
Module 18 explores how to build applications that are secure by design, not just functional.
We’ll cover:
- Android’s security model and application sandbox
- Permissions and runtime permission flow
- Authentication vs authorization
- Secure token storage
- Android Keystore System
- Encryption fundamentals
- Biometric authentication
- Network security configuration
- Certificate pinning
- Protecting against reverse engineering
- Root detection concepts
- OWASP Mobile Top 10
- Secure coding practices
- Play Integrity API (conceptual overview)
This module completes another critical pillar of professional Android development: building apps that protect user data and resist common attacks.