Úlohy na pozadí v .NET MAUI riešite pomocou dvoch natívnych plánovačov: WorkManager na Androide a BGTaskScheduler na iOS. MAUI neposkytuje jednotnú cross-platform abstrakciu, takže v projekte Platforms/Android musíte implementovať Worker triedu a v Platforms/iOS zaregistrovať identifikátory úloh cez BGTaskScheduler.Shared.Register. V tomto sprievodcovi nájdete kompletnú implementáciu, ktorá rieši DI v killed-state, obmedzenia iOS background fetch (cca 30 sekúnd) a Doze mode na Androide.
.NET MAUI 10 nemá zabudovanú cross-platform abstrakciu pre úlohy na pozadí, takže musíte volať natívne API cez partial classes alebo dependency injection.
Na Androide používate WorkManager s PeriodicWorkRequest (minimum 15 minút) alebo OneTimeWorkRequest s constraints (sieť, batéria, charging).
Na iOS používate BGTaskScheduler s BGAppRefreshTaskRequest (krátke úlohy ~30 s) alebo BGProcessingTaskRequest (dlhé úlohy s podmienkou nabíjania).
iOS nezaručí presné časovanie. Systém spúšťa úlohy oportunisticky podľa správania používateľa, sily signálu a stavu batérie.
Pre prístup k službám registrovaným v MauiProgram.cs (SQLite, HttpClient, logger) musíte vo Worker triedach použiť IPlatformApplication.Current.Services.
Foreground service s notifikáciou je jediný spoľahlivý spôsob, ako bežať bez prerušenia na Androide 14+ s prísnymi pravidlami battery optimization.
Prečo .NET MAUI nemá jednotné API pre úlohy na pozadí?
Honestly, toto je jeden z prvých momentov, kedy tímy pri migrácii z Xamarin.Forms narazia na realitu cross-platform vývoja. V mojej praxi sa tá otázka opakuje pri každom kickoffe: prečo MAUI Essentials úmyselne neposkytuje univerzálnu službu pre plánovanie úloh na pozadí?
Dôvod je jednoduchý. Operačné systémy iOS a Android pristupujú k bežaniu kódu mimo aktívnej aplikácie diametrálne odlišne, a akákoľvek "spoločná" abstrakcia by buď bola zavádzajúca, alebo by stratila platformové garancie, ktoré sú v produkcii kritické.
Android od verzie 12 (API 31) silne uprednostňuje WorkManager Jetpack komponent, ktorý poskytuje deklaratívne constraints (sieťová konektivita, úroveň batérie, stav nabíjania), perzistentnú frontu, exponenciálny backoff a integráciu s Doze mode. iOS na druhej strane zámerne neposkytuje žiadnu garanciu, kedy alebo či úloha vôbec pobeží. BGTaskScheduler framework dáva systému plnú kontrolu a aplikácia dostane len krátke okno (rádovo desiatky sekúnd) podľa toho, ako často ju používateľ otvára.
Praktický dôsledok pre MAUI architektúru je jasný: kód, ktorý sa má vykonať na pozadí, musíte zapuzdriť do platform-specific implementácie a vystaviť ho cez rozhranie (napríklad IBackgroundScheduler) registrované v MauiProgram.cs. Logika synchronizácie samotnej (HTTP volania, SQLite zápisy) môže byť v zdieľanom .NET Standard kóde, ale spúšťač musí byť natívny. Podobný prístup používame pri integrácii natívnych runtime služieb v článku push notifikácie v .NET MAUI, kde tiež deklarujete spoločný interface a dva natívne handlery.
Android: WorkManager v .NET MAUI krok za krokom
WorkManager je odporúčaná Jetpack knižnica pre akúkoľvek odložiteľnú prácu na pozadí na Androide. V .NET MAUI projekte ju nemusíte explicitne pridávať cez NuGet, pretože bindings sú súčasťou balíka Xamarin.AndroidX.Work.Runtime, ktorý je tranzitívnou závislosťou MAUI workloadu. Stačí pridať dve triedy: vlastnú Worker triedu a registráciu v MainActivity alebo v OnCreate aplikácie.
Tu je minimálny príklad pre periodickú synchronizáciu dát zo SQLite databázy do REST API každých 30 minút. Všimnite si constraint NetworkType.Connected, ktorý zaručí, že úloha sa nespustí, ak zariadenie nemá internet:
// Platforms/Android/Workers/SyncWorker.cs
using Android.Content;
using AndroidX.Work;
using Microsoft.Extensions.DependencyInjection;
namespace MyApp.Platforms.Android.Workers;
public class SyncWorker : Worker
{
public SyncWorker(Context context, WorkerParameters workerParams)
: base(context, workerParams) { }
public override Result DoWork()
{
try
{
var services = IPlatformApplication.Current?.Services
?? throw new InvalidOperationException("DI kontajner nie je dostupný");
var syncService = services.GetRequiredService<ISyncService>();
var logger = services.GetRequiredService<ILogger<SyncWorker>>();
logger.LogInformation("Začínam synchronizáciu na pozadí");
syncService.SyncPendingChangesAsync().GetAwaiter().GetResult();
logger.LogInformation("Synchronizácia dokončená");
return Result.InvokeSuccess();
}
catch (Exception ex)
{
// WorkManager automaticky aplikuje exponenciálny backoff
return Result.InvokeRetry();
}
}
}
Plánovanie úlohy z MAUI vrstvy realizujete cez rozhranie. Implementácia volá WorkManager.GetInstance(context).EnqueueUniquePeriodicWork(...), pričom kľúč "sync-pending-changes" zaručí, že duplicitné požiadavky sa zlúčia a nevytvoríte si stovku frontových úloh pri každom štarte aplikácie:
// Platforms/Android/Services/AndroidBackgroundScheduler.cs
public class AndroidBackgroundScheduler : IBackgroundScheduler
{
public Task SchedulePeriodicSyncAsync(TimeSpan interval)
{
var constraints = new Constraints.Builder()
.SetRequiredNetworkType(NetworkType.Connected!)
.SetRequiresBatteryNotLow(true)
.Build();
var request = PeriodicWorkRequest.Builder.From<SyncWorker>(interval)
.SetConstraints(constraints)
.SetBackoffCriteria(BackoffPolicy.Exponential,
TimeSpan.FromMinutes(1))
.Build();
WorkManager.GetInstance(Platform.AppContext)
.EnqueueUniquePeriodicWork(
"sync-pending-changes",
ExistingPeriodicWorkPolicy.Keep!,
(PeriodicWorkRequest)request);
return Task.CompletedTask;
}
}
iOS: BGTaskScheduler, registrácia, plánovanie a obmedzenia
Na iOS prevláda zásadne odlišný mentálny model. Aplikácia neplánuje "spusti tento kód o 30 minút", namiesto toho hovorí systému "rád by som čoskoro spustil úlohu typu X", a iOS rozhodne kedy (alebo či vôbec) na základe heuristiky o správaní používateľa. Apple nedáva časové garancie. Z mojej skúsenosti pri produkčných aplikáciách s desiatkami tisíc inštalácií reálna frekvencia spúšťania BGAppRefreshTask kolíše medzi 1× za hodinu (aktívni používatelia) a 1× za 2 dni (pasívni používatelia).
Príprava má tri kroky: pridanie identifikátorov do Info.plist, registrácia handlerov pri štarte aplikácie a submitnutie požiadavky. Identifikátory musia byť reverse-DNS formát a presne sa zhodovať na všetkých troch miestach:
Registrácia handlera prebieha v AppDelegate.FinishedLaunching. Kritické je urobiť to pred tým, ako sa aplikácia stane "active", inak iOS pri prvom volaní úlohy spadne s výnimkou. V .NET MAUI 10 to znamená v MauiProgram.CreateMauiApp alebo cez vlastný UIApplicationDelegate:
// Platforms/iOS/Services/IOSBackgroundScheduler.cs
using BackgroundTasks;
using Foundation;
using UIKit;
public class IOSBackgroundScheduler : IBackgroundScheduler
{
private const string RefreshId = "com.myapp.sync.refresh";
public void Register()
{
BGTaskScheduler.Shared.Register(
RefreshId,
dispatchQueue: null,
handler: HandleRefresh);
}
private void HandleRefresh(BGTask task)
{
// 1. Naplánuj ďalšie spustenie, inak sa už nikdy nespustí
ScheduleNextRefresh();
var cts = new CancellationTokenSource();
task.ExpirationHandler = () => cts.Cancel();
Task.Run(async () =>
{
try
{
var services = IPlatformApplication.Current!.Services;
var sync = services.GetRequiredService<ISyncService>();
await sync.SyncPendingChangesAsync(cts.Token);
task.SetTaskCompleted(success: true);
}
catch (OperationCanceledException)
{
task.SetTaskCompleted(success: false);
}
});
}
public Task SchedulePeriodicSyncAsync(TimeSpan interval)
{
ScheduleNextRefresh();
return Task.CompletedTask;
}
private void ScheduleNextRefresh()
{
var request = new BGAppRefreshTaskRequest(RefreshId)
{
EarliestBeginDate = NSDate.FromTimeIntervalSinceNow(15 * 60)
};
BGTaskScheduler.Shared.Submit(request, out var error);
}
}
Ako otestovať BGTaskScheduler bez čakania na iOS
Apple poskytuje LLDB príkaz, ktorý umelo spustí background task v Xcode debugger. Pripojte zariadenie cez USB, spustite aplikáciu, prepnite ju do pozadia, a v Xcode debug console napíšte:
e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"com.myapp.sync.refresh"]
Ako sprístupniť DI služby vo Worker triedach
Najčastejšia chyba, ktorú vidím na fórach (známa otázka na Microsoft Q&A), je predpoklad, že IPlatformApplication.Current.Services bude funkčné aj keď je aplikácia v killed state. Realita je iná. Keď WorkManager spustí Worker po reboot zariadenia, aplikačný proces ešte neexistuje a MAUI App trieda nebola inicializovaná. Android síce vytvorí Application objekt (kvôli android:name="...MainApplication" v manifeste), ale MauiProgram.CreateMauiApp sa nezavolá automaticky.
Riešením je v MainApplication.OnCreate explicitne zavolať bootstrap kód, ktorý postaví len DI kontajner, bez UI vrstvy. V .NET MAUI 10 to vyzerá takto:
// Platforms/Android/MainApplication.cs
[Application]
public class MainApplication : MauiApplication
{
public MainApplication(IntPtr handle, JniHandleOwnership ownership)
: base(handle, ownership) { }
public override void OnCreate()
{
base.OnCreate();
// Vynúti vytvorenie DI kontajnera aj pri spustení Worker triedy
// v killed-state. Bez tohto by IPlatformApplication.Current bolo null.
_ = IPlatformApplication.Current?.Services;
}
protected override MauiApp CreateMauiApp() => MyApp.MauiProgram.CreateMauiApp();
}
Pre prácu s lokálnou databázou v rámci úloh na pozadí pozrite môj kompletný sprievodca SQLite v .NET MAUI. Repository vzor tam popísaný funguje rovnako vo Worker kontexte. Ak na pozadí voláte aj backend API, oplatí sa preštudovať aj REST API s odolnou servisnou vrstvou, najmä kvôli retry politikám pri prerušenej sieti.
Kedy použiť foreground service namiesto WorkManager
WorkManager je vhodný pre odložiteľnú prácu (synchronizácia dát, upload fotiek, periodický prieskum aktualizácií). Ak ale potrebujete kontinuálne bežiaci kód (sledovanie GPS, prehrávanie hudby, hovor cez VoIP), musíte použiť foreground service s perzistentnou notifikáciou. Android 14 (API 34) priniesol ďalšie sprísnenie: foreground service musíte typovať (foregroundServiceType="location", "dataSync", "mediaPlayback" atď.) a niektoré typy vyžadujú runtime permission alebo deklaráciu v manifeste.
Vlastnosť
WorkManager
Foreground Service
Vhodné pre
Odložiteľná práca, sync, upload
Kontinuálne sledovanie, prehrávanie
Notifikácia pre používateľa
Žiadna
Povinná, perzistentná
Minimálny interval
15 minút (periodické)
Žiadne obmedzenie, beží spojito
Doze mode
Rešpektuje, čaká na maintenance window
Vyňatý z Doze
Killed-state spúšťanie
Áno, automaticky systémom
Nie, musí byť živý proces
Battery optimization
Áno, OS môže odložiť
Vyňatý, ak má notifikáciu
Android 14 type
Nepotrebné
Povinné (foregroundServiceType)
Testovanie a debugovanie úloh na pozadí
Testovať background úlohy bez správneho nastavenia debuggovania je frustrujúce. Viete len, či sa kód spustil alebo nie. Odporúčam tri praktiky, ktoré som zaviedol na všetkých projektoch po jednom dramatickom incidente, kedy sa sync úloha v produkcii spúšťala raz za tri dni namiesto deklarovaných 30 minút.
1. Štruktúrovaný logging s perzistenciou. Background úlohy nemajú konzolu, nevidíte ich v adb logcat, pokiaľ aktívne nepripájate zariadenie. Logujte do SQLite tabuľky BackgroundTaskRuns s timestampom, výsledkom a trvaním. Pri ďalšom otvorení aplikácie zobrazíte priebeh v dev menu.
2. Crash reporting integrovaný vo Worker triede. Neošetrené výnimky vo Worker triedach Android pohltí a nikdy o nich neviete. Obaľte celé DoWork() try/catch blokom a explicitne ich preposielajte do Sentry alebo Firebase Crashlytics. Podrobne sa tomu venujem v článku crash reporting v .NET MAUI.
3. ADB simulácia WorkManager constraints. Príkaz adb shell cmd jobscheduler run -f <package> 999 spustí naplánovanú prácu okamžite, bez čakania na splnenie constraints. Pre BGTaskScheduler je ekvivalent v Xcode debug console uvedený vyššie.
Bežné problémy: Doze mode, battery optimization, killed state
Prečo úloha na pozadí nebeží na Xiaomi/Huawei zariadeniach?
Čínski výrobcovia (Xiaomi, Huawei, Oppo, Vivo) štandardne agresívne zabíjajú aplikácie po zatvorení a ignorujú WorkManager garancie. Jediné spoľahlivé riešenie je naviesť používateľa, aby manuálne vypol "battery saver" pre vašu aplikáciu cez Settings.ActionRequestIgnoreBatteryOptimizations intent. Webová stránka dontkillmyapp.com dokumentuje špecifické workaroundy pre jednotlivých výrobcov.
iOS úloha sa zaregistruje, ale BGTaskScheduler.Submit vráti chybu
Najčastejšia príčina: identifikátor v Info.plist nezhoduje s reťazcom v Register a Submit. Po druhé, simulator BGTaskScheduler nepodporuje, takže testujte vždy na fyzickom zariadení. Po tretie, ak aplikáciu používateľ zabije swipe-up gestom v App Switcher, iOS od verzie 16 zastaví všetky background tasks pre danú aplikáciu až do ďalšieho manuálneho otvorenia.
Doze mode a App Standby Buckets
Android 6+ má Doze mode, ktorý odkladá background úlohy, keď je zariadenie nehnuté a obrazovka vypnutá. Android 9+ pridal App Standby Buckets. Vaša aplikácia spadne do "rare" bucketu po niekoľkých dňoch neaktivity a WorkManager úlohy sa môžu spúšťať raz za 24 hodín. Riešenie: minimalizujte sieťové volania (batch), používajte setRequiresDeviceIdle(false) len keď to skutočne potrebujete.
Často kladené otázky
Môžu úlohy na pozadí bežať v .NET MAUI, keď je aplikácia úplne zatvorená?
Áno na Androide cez WorkManager. Systém spustí váš Worker aj po reboot zariadenia, ak ste úlohu predtým naplánovali a MainApplication.OnCreate inicializuje DI kontajner. Na iOS čiastočne: BGTaskScheduler aplikáciu prebudí, ale len ak ju používateľ nezabil swipe-up gestom v App Switcher.
Aký je rozdiel medzi BGAppRefreshTask a BGProcessingTask na iOS?
BGAppRefreshTask je krátka úloha (~30 sekúnd) určená na rýchle obnovenie obsahu, napríklad stiahnutie najnovších správ. BGProcessingTask je dlhá úloha (až niekoľko minút), ktorú systém spúšťa typicky v noci pri nabíjaní, vhodná pre indexáciu, ML tréning na zariadení alebo bulk synchronizáciu.
Prečo WorkManager v .NET MAUI nedodržiava môj 5-minútový interval?
Minimum pre PeriodicWorkRequest je systémom vynútených 15 minút. Kratšie intervaly Android ticho povýši. Navyše Doze mode a App Standby Buckets môžu interval natiahnuť aj na niekoľko hodín pri neaktívnej aplikácii. Pre kratšie intervaly použite OneTimeWorkRequest v reťazi alebo zvážte foreground service.
Ako prebudím MAUI aplikáciu na iOS bez čakania na BGTaskScheduler?
Použite silent push notification (APNs payload s content-available: 1). iOS prebudí aplikáciu na background fetch, počas ktorého máte približne 30 sekúnd na vykonanie práce. Toto je jediný spôsob, ako garantovať near-realtime vykonanie kódu z vášho servera.
Funguje WorkManager v Hot Reload režime počas vývoja?
Hot Reload úspešne aplikuje XAML a UI zmeny, ale Worker triedy nie sú súčasťou Hot Reload pipeline. Pri zmenách v DoWork() musíte spraviť plný redeploy aplikácie. Detaily o limitoch Hot Reload nájdete v našom článku Hot Reload v .NET MAUI: prečo prestáva fungovať.
Praktický návod, ako v .NET MAUI 10 spojazdniť App Links na Androide a Universal Links na iOS — od overovacích súborov cez Shell routing až po lokálne testovanie.
Hot Reload v .NET MAUI 10 prestal fungovať? Prejdite si presné príčiny rude edits, iOS interpreter setup, multi-target footguny a šesťkrokový diagnostický checklist pre rok 2026.
App Center skončil. Praktický návod, ako nasadiť Sentry alebo Firebase Crashlytics v .NET MAUI 2026 - integrácia, upload symbolov, GDPR a check-list pre migráciu.