A slow app is the number one reason users uninstall software. Milliseconds of delay at startup or during list scrolling reduce retention by tens of percent. Performance is not just speed, but also stability: no ANR, crashes, or memory leaks. This article covers all aspects of performance: from memory management (GC, ARC) to profiling with tools. Learn more in the official Android Performance guide.
Key Takeaways
App performance is directly tied to jank — a noticeable delay between user action and UI response. Main causes: Main Thread blocking (heavy operations on the UI thread), frequent layout redraws (overdraw), memory leaks (frequent GC), non-optimal algorithms (O(n²) on large datasets). Frame Rate (FPS) is the number of frames per second. For a comfortable experience, you need stable 60 FPS (Android) or 120 FPS (iPhone Pro, iPad Pro). VSync synchronizes rendering with the screen refresh rate.
Jank occurs when rendering a single frame exceeds 16.6 ms (for 60 FPS) or 8.3 ms (for 120 FPS). GPU profiling (Profile GPU Rendering on Android, Core Animation on iOS) shows which rendering stages take the most time. Main stages: Layout (positioning elements), Draw (rendering), Display (transfer to frame buffer). The most common issue is layout inflation in XML, especially with complex nested ConstraintLayout.
Time-to-Interactive (TTI) is the time it takes for the app to become fully ready for interaction. TTI includes Cold Start, data loading, and library initialization. Google recommends TTI under 5 seconds, Apple — under 2 seconds for main screens. Lazy Loading is a deferred content and library loading technique, critical for improving TTI. At IT Sectr, we use lazy initialization by default on all projects.
ANR and Crash are the main enemies of mobile app performance. ANR (Application Not Responding) is a dialog box on Android that appears if the main thread is blocked for more than 5 seconds. Causes: synchronous network requests on the UI thread, database operations without coroutines, large bitmap decode without downsampling, deadlock on the Main Thread. ANR call stack is saved in /data/anr/traces.txt and allows pinpointing the exact blocking location.
Crash is an unexpected app termination. On Android — it is an Exception (Java/Kotlin) or Signal (native code). On iOS — NSException or signal (EXC_BAD_ACCESS — accessing freed memory). Crash Reporting tools: Firebase Crashlytics, Sentry, BugSnag. They collect stack traces, device data, and reproduction steps. Stack Overflow — call stack overflow from infinite recursion. OutOfMemoryError — when the heap is full.
StrictMode is an Android tool for detecting thread safety violations. It allows setting rules: ThreadPolicy (forbid disk/network on main thread), VmPolicy (detect Activity, SQLite, CloseGuard leaks). StrictMode should be enabled only in debug builds — it should not run in release. The iOS equivalent is Main Thread Checker (Xcode), which automatically detects UIKit calls not on the main thread.
A memory leak is a situation where an object remains in memory even though the app no longer uses it. This directly reduces app performance. On Android, GC (Garbage Collection) cannot collect an object if there is a strong reference to it. Typical causes: static references to Activity, uncleared callbacks/observers, inner classes with implicit reference to the outer class, Handler with uncleared messages. LeakCanary is a library for automatic leak detection.
ARC (Automatic Reference Counting) is the memory management model in iOS. Each object has a reference count (retain count). When the count reaches zero, memory is freed. A Retain Cycle occurs when two objects hold strong references to each other (A → B and B → A). ARC will never zero out the counts. Solution: weak or unowned references. Weak automatically nilifies upon object deallocation. Unowned does not nilify but guarantees the object is alive.
GC (Garbage Collection) runs on Android (Java/Kotlin). GC periodically pauses execution (Stop-the-World pause) to find and free unreachable objects. GC Trigger: when the heap fills to a certain percentage. ARC runs on iOS (Swift/Objective-C) and has no pauses — counters are updated atomatically with each assignment. ARC is more predictable but can accumulate excessive retain/release operations under high assignment frequency.
Weak Reference and Strong Reference — the reference type determines whether GC/ARC can free the object. Strong Reference — the object will not be collected as long as this reference exists. Weak Reference — GC/ARC can collect the object; the weak reference becomes nil (in Swift/Java WeakReference). Unowned Reference (Swift) — does not nilify upon deallocation; accessing it after the object is dead causes a crash. On Android, java.lang.ref.WeakReference is used for weak references.
Example of detecting a leak on Android using LeakCanary:
// Утечка: анонимный класс держит ссылку на Activity
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
val handler = object : Handler(Looper.getMainLooper()) {
override fun handleMessage(msg: Message) {
// Используем `this@MainActivity`, сохраняя ссылку на Activity
Log.d("TAG", "Handler received message")
}
}
handler.sendEmptyMessageDelayed(0, 60000)
}
}
// Исправление: статический Handler + WeakReference
class SafeHandler(activity: MainActivity) : Handler() {
private val weakActivity =
WeakReference(activity)
override fun handleMessage(msg: Message) {
weakActivity.get() ?: return
Log.d("TAG", "Handler received message")
}
}
Profiling is the process of measuring app performance: CPU, memory, network, power consumption. Without profiling, blind optimization is useless — you won't know which part of the code is actually slow.
| Tool | Platform | Measures | When to Use |
|---|---|---|---|
| Instruments (Time Profiler) | iOS | CPU, function calls, execution time | Algorithm optimization, bottleneck search |
| Instruments (Allocations) | iOS | Memory, object count, retain counts | Finding leaks and excessive memory consumption |
| Instruments (Leaks) | iOS | Retain cycles, memory leaks | Regular pre-release checks |
| Android Profiler (CPU) | Android | CPU usage, thread activity, traces | Finding Main Thread blockages |
| Android Profiler (Memory) | Android | Heap dump, allocation tracking | Finding leaks, object analysis |
| Android Profiler (Network) | Android | Traffic, speed, request timings | Network call optimization |
| LeakCanary | Android | Automatic memory leak detection | At all development stages |
| StrictMode | Android | Disk/network on main thread, leaks | Debug builds |
| Traceview / Systrace | Android | Method tracing, system events | Deep latency analysis |
Instruments (Xcode) is the most powerful tool for iOS. Time Profiler shows which functions consume the most CPU. Allocations tracks object creation and deallocation. Leaks automatically finds retain cycles. Profiling steps: (1) launch Instruments; (2) select template (Time Profiler for CPU); (3) run the problematic scenario; (4) analyze call stack — the widest column is the hottest function.
Android Profiler is built into Android Studio (View → Tool Windows → Profiler). CPU Profiler shows each thread's load. Memory Profiler provides heap dump and allocation tracking. Network Profiler shows all HTTP requests with timings. Energy Profiler measures power consumption: WakeLock, Location, Network. For detailed tracing, use Systrace (Android 10+) or Perfetto — system tracing with microsecond precision.
App startup is one of the key performance indicators. It is divided into three types: Cold Start — the app starts from scratch: the process is created, Application.onCreate (Android) / AppDelegate.applicationDidFinishLaunching (iOS), class loading, library initialization. Warm Start — the process exists, but the Activity/ViewController is destroyed (e.g., on screen rotation or returning from memory). Hot Start — Activity/ViewController is in memory, the app is simply displayed (switching from another app).
Cold Start is the most important metric. On Android it includes: (1) launch Activity — XML loading, View initialization; (2) first frame — time to first render. Google recommends: launch Activity < 200 ms, first frame < 500 ms, TTI < 5 seconds. Cold Start optimization: reduce Application.onCreate (coroutines for lazy initialization), use SplashScreen API (Android 12+), defer library initialization (WorkManager, DI), remove unnecessary ContentProviders.
On iOS, Cold Start includes: Mach-O binary loading, dyld (dynamic linker), Objective-C runtime initialization, Application delegate, first controller. Chrome Custom Tabs (Android) and Universal Links (iOS) are technologies for quickly opening external content in the app without a full Cold Start. It is recommended to test Cold Start on real mid-range devices.
App size is a performance factor for installation and updates. It affects conversion: every 10 MB reduces conversion by 1%. Google Play recommends APK size under 150 MB; App Store — under 200 MB (cellular networks — 100 MB). Main optimization methods: image compression (WebP instead of PNG saves 25-35%), vectorization (VectorDrawable on Android, SF Symbols on iOS), unused code removal (R8/ProGuard), unused resource removal (lint → unused resources).
App Bundle (Android) is a publishing format where Google Play generates an optimized APK for each device. App Bundle reduces download size by 20-40%. Dynamic Delivery — modules downloaded on demand (on-demand feature modules). The iOS equivalent is On-Demand Resources (ODR): resources downloaded after first launch (game levels, videos).
Lazy Loading is a technique where modules and libraries are not loaded at startup but loaded as needed. Split APK (Android) and App Slicing (iOS) split the app into architecture slots: arm64-v8a, x86_64. App Size Optimization is an ongoing process: analyze APK composition (Analyze APK in Android Studio), remove duplicate icons, use SVG instead of multiple PNG densities. At IT Sectr, we include build size checks in CI/CD for every MR.
Frequently Asked Questions
ANR (Application Not Responding) is a dialog that appears on Android when the main thread is blocked for more than 5 seconds. To avoid ANR, move all heavy operations (network, database, file processing) to background threads. The iOS equivalent is frozen UI, when the app stops responding to touches.
A Memory Leak is when an object cannot be freed because references to it still exist. A Retain Cycle is a situation in iOS/Objective-C where two objects reference each other (A → B → A), and ARC cannot free either. Solution: weak/unowned references and timely callback cleanup.
For iOS: Instruments (Time Profiler, Allocations, Leaks). For Android: Android Profiler (CPU, Memory, Network), LeakCanary (memory leaks), StrictMode (thread violations). It is recommended to combine profiling during development and integration.
Cold Start — the app starts from scratch: the process is created, classes are loaded, Application.onCreate runs. Warm Start — the process exists but the Activity/ViewController is recreated. Hot Start — Activity/ViewController is already in memory, simply displayed. Cold Start is the slowest (1-5 seconds) and is critical for user experience.
Main methods: remove unused resources and code (use R8/ProGuard), vectorize images (VectorDrawable, SF Symbols), compress PNG/WebP (Android), use App Bundle instead of APK, remove unnecessary libraries, use Lazy Loading for modules. Size optimization can reduce APK size by 40-60%.
Summary
We will develop a mobile application turnkey
IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.