Skip to content
Mobile6 min read

Mobile performance: startup, battery, and the things that get you uninstalled

Nobody uninstalls an app for being 200ms slower than a competitor. They uninstall it for taking four seconds to open, for draining the battery, and for being too large to install on a phone that is already full — three problems that are measurable, fixable, and usually unmeasured.

Part ofMobile App Development

Mobile performance work tends to be directed at whatever is most visible to the team, on the newest device in the office, over office wifi. The problems that actually cost users are structural and show up on a three-year-old mid-range phone on a congested network — which is what most of an installed base is using.

Cold start is the metric that shapes the first impression

Cold start is the time from tap to usable content with no process already running. It is the most-repeated interaction in the product and the one that determines whether the app feels heavy.

CauseFix
Initialising every SDK at launchDefer everything not needed for the first screen
Blocking network call before first renderRender cached content, then refresh
Reading and parsing a large local file synchronouslyLoad lazily, or move it off the launch path
Deep dependency graph constructed eagerlyConstruct on demand
Heavy work in the application-level entry pointThis runs before anything is visible — keep it near-empty
Analytics and crash SDKs initialised seriallyCrash reporting first, the rest deferred

The right target depends on the device floor you support, not on the device you develop on. Measure on the slowest phone you claim to support and treat that number as the real one; on a current flagship almost any app starts acceptably.

Scrolling: the other thing people feel immediately

  • Recycle views rather than constructing per item; every list framework supports this and misuse is common
  • Fixed item heights where possible, since measuring variable heights during scroll is expensive
  • Decode images off the main thread, at the size they will be displayed — decoding a 4000px image into a 200px slot is the classic cause of stutter
  • Nothing synchronous in the per-item render path — no disk reads, no date parsing, no formatting that could be precomputed
  • Paginate. A list that loads three thousand rows to show twelve is slow to appear and expensive in memory

Image decoding deserves the emphasis. Serving correctly sized images from the server, or decoding to the display size, removes most list stutter in most applications — and it also reduces memory pressure, which is what causes the app to be killed in the background and therefore cold-start again.

Battery: the complaint you never see directly

Users rarely report battery drain; the operating system reports it for you, in a screen that names your app, and the response is an uninstall. It is almost always caused by a small number of specific behaviours.

BehaviourBetter
Polling the server on a timerPush notifications, or the platform's scheduled-work API
Continuous precise locationSignificant-change or geofence APIs, coarse where it suffices
Many small network requestsBatch them; the radio staying awake costs more than the bytes
Keeping a wake lock while idleHold it only for the work, and release it in every path
Animation running while off-screenStop it when the view is not visible
Background work on every app switchCoalesce, and let the platform schedule it

App size affects installs, and then updates

Download size determines who completes an install on mobile data and who gives up. Installed size determines who deletes your app when their phone fills up — which is a silent, unmeasurable loss.

  1. Ship per-device variants rather than one universal binary containing every architecture and density.
  2. Audit the dependency list. Libraries added for one function frequently bring a large payload with them.
  3. Compress and right-size bundled images, and prefer vectors for icons.
  4. Move large optional assets to on-demand download after install, rather than bundling them.
  5. Strip debug symbols from release builds while still uploading them for crash symbolication.
  6. Re-measure every release, because size grows silently and nobody notices a steady increase.

The last point is the discipline that matters. App size is a metric that only ever moves in one direction unless someone watches it, and a budget checked in the build — failing when the app grows past a threshold — is what keeps it honest.

Memory, because being killed looks like slowness

An app using a lot of memory is terminated when it goes to the background. The user returns expecting their state and gets a cold start from the beginning, which they experience as the app being slow and forgetful rather than as a memory problem.

  • Release bitmaps and caches when the app backgrounds or the system signals pressure
  • Bound every in-memory cache explicitly; an unbounded cache is a leak with a friendly name
  • Restore state properly on relaunch, so a termination is recoverable rather than a reset to the home screen
  • Watch for retained references to view controllers and activities, the most common leak on both platforms

Test on the devices people have

Test onNot only on
The oldest OS version you supportThe newest
A low-end device from three years agoThe team's current phone
A throttled, high-latency networkOffice wifi
A device with nearly full storageA clean test device
Low battery, with power saving activePlugged in, which disables throttling
An account with years of accumulated dataA fresh account with three records

The final row of that table is worth acting on. Applications are built and demonstrated against fresh accounts holding a handful of records, and they behave entirely differently for the customer with four years of history — who is, by definition, the one you least want to lose.

Questions

What makes an app slow to start?

Most often third-party SDK initialisation on the launch path. Each library added over the years contributes a little startup work, none individually noticeable, and the total is frequently most of the cold start. Measure the launch path per component before optimising your own code, defer everything not needed for the first screen, and render cached content rather than blocking on a network call.

What causes mobile battery drain?

Polling on a timer, continuous precise location, many small network requests, wake locks held while idle, and animations running off-screen. The radio is the expensive part rather than the data — it stays in a high-power state for seconds after each request, so twenty small requests over a minute can cost far more than one larger one. Batching and coalescing background work is usually the highest-value change available.

Does app size really matter?

Yes, twice. Download size determines who completes an install on mobile data, and installed size determines who deletes the app when their phone fills up — a silent loss you cannot measure. Ship per-device variants rather than a universal binary, move large optional assets to on-demand download, and check size as a build budget, since it only ever grows unless someone is watching.

Why does the app seem to forget where the user was?

Usually memory. An app using a lot of memory is terminated when backgrounded, so returning to it produces a cold start from the beginning — which users experience as slowness and forgetfulness rather than as a memory problem. Bound every in-memory cache explicitly, release bitmaps under pressure, and restore state properly on relaunch so termination is recoverable.

Want this applied to your systems?

Most of what is described here begins as an assessment: a fixed-scope review producing a prioritised written report you can act on with any supplier.

Prefer email? hello@kornspire.com