Background location na reálných telefonech: proč to v demu funguje a v provozu padá
V demu sledování polohy řidiče běží bez problémů. V provozu — telefon v kapse, appka na pozadí celý den — OS appku zabije a poloha zmizí. Jak to ošetřit správně.
V demu to funguje perfektně. GPS souřadnice přicházejí každé 3 sekundy, řidič na mapě se pohybuje plynule, dispečer vidí přesnou polohu. Demo trvá 20 minut, telefon leží na stole obrazovkou nahoru, appka je v popředí.
V provozu je to jinak. Směna trvá 8 hodin. Telefon je v kapse nebo na držáku v autě, obrazovka zhasnutá, appka na pozadí. Po 45 minutách přestane poloha přicházet. Po 2 hodinách dispečer vidí řidiče naposledy na parkovišti před supermarketem.
Toto není bug v kódu. Je to výsledek toho, jak moderní mobilní OS spravují baterii — a jak OEM výrobci na to navrší vlastní vrstvu.
Proč demo a produkce nejsou totéž
Když appka běží v popředí (foreground), OS jí dává plnou prioritu. GPS čip posílá updates, CPU se neuspí, síťové volání projde okamžitě. Tohle je stav dema.
Jakmile uživatel zmáčkne home tlačítko nebo zhasne obrazovku, přejde appka do background. OS začne šetřit baterií. Konkrétně to znamená:
- Android Doze mód (od Android 6): po několika minutách bez pohybu nebo interakce OS zastaví síťový přístup a odloží alarmy. GPS přijímá, ale pakety se nedostávají na server.
- Background App Refresh throttling (iOS): appka na pozadí dostane CPU čas jen občas, na minuty. Mezi tím neběží nic.
- Foreground service na Androidu problém částečně řeší — ale jen částečně. Drží process naživu, ale OEM battery manager ho může stejně zabít.
Doze mód a foreground service jsou standard Android. Nad tím každý výrobce přidá vlastní vrstvu.
Per-OEM realita: Samsung, Xiaomi, Huawei, OnePlus
Tady to začne být nepříjemné. Každý velký Android výrobce má vlastní battery management systém, který funguje jinak a vyžaduje jiný permission flow.
Samsung (One UI) má "Adaptive battery" a "App power management". Appka musí být explicitně vyjmuta z "Sleeping apps" — buď uživatelem ručně, nebo přes REQUEST_IGNORE_BATTERY_OPTIMIZATIONS intent. Ale Samsung k tomu v novějších One UI verzích přidal ještě "Background app limits", které funguje nezávisle. Musíte ošetřit obojí.
Xiaomi (MIUI) je notoricky agresivní. MIUI má "Battery saver" a k tomu "Autostart" oprávnění, bez kterého appka po restartu telefonu nikdy nespustí foreground service — ani kdyby OS nezaspal. Povolení ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS nestačí. MIUI deep link na vlastní Battery Settings stránku musí otevřít appka sama a navigovat uživatele krok za krokem.
Huawei (EMUI / HarmonyOS) má "Protected apps" seznam a "App launch" nastavení. Bez zapsání do Protected apps EMUI foreground service zastaví během 5–10 minut po zhasnutí obrazovky. Na starších EMUI verzích (8, 9) to neexistovalo, na novějších HarmonyOS je situace jiná znovu.
OnePlus (OxygenOS) v minulých verzích notoricky ignoroval foreground service notification a appku zabíjel. OxygenOS 14+ situaci zlepšil, ale "Battery optimization" dialog nestačí — je potřeba nasměrovat na "Advanced optimization" v Battery settings.
Tohle je per-OEM práce. Nelze napsat jeden kód a spoléhat se, že funguje stejně na Samsungu i Xiaomi.
Co se stane, když OS appku zabije
Foreground service má jeden klíčový příznak: START_STICKY. Říká OS, aby service po zabitá restartoval. Ale restart trvá sekundy až desítky sekund. Mezitím přišly GPS souřadnice, které nikdo nezaznamenal.
Horší varianta: OS appku zabije a nerestartuje — kvůli memory pressure, nebo proto že MIUI Autostart nemá povolení. V tom případě se service nespustí vůbec, dokud uživatel appku ručně neotevře.
Jak to detekovat? Heartbeat. Appka každých N sekund pošle ping na server. Pokud ping nepřijde déle než 2× interval, server ví, že je appka mrtvá — ne offline. Tahle informace má jiný business dopad než "řidič nemá signál".
Na straně appky: při startu kontrolujeme timestamp poslední úspěšné GPS souřadnice. Pokud je starší než 5 minut a appka právě nastartovala, víme, že byl restart po zabití. Log to, reportuj do monitoring systému.
Offline queue a recovery
GPS souřadnice, které přijdou v době bez konektivity, nelze zahodit. Dispečer potřebuje vědět, kudy řidič jel — i když v tunelu neměl signál.
Řešení: lokální fronta souřadnic. Každá souřadnice se uloží do lokálního úložiště (SQLite, Hive, SharedPreferences — závisí na platformě). Separate worker thread periodicky zkouší souřadnice odeslat na server. Při úspěchu je smaže. Při failureu je nechá a zkusí to znovu s exponenciálním backoffem.
Fronta musí mít horní limit — nelze akumulovat souřadnice donekonečna. Rozumný limit je 2–4 hodiny záznamu při normální frekvenci. Starší záznamy se zahodí, ale zaznamená se, že data za toto období chybí.
Recovery po restartu: při spuštění appky worker zkontroluje, jestli fronta obsahuje neposlané záznamy, a odešle je v dávkách. Server musí zvládat příjem out-of-order souřadnic a idempotentní zápis (stejná souřadnice odeslaná dvakrát nesmí vytvořit duplikát).
Online/offline semantika a heartbeat
"Řidič je offline" je ambivalentní stav. Může znamenat:
- Řidič záměrně vypnul dostupnost (přestávka, konec směny)
- Appka ztratila GPS signál (tunel, garáž)
- Appka ztratila síťové připojení
- OS appku zabil na pozadí
- Telefon se vybil
Pro dispatch systém jsou to různé situace s různými akcemi. Heartbeat systém je rozlišuje:
- GPS pauza bez výpadku heartbeatu = signál chybí, ale appka běží
- Heartbeat chybí, ale poslední zpráva byla "going offline" = záměrné odpojení
- Heartbeat chybí bez zprávy = appka mrtvá nebo baterie vybitá
Heartbeat interval musí být kompromis. Každých 30 sekund je příliš časté — žere baterii a přetěžuje server při 100 připojených řidičích. Každých 5 minut je příliš řídké — 5 minut nevíte, jestli je appka naživu. Rozumný kompromis je 60–90 sekund, s tím že server označí řidiče jako "suspect offline" po 3 propásnutých heartbeatech (2,5–4,5 minuty latence).
Permission flow: navrhnout předem, ne dodělat
Permission flow pro background location musí být navržen jako součást onboardingu, ne jako afterthought. Uživatel musí pochopit, proč appka potřebuje polohu "vždy" (not just while using the app), jinak to v systémovém dialogu zamítne.
Na Androidu to znamená postupný request: nejdřív "While using", pak vysvětlení proč "Always", pak request na "Always" přes nastavení (systémový dialog neumožňuje přímý grant "Always" pro background — uživatel musí jít do Settings). Tento flow vymysleli Google záměrně jako friction. Nelze to obejít, jen to dobře vysvětlit.
Na iOS: "When In Use" → CoreLocation usage description → request upgrade na "Always" → description proč. iOS 14+ přidal "Precise location" / "Approximate location" toggle — sledování polohy řidiče potřebuje Precise, a uživatel to musí vědomě schválit.
Po udělení "Always" nezapomínat znovu zkontrolovat při každém spuštění. Uživatel může oprávnění kdykoli odebrat v Settings — bez notifikace appce.
Telemetrie: bez dat nemáte problém, máte jen stížnosti
Appka bez telemetrie vám dává jen zprávy řidičů: "poloha nefungovala". Nevíte kdy, kde, jaký telefon, jaký Android, jestli šlo o Doze, MIUI kill nebo výpadek sítě.
Minimální telemetrie pro background location:
- Počet GPS updates za hodinu (anomálie = Doze nebo kill)
- Počet heartbeat failures a jejich délka
- Restart events s příznakem "po kill" vs "záměrný start"
- Verze OS a model telefonu u každého eventu
S těmito daty vidíte: Xiaomi MIUI 14 tvoří 60 % kill eventů i když je 20 % flotily. Samsung One UI 6 má problém po 23:00 kdy agresivněji spí. Tohle jsou reálná čísla, která jsme viděli na Carivio a TaxiLight.
Bez telemetrie problém existuje, ale nemáte páku ho prioritizovat ani dokázat, že fix fungoval.
Upřímné shrnutí
Background location v dispatch appce není one-size-fits-all problém. Je to součet: OS battery management + per-OEM vrstva + permission flow + offline queue + heartbeat + kill detection + telemetrie.
Demo funguje, protože eliminuje většinu z toho. Appka je v popředí, telefon na stole, session trvá 20 minut. Produkce eliminovat neumí.
Tohle se nedá vyřešit jedním nastavením. Je to inženýrská práce, která musí být navržena předem — ne dodělávána po stížnostech řidičů.
Pokud stavíte dispatch systém nebo řidičskou appku a nechcete zjišťovat problémy až v provozu, napište nám — projdeme architekturu a řekneme, kde jsou díry.
FAQ
Je background location na iOS spolehlivější než na Androidu?
Na iOS je situace konzistentnější — Apple dává CLLocationManager relativně předvídatelné chování a neexistuje ekosystém OEM battery optimalizací jako na Androidu. Ale ani iOS není imunní: při přepnutí do background módu dostanete omezený čas, "always" permission vyžaduje víc kroků schválení od uživatele, a Low Power Mode lokaci throttluje. Na Androidu je problémů víc, ale jsou alespoň zdokumentované a řešitelné per-OEM.
Stačí nastavit foreground service a je vystaráno?
Foreground service je nutná podmínka, ne dostatečná. Zabrání Androidu zabít process při normálním paměťovém tlaku. Ale neochrání vás před uživatelem, který "Force stop" zmáčkne ručně, před MIUI agresivním správcem baterií, ani před Doze módem pokud nemáte správnou wake lock konfiguraci. Foreground service je základ — na vrchu potřebujete kill detection, offline queue a heartbeat.
Musím implementovat per-OEM ošetření pro každého výrobce zvlášť?
Ano, pokud chcete spolehlivost v produkci. Nelze napsat jeden kód a spoléhat se, že funguje všude stejně. Prakticky to znamená: detekovat výrobce za runtime, nasměrovat uživatele na správné systémové nastavení přes deep link (na dontkillmyapp.com jsou zdokumentované URL schemata pro MIUI, EMUI, OxygenOS), a testovat na fyzických zařízeních každého výrobce, ne jen na emulátoru. Emulátory battery optimalizace nesimulují.