An app we inherited last year had a 4.1% crash-free-users problem — meaning 1 in 25 sessions ended in a crash — and the previous team swore the code was fine. It was fine, on their Pixel 7. On a Xiaomi Redmi running MIUI, the background sync was dead within an hour of the screen turning off, so the app showed three-day-old data and the one-star reviews wrote themselves. Nobody had ever installed a build on a non-Google phone. That is the whole story of most Android projects that go quiet: the idea shipped, the platform didn't.
We build Android for B2B clients, and the timeline splits the same way every time. The happy-path screens — the ones a demo shows — take about a week. The remaining 80% is the platform fighting you: process death, Doze, foreground-service restrictions, OEM battery managers, R8 stripping classes out of your release build, and a Play review bot that rejects you the Friday before launch. If your vendor priced only the week, you pay for the 80% in reviews. Here's where a team that has actually shipped Android at scale spends its effort, and how to tell them apart from a bootcamp graduate with a portfolio.
Background work is where cheap apps die
The most common failure we inherit is an app that assumes it can run whenever it likes. Since Android 8 the system kills cached background processes on memory pressure within minutes; Doze batches your network access into short maintenance windows once the screen's been off and stationary; and from Android 12 you can't even start a foreground service from the background in most cases. A shop that hasn't internalized this builds sync with a bare `Thread` and a `while(true)` — flawless until the phone locks, then the data silently freezes and the user blames you, not the OS.
The boring, correct answer: WorkManager for deferrable guaranteed work, with `Constraints` for network and battery plus exponential backoff that respects Doze instead of hammering a dead radio; a foreground service with a real notification only while the user is actively watching a transfer or a workout; and expedited work when you genuinely need it now. Then there's the OEM war on top. Samsung, Xiaomi, Oppo, Vivo and Huawei ship battery managers that ignore the AOSP rules and kill 'unrecognized' apps outright — Xiaomi's aggressive by default, Samsung's after a few days idle. If your vendor has never opened dontkillmyapp.com or requested `REQUEST_IGNORE_BATTERY_OPTIMIZATIONS` on a real MIUI device, they'll ship you an app that works on Google's reference hardware and dies on half the phones in India, Southeast Asia and Latin America.
Process death is a normal event, not an edge case
Background your app while it's low in the LRU list and something else wants the RAM, and the OS kills the whole process — but keeps your task in Recents. When the user returns, `onCreate` runs again with a `savedInstanceState` bundle, your static fields are gone, and your Activity is rebuilt into a state your code never anticipated. Apps built without this lose the half-filled form, NPE on a singleton that no longer exists, or bounce back to the login screen every time someone takes a call mid-checkout. It reads as a flaky, unreproducible bug. It's the platform working exactly as documented.
The fix is a saved-state discipline template mills skip because it never shows up in a demo: `SavedStateHandle` in every ViewModel, a single source of truth for navigation state, and a persistence layer so the source of truth is Room or DataStore, not RAM. Then you test it deliberately — Developer Options → 'Don't keep activities' turned on, run the full flow. When we audit an Android codebase, that toggle is the fastest tell we have. If flipping it turns the app into a crash reel, the previous team never understood the lifecycle, and everything else they built stands on the same sand.
Fragmentation is a testing problem, not a code problem
There is no 'the Android phone.' There are thousands of screen sizes, a live spread from Android 9 through 15 in real user bases, notches, foldables that fire a configuration change mid-fold, and OEMs who patch the framework so a standard API behaves differently. You don't code your way to confidence here — you test your way there. A serious shop runs each build across Firebase Test Lab or a physical device farm, sets `minSdk` from your actual install-base analytics rather than a guess, and treats a 2GB-RAM Android Go device as a first-class target, because that's what a field technician or a warehouse worker is actually holding.
This is also where R8 earns its keep and causes its scariest bugs. Shrinking and obfuscating the release build is non-negotiable for size and basic tamper resistance, but it's exactly the step that strips a model class your Gson or Moshi reflection needs — so the app is perfect in `debug` and crashes only in the signed `release` your users download. A team that knows Android writes tight `@Keep` and `-keep` rules, runs its full QA pass against the actual release variant (not debug), and uploads the Crashlytics deobfuscation mapping with the build, so the first crash report is readable instead of a stack of `a.b.c()`.
The Play Store is a system you engineer around
Correct code isn't the finish line — Google's review and policy machine is. Play raises the required `targetSdk` every August and stops surfacing updates to non-compliant apps; the Data Safety form, the background-location and full-screen-intent justifications, and the in-app account-deletion requirement will get a working app rejected or pulled if the declarations don't match your manifest permissions. We've watched a launch slip two weeks because a vendor requested `ACCESS_BACKGROUND_LOCATION` for a feature that only needed foreground location, couldn't write the justification, and the review bot said no three times.
A real partner engineers the release pipeline from day one: signed App Bundles with Play App Signing, staged rollouts that reach 1% then 5% then 20% so a bad build is halted before it hits everyone, internal and closed testing tracks wired to your CI, and permissions scoped to exactly what ships. Done right, an update is a routine Tuesday. Done wrong, every release is a coin flip you find out about after your users do — via a rating that takes months to climb back.
How to actually vet the company
Skip the portfolio screenshots — anyone can show a clean UI. Ask the questions only a working Android team answers well. How do you survive process death — where does your state actually live? What's your plan for Xiaomi and Samsung killing background work? Do you QA the R8-shrunk release variant or just debug? What's your targetSdk cadence and staged-rollout percentages? A shop that answers crisply, naming the tradeoffs, has shipped at scale. One that pivots to 'we use the latest Jetpack' and 'our developers are certified' has not — and you'll learn the difference three months post-launch when the reviews start.
Custom Android is worth paying for precisely because of that invisible 80%. The visible app is a commodity; reliability under real-world conditions is the product. Pick the team that can describe the failure modes before you've hit them, because the alternative is meeting each one live, in production, with your name on the listing.