Opdateret: 19. september 2026
Baggrundstjenester i .NET MAUI kræver, at du taler til iOS BGTaskScheduler og Android WorkManager direkte via platform-specifikke handlers, fordi MAUI ikke leverer en indbygget cross-platform abstraktion for baggrundsafvikling. På iOS 26 registrerer du opgaver i Info.plist under BGTaskSchedulerPermittedIdentifiers og planlægger dem via BGTaskScheduler.Shared. På Android 15 bruger du WorkManager med en ForegroundServiceType, når opgaven skal køre længere end en håndfuld sekunder. Så, lad os tage det skridt for skridt, med kørende C#-kode og ikke pseudokode.
Hvad er baggrundstjenester i .NET MAUI?
Baggrundstjenester i .NET MAUI er kode, der udføres, når din app ikke er i forgrunden, uanset om den er suspenderet, minimeret eller lukket af brugeren. I modsætning til en HttpClient-forespørgsel udført mens brugeren scroller, giver hverken iOS eller Android dig fri adgang til CPU'en i baggrunden. Begge operativsystemer prioriterer batterilevetid over din apps behov, og de har brugt de sidste fem år på at stramme reglerne kraftigt.
MAUI selv har ikke en indbygget klasse à la BackgroundService fra ASP.NET Core Hosting. Det, du får ud af boksen, er tråde og Task.Run, som fungerer perfekt så længe processen kører. Men i det øjeblik brugeren swiper appen væk eller iOS suspenderer den efter cirka 30 sekunder i baggrunden, dør din tråd stille og roligt. Løsningen er at kalde ned til platform-native API'er via MAUI's Handler-mønster eller partielle klasser, og så eksponere en fælles interface til din view model.
De to native API'er, du er nødt til at lære, er Apples BackgroundTasks-framework (introduceret i iOS 13, forfinet i iOS 26) og Googles WorkManager (WorkManager 2.10 er den aktuelle stabile version pr. september 2026). Alt andet, Xamarin.Essentials' gamle Task.Run-tricks og tredjeparts "background service"-NuGets fra Xamarin-æraen, er enten deprecated eller decideret vildledende på moderne OS-versioner.
iOS BGTaskScheduler: registrering, planlægning og eksekvering
På iOS 26 skal enhver baggrundsopgave gennem tre trin: deklarer identifieren i Info.plist, registrer en handler i FinishedLaunching, og planlæg opgaven med BGTaskScheduler.Shared. Systemet vælger derefter selv hvornår. Du beder pænt, iOS beslutter.
Start med at tilføje identifieren og de tilladte modes til Platforms/iOS/Info.plist:
<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
<string>com.mobiletechlead.sampleapp.refresh</string>
<string>com.mobiletechlead.sampleapp.processing</string>
</array>
<key>UIBackgroundModes</key>
<array>
<string>fetch</string>
<string>processing</string>
</array>
Derefter en partiel implementering i Platforms/iOS/BackgroundTaskService.cs, der registrerer og planlægger opgaven. Bemærk at Register skal kaldes, inden FinishedLaunching returnerer, ellers ignorerer iOS din handler:
using BackgroundTasks;
using Foundation;
using UIKit;
namespace MobileTechLead.Services;
public partial class BackgroundTaskService : IBackgroundTaskService
{
const string RefreshId = "com.mobiletechlead.sampleapp.refresh";
public void RegisterHandlers()
{
BGTaskScheduler.Shared.Register(RefreshId, null, task =>
{
HandleRefresh((BGAppRefreshTask)task);
});
}
public void Schedule()
{
var request = new BGAppRefreshTaskRequest(RefreshId)
{
EarliestBeginDate = NSDate.FromTimeIntervalSinceNow(15 * 60) // 15 min
};
if (!BGTaskScheduler.Shared.Submit(request, out var error))
{
System.Diagnostics.Debug.WriteLine($"BGTask submit failed: {error}");
}
}
static async void HandleRefresh(BGAppRefreshTask task)
{
// ALTID replanlæg, ellers får du kun ét skud pr. app-lifecycle.
var svc = IPlatformApplication.Current!.Services.GetRequiredService<BackgroundTaskService>();
svc.Schedule();
var cts = new CancellationTokenSource();
task.ExpirationHandler = () => cts.Cancel();
try
{
await SyncLatestArticlesAsync(cts.Token);
task.SetTaskCompleted(success: true);
}
catch
{
task.SetTaskCompleted(success: false);
}
}
}
Android WorkManager: WorkRequest, Worker og constraints
WorkManager er Googles officielle anbefaling til alt baggrundsarbejde, der skal overleve app-lukninger, systemgenstarter og batterioptimering. Den kombinerer JobScheduler, AlarmManager og BroadcastReceiver bag én enkelt API, og siden Android 12 er alle nye baggrundsarbejder tvunget gennem WorkManager, hvis de skal have en chance for at køre.
I MAUI opretter du en Worker-klasse i Platforms/Android/. Bemærk at WorkManager instansierer din klasse via reflection, så konstruktørens signatur er kritisk. Den skal tage Context og WorkerParameters:
using Android.Content;
using AndroidX.Work;
namespace MobileTechLead.Platforms.Android;
public class SyncWorker : Worker
{
public SyncWorker(Context context, WorkerParameters parameters)
: base(context, parameters) { }
public override Result DoWork()
{
try
{
// Kør ikke async void her. WorkManager forventer synkron Result.
var sync = IPlatformApplication.Current!.Services
.GetRequiredService<IArticleSyncService>();
sync.SyncAsync(CancellationToken.None).GetAwaiter().GetResult();
return Result.InvokeSuccess();
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"SyncWorker failed: {ex}");
return Result.InvokeRetry();
}
}
}
Planlægningen sker fra din platform-specifikke service-implementering. Brug PeriodicWorkRequest til gentagne opgaver. Mindste interval er 15 minutter, uanset hvad du sætter:
public partial class BackgroundTaskService
{
public void Schedule()
{
var constraints = new Constraints.Builder()
.SetRequiredNetworkType(NetworkType.Connected)
.SetRequiresBatteryNotLow(true)
.Build();
var request = PeriodicWorkRequest.Builder.From<SyncWorker>(
TimeSpan.FromMinutes(15))
.SetConstraints(constraints)
.AddTag("article-sync")
.Build();
WorkManager.GetInstance(Platform.AppContext)
.EnqueueUniquePeriodicWork(
"article-sync",
ExistingPeriodicWorkPolicy.Keep,
request);
}
}
ExistingPeriodicWorkPolicy.Keep sikrer, at gentagne opkald ikke overskriver en allerede kørende plan (hvilket ville nulstille interval-timeren og reelt aldrig lade opgaven køre). Ærlig talt, den fælde faldt jeg selv i på en prod-app, hvor sync'en aldrig fyrede, fordi den blev enqueue'et hver gang appen åbnede. Det tog en fredag eftermiddag og lidt for meget kaffe at finde ud af.
Foreground services på Android 14 og 15
WorkManager giver dig ca. 10 minutter pr. eksekvering. Har du brug for længere kørselstid (en filoverførsel, en lang beregning, lydafspilning), skal du bruge en foreground service. Fra Android 14 (API 34) er reglerne strammet betydeligt: du skal deklarere en foregroundServiceType i manifestet, og du skal have en tilsvarende FOREGROUND_SERVICE_* tilladelse.
Deklarationen i Platforms/Android/AndroidManifest.xml ser sådan ud for en synkroniseringstjeneste:
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<service
android:name=".SyncForegroundService"
android:foregroundServiceType="dataSync"
android:exported="false" />
På kodesiden skal servicen selv kalde StartForeground inden for 5 sekunder, ellers dræber systemet den med en ForegroundServiceDidNotStartInTimeException. Byg altid din notifikation først, kald derefter StartForeground, og først bagefter begynd på det tunge arbejde.
Sådan bygger du en cross-platform abstraktion i MAUI
Nu hvor du har set begge platforme, er den vigtige del at pakke dem ind i en fælles kontrakt, så din view model ikke ved noget om hverken BGTaskScheduler eller WorkManager. Definer en interface i det delte projekt:
namespace MobileTechLead.Services;
public interface IBackgroundTaskService
{
void RegisterHandlers();
void Schedule();
void Cancel();
}
Brug MAUI's partielle klasser til at levere implementeringen: én BackgroundTaskService.cs i det delte projekt med bare interface-erklæringen, og så Platforms/iOS/BackgroundTaskService.cs og Platforms/Android/BackgroundTaskService.cs med de rigtige metoder. Registrer den derefter i MauiProgram.cs som en singleton, så både App.xaml.cs og dine view models kan hente den ud af DI-containeren:
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
});
builder.Services.AddSingleton<IBackgroundTaskService, BackgroundTaskService>();
builder.Services.AddSingleton<IArticleSyncService, ArticleSyncService>();
return builder.Build();
}
Kald RegisterHandlers() så tidligt som muligt, i FinishedLaunching på iOS og i App.OnCreate på Android. Kald Schedule(), når det giver mening for din UX: efter login, efter første data-load, eller efter brugeren har opdateret indstillinger. Se også vores guide til dependency injection og service lifetimes i .NET MAUI for en dybere gennemgang af, hvorfor en singleton er det rigtige valg her.
Hvor ofte kører iOS-baggrundsopgaver reelt?
Kort svar: sjældnere end du håber. Apple offentliggør ikke præcise tal, men i praksis kører BGAppRefreshTask typisk 1-4 gange i døgnet på en aktivt brugt app, og for apps, brugeren sjældent åbner, kan der gå dage mellem hver eksekvering. Systemet bruger on-device machine learning til at forudsige, hvornår brugeren næste gang åbner din app, og forsøger at opdatere data lige inden. Det er derfor tidspunkter ofte føles tilfældige.
Faktorer, der reducerer eksekveringsfrekvens dramatisk:
- Low Power Mode: brugeren har aktiveret batterisparetilstand, og iOS suspenderer stort set al baggrundsaktivitet.
- Background App Refresh er slået fra: enten globalt eller for din specifikke app i Indstillinger.
- Brugeren har force-quittet appen: iOS 26 tillader stadig baggrundstasks efter en force-quit, men frekvensen falder drastisk.
- Enheden er ikke tilsluttet oplader og ikke på Wi-Fi:
BGProcessingTaskmedRequiresNetworkConnectivity = trueventer typisk, indtil begge betingelser er opfyldt.
Test derfor aldrig baggrundsadfærd ved at vente. Brug i stedet iOS' indbyggede debug-trigger fra Xcode's LLDB-konsol:
e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"com.mobiletechlead.sampleapp.refresh"]
Debugging og fejlfinding af baggrundstasks
Baggrundstasks er den type kode, hvor et almindeligt breakpoint ikke er nok, fordi processen allerede er suspenderet, når du forventer, den kører. Her er nogle konkrete strategier fra apps, jeg selv har shippet:
- Log til fil, ikke til debug output. Skriv altid din baggrundsaktivitet til en persistent log via
ILoggereller Serilog med en filsink. Debug-outputtet er væk, når du kobler telefonen fra. - Simuler via LLDB (iOS) eller
adb shell(Android). På Android kan du fremtvinge en WorkManager-eksekvering med:adb shell cmd jobscheduler run -f com.mobiletechlead.sampleapp 999. - Brug Android Studio's App Inspection. WorkManager-fanen viser dig alle enqueue'ede opgaver, deres constraints, sidste kørsel og næste planlagte kørsel. Det er guld værd, når du fejlfinder "hvorfor kører min opgave ikke".
- Håndter
ExpirationHandler(iOS) ogOnStopped(Android). Systemet vil afbryde dig, når budgettet er brugt. Gemmer du ikke state, mister du arbejdet.
Der er også en mere subtil fælde: MAUI's IPlatformApplication.Current kan være null, hvis din baggrundsopgave starter, før MAUI-app'en er fuldt initialiseret (fx efter en systemreboot på Android). Byg altid din DI-container manuelt som fallback, eller læs sikkert med IPlatformApplication.Current?.Services, og fail gracefully. Jeg brændte mig præcis her på en app i sidste kvartal, hvor sync'en crashede stille, hver gang Android bootede op.
Batteri, Doze og App Standby: hvad du ikke kan ignorere
Både iOS og Android har aggressive batterioptimeringer, som direkte påvirker din opgaves eksekveringsfrekvens. På Android hedder mekanismerne Doze mode (enheden er stationær og skærmen slukket) og App Standby Buckets (systemet klassificerer din app i én af fem buckets baseret på brugsmønster). En app i "rare"- eller "never"-bucket får kun WorkManager-opgaver til at køre én gang i døgnet.
Producenter som Samsung, Xiaomi og Huawei lægger deres egne aggressive optimeringer oven på. Xiaomi's MIUI kan for eksempel dræbe din app helt, med mindre brugeren manuelt slår "Autostart" til i indstillingerne. Der er ikke meget, du kan gøre kode-mæssigt, men du kan opdage det og guide brugeren. Vores guide til push notifikationer i .NET MAUI går i detaljer med præcis dette problem, som også rammer FCM-leverance.
For iOS er den vigtigste optimering, at en BGProcessingTask, du markerer med RequiresExternalPower = true, kun får lov at køre, når telefonen er tilsluttet oplader. Det er perfekt til nattelige oprydningstasks, men ubrugeligt, hvis du forventer real-time synkronisering. Kombiner BGAppRefreshTask (kort, hyppig, netværkskrævende) med BGProcessingTask (længere, sjældnere, opladerkrævende) for at få det bedste ud af begge verdener.
Ofte stillede spørgsmål
Understøtter .NET MAUI baggrundstjenester ud af boksen?
Nej. MAUI har ingen cross-platform abstraktion for baggrundsafvikling. Du skal kalde BGTaskScheduler på iOS og WorkManager på Android via platform-specifikke partielle klasser eller Handler-mønsteret, og selv bygge en fælles interface til dit delte projekt.
Hvor længe må en iOS-baggrundsopgave køre?
BGAppRefreshTask giver typisk 30 sekunder. BGProcessingTask kan køre op til flere minutter, men kun når enheden er tilsluttet oplader og på Wi-Fi. Systemet kalder altid ExpirationHandler, når budgettet er ved at være brugt, så respekter det signal.
Hvad er forskellen på WorkManager og en foreground service på Android?
WorkManager er til opgaver, der må udsættes og kan tåle constraints (netværk, batteri). Foreground services er til brugerbevidst arbejde, der skal køre nu, og som kan vare længere end 10 minutter. De kræver en synlig notifikation og en foregroundServiceType-deklaration fra Android 14 og opefter.
Hvorfor kører min WorkManager-opgave ikke?
Tre almindelige årsager: (1) constraints er ikke opfyldt (fx SetRequiresBatteryNotLow(true) på en enhed under 15% batteri), (2) du enqueue'er med ExistingPeriodicWorkPolicy.Replace hver gang, hvilket nulstiller timeren, eller (3) enheden er i "rare"-bucket, og WorkManager kun får lov at køre én gang i døgnet.
Kan jeg starte en baggrundstask efter en systemreboot?
På Android ja. WorkManager håndterer det automatisk, hvis din opgave var enqueue'et med PeriodicWorkRequest. På iOS nej. Brugeren skal åbne appen mindst én gang efter reboot, før BGTaskScheduler begynder at planlægge dine opgaver igen.




