Mobile App Performance
Mobile app performance is how fast an app responds and how much of the phone it consumes while doing so. In ISO/IEC 25010 terms it covers two sub-characteristics of performance efficiency, time behaviour and resource utilisation, and on a phone five measures carry most of the risk: startup time, frame rate, memory, battery and behaviour under real network conditions. You check each one against an agreed target or against the previous release, on the devices your users actually own.
Example: a shopping app shows its home screen in 1.8 seconds on the developer’s new flagship phone. On the cheapest phone the team supports, starting from a stopped process, the same screen takes 4.6 seconds, the product list stutters while images load, and on a weak mobile connection the home screen waits for 19 requests. No functional test case fails, yet every user of that phone notices.
Why it matters
Users judge an app in its first seconds and in every scroll. A slow start, a stuttering list or a phone that warms up and loses charge can lead to uninstalls and poor store reviews. The platforms measure it too: Android vitals in Google Play Console reports startup time, slow and frozen frames and battery-related behaviour from real devices, and the Xcode Organizer shows launch time, hangs, memory and battery use from users who share analytics.
For a tester, performance defects are easy to miss because they depend on conditions: a cheap device, a cold start, a long session, a weak signal. They also creep in a little with each release until the app is clearly slower than it was. Measuring the same scenarios the same way every release turns "it feels slow" into numbers a developer can act on.
The five measures and what each one means
Each measure has its own definition and its own way to fail. Learn the vocabulary first: a report that says "startup is slow" is far weaker than one that says "cold start to first frame went from 1.4 s to 2.6 s".
- Startup time. A cold start launches the app when its process is not running, so everything is created and loaded; a warm start reuses part of what exists but must recreate the screen; a hot start only brings a screen that is still in memory back to the front. Android names two moments: time to initial display (TTID), the first frame, and time to full display (TTFD), when the content the user came for is ready, which the app reports by calling
reportFullyDrawn(). Android vitals measures startup to the first frame (TTID) and treats cold starts of 5 seconds or more, warm starts of 2 seconds or more and hot starts of 1.5 seconds or more as excessive. - Frame rate. At 60 Hz the app has about 16.7 ms to produce each frame; at 120 Hz about 8.3 ms. Frames that miss the budget show as stutter, called jank (Apple speaks of hitches). Android vitals reports slow rendering and frozen frames; a frozen frame is one that takes longer than 700 ms to render. The usual cause is long work on the main thread, the same thread whose blocking produces the ANR from the lesson on Android and iOS.
- Memory. Watch the peak a flow needs and whether memory returns to its baseline when the flow ends; memory that grows with every repetition is a leak. High memory use gets the app ended: in the background the system ends it sooner, so the user may return to a restarted app that has lost their input; in the foreground, passing its limit ends it as an out-of-memory crash.
- Battery. Energy goes to the screen, processor, radios and sensors such as GPS. Typical defects are work that does not stop: location updates after the user leaves the map, wake locks that keep the processor awake, polling every few seconds, and background jobs that ignore Android’s Doze idle mode or iOS Low Power Mode. Each platform’s battery settings show every app’s share.
- Network conditions. Users move between Wi-Fi, fast and slow mobile data and no signal. Latency (the time of each round trip) punishes screens that make many requests one after another; low bandwidth punishes large images and responses; packet loss forces lost data to be sent again, which adds more delay.
| Measure | What to record | How: Android, then iOS | Warning sign |
|---|---|---|---|
| Startup time | Cold, warm and hot start; TTID and TTFDmedian of 10 runs on a release build | Android: am start -W after am force-stopiOS: the App Launch template in Instruments | Cold start of 5 s or moreexcessive in Android vitals, or slower than the last release |
| Frame rate | Frame time against the budget16.7 ms at 60 Hz, 8.3 ms at 120 Hz | Android: janky frames and percentiles in dumpsys gfxinfoiOS: Animation Hitches in Instruments | Visible stutter while scrollingfrozen frames over 700 ms |
| Memory | Peak during a flow and return to baselinerepeat the same flow 20 times | Android: dumpsys meminfo, Android Studio memory profileriOS: Allocations and Leaks in Instruments | Growth with every repetitionapp restarted after returning from the background |
| Battery | Drain in a fixed session; background work30 minutes on battery, then an hour in the background | Android: dumpsys batterystatsiOS: the energy gauge in Xcode; both: per-app share in Settings | Location, wake locks or polling still active after leaving the screen |
| Network conditions | Time to a usable screen and data used, per profilegood Wi-Fi, typical 4G, poor 3G-like | Android: emulator speed and delay, or a throttling proxyiOS: Network Link Conditioner profiles | Many sequential requests; large responsesa spinner that never ends |
Startup and frozen-frame thresholds from Android vitals; frame budgets follow from the refresh rate. Compare every result with the previous release on the same device.
Measuring reliably: conditions before numbers
A number means something only with its conditions. Debug builds carry extra checks and can be much slower than what users install, so measure a release build. Use real devices, starting with the lowest-end one you support; an emulator runs on your computer’s processor and says little about a budget phone. Keep the state identical between runs: same account and data, other apps closed, power saving off, and let a hot phone cool down, because heat slows the processor.
Repeat each measurement (ten runs is a common choice) and report the median, the middle value of the sorted runs, with its spread from the lowest to the highest run; one run proves nothing. Compare with the previous release measured the same way: that shows a regression even where nobody set a target. Your lab numbers reproduce problems and confirm fixes; the field data in Android vitals and the Xcode Organizer shows what real users experience across many devices.
# Android: release build on a real device, adb from the SDK platform tools
adb shell am force-stop com.example.shop # the next launch is a cold start
adb shell am start -W -n com.example.shop/.MainActivity
# TotalTime: 1873 # ms to the first frame, about TTID
adb logcat -d | grep "Fully drawn" # TTFD, if the app reports it
# ... Fully drawn com.example.shop/.MainActivity: +2s105ms
adb shell dumpsys gfxinfo com.example.shop reset # clear the frame statistics
# ...scroll the list for 30 seconds...
adb shell dumpsys gfxinfo com.example.shop # janky frames, frame percentiles
adb shell dumpsys meminfo com.example.shop # memory used by the app process
adb shell dumpsys batterystats --reset # start a battery session
# ...unplug the phone, use the app for 30 minutes, plug it back in...
adb shell dumpsys batterystats com.example.shop # the app's use since the reset
# iOS: Xcode > Product > Profile opens Instruments with templates such as
# App Launch, Time Profiler, Animation Hitches, Allocations, Leaks, NetworkTesting under real network conditions
The scenarios guide showed how to switch networks and force offline states. For performance the questions are how long the user waits and how much data the app spends on a weak connection. Use a fixed set of profiles so results stay comparable, for example good Wi-Fi, typical 4G and a poor 3G-like profile with high latency and some loss. The Android emulator sets speed and delay, iOS’s Network Link Conditioner offers profiles such as 3G, Edge and Very Bad Network, and a throttling proxy or a restricted test Wi-Fi covers real Android devices.
Latency adds up with sequential requests: six calls made one after another at 300 ms per round trip cost at least 1.8 s of network waiting alone. Bandwidth punishes size: a 2 MB response that nobody notices on office Wi-Fi takes seconds on a slow connection. Ask how many requests each key screen makes and how large they are, and check that responses are compressed and images arrive at the size the screen shows.
- Time from tap to usable screen on each profile, taken from a screen recording or the app’s own timing.
- Feedback while waiting: a loading state appears at once and the screen stays responsive.
- Timeouts and retries: a hanging request ends in a clear message, and retries do not multiply the traffic.
- API time measured separately, so you know whether the delay is the server, the network or the app.
Try it: Response Size Analyzer Try it: API Status Code Simulator Try it: Response Time Analyzer
Worked example: a restaurant list that stutters
A food delivery app is preparing version 3.2, and colleagues trying the release candidate say the restaurant list "feels heavy" on cheaper phones. The tester installs the release candidate and, for comparison, the current version 3.1.4 on the lowest-end supported Android phone (60 Hz display), signs in with the standard test account, scrolls the list to the end and back for 30 seconds, and collects frame and memory statistics. Three runs per version give the medians below.
The frame statistics confirm the complaint: the new version renders fewer frames in the same 30 seconds, and far more of them miss the budget (a 90th percentile of 38 ms means one frame in ten took 38 ms or longer). The network traffic shows a likely cause: each restaurant card now downloads a 600 KB photo that the phone scales down to a small thumbnail, and decoding and scaling large images costs processor time and memory, which delays frames.
- Verdict: a performance regression against 3.1.4 on every frame measure and on memory; janky frames went from 4.1 % to 27.8 %.
- Bug report title: "Restaurant list scroll: 27.8 % janky frames on low-end Android vs 4.1 % in 3.1.4", with device, build, steps, the table, a screen recording and an image URL with its size.
- The likely cause is for the developer to confirm; the expected fix is thumbnails delivered at display size.
- Confirmation testing repeats the same scenario on the same device and compares every row; then the scenario joins the performance checklist of every release.
Device: lowest-end supported Android phone, 60 Hz display, release builds
Scenario: restaurant list, scroll to the end and back for 30 s, median of 3 runs
3.1.4 (current) 3.2.0-rc1
Total frames rendered 1,710 1,240
Janky frames 4.1 % 27.8 %
90th percentile frame time 14 ms 38 ms
99th percentile frame time 31 ms 160 ms
Memory after scroll (meminfo) 160 MB 410 MB
Photo per restaurant card 45 KB 600 KBCommon mistakes
- Measuring on the developer’s flagship phone or an emulator and reporting the result as the app’s performance.
- Measuring a debug build and filing slowness that does not exist in the release build.
- Treating one run as the result; startup and frame numbers vary, so use several runs and the median.
- Calling every launch a cold start. If the process was still running, you measured a warm or hot start; stop it first.
- Testing memory and battery in five-minute sessions. Leaks and drain need repetition and time: the same flow twenty times, a 30-minute session, an hour in the background.
- Testing only on office Wi-Fi, or only checking that offline mode works, without measuring how long a user waits on a slow connection.
The QA perspective
On a team, mobile performance work starts with targets. Ask the product owner and developers for a budget per key journey, for example cold start to a usable home screen and smooth scrolling of the main list on the lowest-end supported device. Where nobody has a number, the previous release is the baseline, and a clear regression is reported as a defect.
Keep a short performance checklist for every release candidate: the same devices, build type, scenarios and network profiles, with results in a table next to the previous release and the raw output and recordings attached. After release, read Android vitals and the Xcode Organizer with the developers: users’ devices find problems your lab missed, and each one becomes a lab scenario you can repeat.
Practice exercise
Judge the performance results of a release candidate
You tested release candidate 5.4 of a shopping app against the current release 5.3 on the lowest-end supported Android phone, with release builds and the method from this lesson. For each row, decide whether it is a regression to report, a result within normal variation, or a reason to measure more. Then write the title of the most important bug report.
App: shopping, lowest-end supported Android phone, release builds, office Wi-Fi
Startup rows: median of 10 cold starts after force-stop, (min-max) in brackets
Measure 5.3 5.4
Cold start, first frame (TTID) 1.4 s (1.3-1.6) 1.5 s (1.3-1.7)
Cold start, home ready (TTFD) 2.1 s (1.9-2.4) 3.9 s (3.6-4.3)
Janky frames, search results scroll 6 % 7 %
Memory before -> after 20x product 142 -> 147 MB 142 -> 236 MB
Battery used, 30-minute browse 4 % 4 %
Home screen requests / transferred 7 / 480 KB 19 / 2.6 MB- Compare each row of 5.4 with 5.3 and note the difference.
- Decide which differences are larger than the run-to-run variation, using the spread you were given.
- Link related rows: which result may explain another one?
- Write the bug report title with the measure, the numbers, the device and the comparison.
Show the model answer
TTID: 1.5 s against 1.4 s, and the spreads overlap (1.3–1.6 and 1.3–1.7). Within normal variation; no report.
TTFD: 3.9 s against 2.1 s, and the spreads do not overlap. This is a clear regression to report: the user waits 1.8 s longer for a usable home screen. The 5 s Android vitals limit does not clear it, because that limit is for the first frame and the previous release is the baseline here.
Home screen requests: 19 requests and 2.6 MB against 7 and 480 KB. This probably explains the TTFD regression and will be much worse on a slow connection, so measure TTFD on the poor 3G profile as well and attach both results.
Memory: after opening a product 20 times, 5.3 returns close to its baseline (+5 MB) while 5.4 keeps 94 MB more. Memory that grows with repetition points to a leak in the product screen; report it with the steps and ask the developer to analyse a heap dump (a snapshot of the objects in memory).
Janky frames 6 % against 7 %, and battery 4 % against 4 %: no meaningful change. The battery reading also moves in whole percent, so it can only show large differences; note it and move on.
Bug report title: "Cold start to usable home screen 3.9 s in 5.4 vs 2.1 s in 5.3 (median of 10, lowest-end Android)". A second report covers the memory growth on the product screen.
Frequently asked questions
What is the difference between a cold, warm and hot start?
A cold start creates the app’s process from nothing, so it is the slowest and the one to measure first. A warm start reuses part of what exists, such as a process that is still running, but recreates the screen. A hot start only brings an existing screen back to the front. Stop the process (for example with adb shell am force-stop) to be sure you are measuring a cold start.
Which device should I measure on?
Start with the lowest-end device you officially support, because problems appear there first and largest. Add one common mid-range device and, for frame rate, one device with a high-refresh display, where the frame budget is shorter. Emulators and simulators are useful for network profiles and functional checks, not for timing.
Can mobile performance checks be automated?
Partly. On Android, the Jetpack Macrobenchmark library measures startup and frame timing on a real device; on iOS, XCTest performance tests can measure launch time, memory and other metrics with XCTApplicationLaunchMetric and similar metric classes. Automated runs are good at catching regressions between builds; exploratory sessions on real devices and field data from users are still needed.
Is mobile performance testing the same as load testing the back end?
No. Load testing checks how the server behaves with many users at once. Mobile performance testing looks at one user’s device: startup, smoothness, memory, battery and waiting time on a real network. A slow API affects both, so measure its response time separately to know which side needs the fix.
References
- Android vitalsThe app-quality metrics Google Play collects from users' devices — user-perceived crash and ANR rates among the core vitals — and the bad-behaviour thresholds that affect an app's visibility.
- Certified Tester Mobile Application Testing (CT-MAT)ISTQB's specialist syllabus on mobile testing: app types, device and platform challenges, test types and tools.
- ISO/IEC 25010:2023 — Systems and software Quality Requirements and Evaluation (SQuaRE) — Product quality modelThe product quality model: functional suitability, performance efficiency, compatibility, interaction capability, reliability, security, maintainability and flexibility.
- XCTestApple's framework for unit, performance and UI tests of Xcode projects on iOS and other Apple platforms.
Practise with Mutqan tools
Analyze response size: raw vs compressed bytes, headers size, JSON structure weight and largest fields.
Get a public endpoint that returns any HTTP status code, with optional delay and custom JSON body, to test client error handling.
Measure an endpoint's response time over several samples: min, max, average, median and p95.
Convert between bytes, KB, MB, GB, TB (decimal SI) and KiB, MiB, GiB (binary IEC), plus bits.
Go deeper
Your progress on this topic
Not started
Mark the topic complete when you have studied it and done the exercise.
Sign in to track your progress
Mark topics complete, continue where you left off and unlock specialisation exams.