Background Tasks auf iOS und Android richtig einsetzen
Wer React Native Hintergrundprozesse wie einen Dauerprozess auf einem Server plant, stößt schnell an die Grenzen von iOS und Android: Beide Betriebssysteme unterbrechen Apps im Hintergrund aggressiv, um Akku zu sparen. Dieser Artikel zeigt, wie BGTaskScheduler, WorkManager und react-native-background-fetch zusammenspielen, damit Background Tasks zuverlässig und batteriefreundlich funktionieren.
Inhaltsverzeichnis
- 1. Warum Hintergrundausführung auf Mobile fundamental anders ist
- 2. Das iOS-Hintergrundausführungsmodell
- 3. Das Android-Hintergrundausführungsmodell
- 4. react-native-background-fetch: periodisches Scheduling
- 5. Headless JS auf Android: was im Hintergrund laufen darf
- 6. Praktische Muster: Foreground-Sync vs. echte Hintergrundausführung
- 7. Background Tasks testen: Simulation und Doze-Debugging
- 8. Akku- und Betriebssystem-Richtlinien beachten
- 9. Migration: New Architecture und Expo Managed Workflow
- 10. Zusammenfassung
- 11. FAQ
1. Warum Hintergrundausführung auf Mobile fundamental anders ist
Auf einem Server oder im Browser ist ein lang laufender Prozess der Normalfall. Ein Node-Prozess lauscht auf Events, ein Cron-Job tickt zuverlässig, ein Webserver hält Verbindungen offen, solange Ressourcen reichen. Wer aus dieser Denkweise kommt und React Native Hintergrundprozesse wie einen Dauerprozess plant, unterschätzt, wie aggressiv iOS und Android das Ausführungsmodell einer App im Hintergrund einschränken. Beide Betriebssysteme betrachten eine App im Hintergrund primär als Kandidaten für die Beendigung, nicht als vertrauenswürdigen Dienst.
Der Grund liegt in der Akkulaufzeit: Ein Smartphone trägt seinen Energiespeicher am Körper, nicht im Rechenzentrum, und jede App, die unkontrolliert Hintergrundprozesse offen hält, kostet messbar Laufzeit. iOS und Android suspendieren Prozesse nach wenigen Sekunden im Hintergrund, entziehen CPU-Zeit nach festen Budgets und reservieren echte Hintergrundausführung für wenige, klar definierte APIs. Für React Native bedeutet das: Background Tasks sind kein Implementierungsdetail, sondern eine Architekturentscheidung, die früh im Projekt getroffen werden muss.
Wer diese Einschränkung ignoriert und versucht, mit setInterval im JavaScript-Thread Hintergrundprozesse zu simulieren, erlebt spätestens beim Wechsel in den Hintergrund, dass der Timer einfach aufhört zu feuern. Der JavaScript-Kontext wird pausiert, sobald die App nicht mehr im Vordergrund ist, und mit ihm jede Zeitsteuerung, die nicht über eine native Bridge an eine echte Betriebssystem-API angebunden ist. Genau an dieser Stelle beginnt der Unterschied zwischen naivem und robustem Umgang mit React Native Background Tasks.
2. Das iOS-Hintergrundausführungsmodell
iOS erlaubt Hintergrundprozesse nur über explizit deklarierte Background Modes in der Info.plist. Eine App, die im Hintergrund Audio abspielen, Standortdaten verarbeiten oder Daten aktualisieren will, muss den passenden Background Mode deklarieren, sonst verweigert das System jede Ausführung außerhalb des Vordergrunds. Für klassische Datensynchronisation ist neben dem alten Fetch-Modus vor allem das modernere BGTaskScheduler-Framework relevant, das seit iOS 13 die empfohlene Grundlage für Background Tasks ist.
BGTaskScheduler unterscheidet zwei Task-Typen: BGAppRefreshTask für kurze, häufige Aktualisierungen und BGProcessingTask für längere, seltenere Arbeit wie Datenbank-Wartung oder größere Downloads, die auch an ein Ladegerät gekoppelt werden kann. Beide Typen erhalten aber nur ein enges Zeitbudget von wenigen Sekunden bis maximal wenigen Minuten, nicht die zehn oder zwanzig Minuten, die Entwickler aus klassischen Cron-Jobs gewohnt sind. Wer diese Budgets überschreitet, wird vom System ohne Vorwarnung hart beendet.
Für Fälle, in denen die App reagieren muss, bevor der Nutzer sie öffnet, bietet iOS Silent Push Notifications an: Ein Push ohne sichtbaren Alert weckt die App kurz auf, damit sie im Hintergrund Daten abrufen kann. Diese Kombination aus BGTaskScheduler für periodische Wartung und Silent Push für ereignisgesteuerte Hintergrundprozesse deckt die meisten praktischen Anforderungen an React Native Background Tasks unter iOS ab, ohne dass die App dauerhaft aktiv gehalten werden muss.
{
"expo": {
"ios": {
"infoPlist": {
"UIBackgroundModes": [
"fetch",
"processing",
"remote-notification"
],
"BGTaskSchedulerPermittedIdentifiers": [
"de.mironsoft.app.refresh",
"de.mironsoft.app.processing"
]
}
}
}
}
// AppDelegate.swift: register BGTaskScheduler tasks before app finishes launching
import BackgroundTasks
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
BGTaskScheduler.shared.register(
forTaskWithIdentifier: "de.mironsoft.app.refresh",
using: nil
) { task in
handleAppRefresh(task: task as! BGAppRefreshTask)
}
BGTaskScheduler.shared.register(
forTaskWithIdentifier: "de.mironsoft.app.processing",
using: nil
) { task in
handleProcessing(task: task as! BGProcessingTask)
}
return true
}
func handleAppRefresh(task: BGAppRefreshTask) {
scheduleAppRefresh() // Always reschedule the next run first
let operation = SyncOperation()
task.expirationHandler = { operation.cancel() }
operation.completionBlock = { task.setTaskCompleted(success: !operation.isCancelled) }
OperationQueue().addOperation(operation)
}
func scheduleAppRefresh() {
let request = BGAppRefreshTaskRequest(identifier: "de.mironsoft.app.refresh")
request.earliestBeginDate = Date(timeIntervalSinceNow: 15 * 60) // Hint only, not a guarantee
try? BGTaskScheduler.shared.submit(request)
}
3. Das Android-Hintergrundausführungsmodell
Android verfolgt mit Doze Mode und App Standby Buckets ein anderes, aber ähnlich restriktives Konzept. Sobald ein Gerät längere Zeit unbewegt und ungeladen ruht, versetzt Doze Mode das System in einen Energiesparmodus, der Netzwerkzugriff, Wakelocks und die meisten Hintergrundprozesse einfriert und nur in kurzen Wartungsfenstern kurzzeitig freigibt. App Standby Buckets stufen Apps zusätzlich nach Nutzungsverhalten ein: selten genutzte Apps landen in restriktiveren Buckets mit deutlich saloppen Ausführungsfenstern.
Für geplante Hintergrundarbeit empfiehlt Google explizit WorkManager statt AlarmManager oder rohen Threads, weil WorkManager die Constraints des Systems respektiert und Arbeit automatisch auf zulässige Zeitfenster verschiebt, statt sie zu verlieren. Für Arbeit, die der Nutzer aktiv sehen soll, etwa einen laufenden Download oder eine Standortverfolgung, ist ein Foreground Service die richtige Wahl, weil er mit einer permanenten Notification sichtbar bleibt und dadurch von den strengsten Doze-Einschränkungen ausgenommen ist.
Manche Teams versuchen, die Ausnahme von Battery Optimizations explizit vom Nutzer einzufordern, um Hintergrundprozesse dauerhaft laufen zu lassen. Das ist riskant: Die Play Store Review prüft diese Berechtigung streng und lehnt Apps ab, deren Kernfunktion diese Ausnahme nicht zwingend rechtfertigt, etwa reine Messaging- oder Fitness-Tracking-Anwendungen. Wer stattdessen auf WorkManager und Foreground Services im richtigen Kontext setzt, kommt ohne diese Ausnahme aus und übersteht die Review deutlich unkomplizierter.
// SyncWorker.kt: WorkManager worker for periodic background sync
import android.content.Context
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
import androidx.work.Result
class SyncWorker(context: Context, params: WorkerParameters) :
CoroutineWorker(context, params) {
override suspend fun doWork(): Result {
return try {
// Keep work small and idempotent, the OS may retry or delay this run
val pending = SyncRepository.fetchPendingItems()
SyncRepository.upload(pending)
Result.success()
} catch (e: Exception) {
if (runAttemptCount < 3) Result.retry() else Result.failure()
}
}
}
// Scheduling with constraints, respected by Doze and App Standby
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.setRequiresBatteryNotLow(true)
.build()
val request = PeriodicWorkRequestBuilder<SyncWorker>(15, TimeUnit.MINUTES)
.setConstraints(constraints)
.build()
WorkManager.getInstance(context)
.enqueueUniquePeriodicWork("sync-work", ExistingPeriodicWorkPolicy.KEEP, request)
4. react-native-background-fetch: periodisches Scheduling
Die Bibliothek react-native-background-fetch kapselt BGTaskScheduler unter iOS und WorkManager unter Android hinter einer gemeinsamen JavaScript-API und ist damit der pragmatische Standardweg, um React Native Background Tasks plattformübergreifend zu planen. Die configure()-Methode registriert ein minimales Intervall, typischerweise fünfzehn Minuten, wobei sowohl iOS als auch Android dieses Intervall nur als Richtwert behandeln und die tatsächliche Ausführung je nach Systemzustand verzögern oder überspringen können.
Auf Android ist die Registrierung eines Headless Tasks zwingend, damit Hintergrundprozesse auch dann ausgeführt werden, wenn die App vollständig beendet wurde, nicht nur pausiert im Hintergrund liegt. Der Headless Task wird über registerHeadlessTask in einem separaten Einstiegspunkt registriert und läuft unabhängig von der React-Komponentenbaum-Instanz, die zuvor im Vordergrund aktiv war.
Wichtig ist, jeden Background Fetch Task explizit mit finish() abzuschließen. Wer das vergisst, riskiert, dass das Betriebssystem die App als fehlerhaft einstuft und zukünftige Hintergrundprozesse seltener oder gar nicht mehr zulässt, weil die Budget-Buchhaltung des Systems den Task als hängend registriert und entsprechend abstraft.
// backgroundTasks.js: configure background-fetch and register the headless task
import BackgroundFetch from "react-native-background-fetch";
export async function configureBackgroundFetch() {
const status = await BackgroundFetch.configure(
{
minimumFetchInterval: 15, // Minutes, OS treats this as a hint, not a guarantee
stopOnTerminate: false,
startOnBoot: true,
enableHeadless: true,
requiredNetworkType: BackgroundFetch.NETWORK_TYPE_ANY,
requiresBatteryNotLow: true,
},
async (taskId) => {
await runSync();
BackgroundFetch.finish(taskId); // Mandatory, or the OS penalizes future runs
},
async (taskId) => {
// Timeout handler, must also call finish
BackgroundFetch.finish(taskId);
}
);
return status;
}
// Headless task, runs even after the app was terminated (Android only)
const headlessTask = async (event) => {
const { taskId } = event;
await runSync();
BackgroundFetch.finish(taskId);
};
BackgroundFetch.registerHeadlessTask(headlessTask);
async function runSync() {
const pending = await getPendingItems();
if (pending.length > 0) {
await uploadItems(pending);
}
}
5. Headless JS auf Android: was im Hintergrund laufen darf
Headless JS ist der Mechanismus, über den React Native JavaScript-Code ausführt, ohne dass eine Activity oder ein UI-Thread existiert. Das ist der entscheidende Unterschied zu normalem App-Code: Im Headless-Kontext gibt es keinen Zugriff auf Views, keine Navigation, kein Rendering, nur reine JavaScript-Logik und die nativen Module, die keine UI-Interaktion voraussetzen.
Typische Anwendungsfälle für Headless JS sind Datensynchronisation mit einem Backend, das Hochladen gepufferter Standortdaten oder das Verarbeiten eingehender Push-Payloads. Nicht geeignet ist Headless JS für alles, was auf sichtbares Rendering, Kamera-Preview oder UI-gebundene Interaktion angewiesen ist, weil diese Ressourcen im Headless-Kontext schlicht nicht existieren und der Versuch eines Zugriffs zu einem Absturz führt.
Auf Android muss der Headless Service zusätzlich in der AndroidManifest.xml registriert werden, sonst startet das System den Task gar nicht erst. Wer Hintergrundprozesse über Headless JS plant, sollte den Task so schlank wie möglich halten: Ein Timeout von wenigen Sekunden ist die Regel, nicht die Ausnahme, und lang laufende Netzwerkaufrufe sollten eine harte Zeitgrenze mit Promise.race absichern, damit ein einzelner hängender Request nicht den gesamten Task blockiert.
6. Praktische Muster: Foreground-Sync vs. echte Hintergrundausführung
Die meisten Apps brauchen in der Praxis keine echte Hintergrundausführung im strengen Sinne, sondern nur einen zuverlässigen Sync-Zeitpunkt beim Rückkehr in den Vordergrund. Ein AppState-Listener, der bei jedem Wechsel von background zu active einen Sync auslöst, deckt einen großen Teil der Anforderungen ab, ohne dass ein einziges Byte an BGTaskScheduler- oder WorkManager-Budget verbraucht wird.
Für Fälle, in denen Daten wirklich eintreffen sollen, während die App geschlossen ist, ist ein Push-getriggerter Ansatz robuster als ein reiner Hintergrund-Timer: Das Backend sendet bei einem relevanten Ereignis eine Silent Push Notification oder eine Firebase-Cloud-Messaging-Data-Message, die App wacht kurz auf, verarbeitet die Nutzlast und schläft wieder ein. Diese Architektur verschiebt die Verantwortung für Zuverlässigkeit vom instabilen Hintergrund-Timer auf das Backend, das ohnehin weiß, wann neue Daten vorliegen.
react-native-background-fetch und WorkManager bleiben trotzdem sinnvoll als Fallback-Ebene für periodische Wartung, etwa das Aufräumen alter Cache-Einträge oder das erneute Versuchen fehlgeschlagener Uploads. Die Kombination aus Foreground-Sync, Push-getriggerten Updates und periodischen Background Tasks als Sicherheitsnetz deckt in der Praxis fast jeden Anwendungsfall ab, ohne dass ein einzelner Mechanismus überfordert wird.
7. Background Tasks testen: Simulation und Doze-Debugging
Background Tasks lassen sich nicht zuverlässig testen, indem man wartet und hofft. Unter Xcode simuliert der Debug-Menüpunkt "Simulate Background Fetch" den Aufruf sofort, ohne auf das reale, vom System kontrollierte Intervall zu warten, und ist damit der schnellste Weg, den Task-Handler selbst zu verifizieren, bevor die App überhaupt in den echten Hintergrund geschickt wird.
Unter Android liefert adb direkte Kontrolle über Doze Mode und Standby-Verhalten. Mit adb shell dumpsys deviceidle force-idle wird das Gerät in den Idle-Zustand versetzt, mit adb shell cmd jobscheduler run lässt sich ein geplanter WorkManager-Job sofort auslösen, unabhängig vom eigentlichen Zeitfenster. Diese Kommandos sind der einzige praktikable Weg, Doze-bedingte Hintergrundprozesse deterministisch zu testen, statt Stunden auf ein reales Wartungsfenster zu warten.
#!/usr/bin/env bash
# Force the device into Doze mode and inspect current standby state
adb shell dumpsys battery unplug
adb shell dumpsys deviceidle force-idle
# Verify the app's current App Standby Bucket
adb shell am get-standby-bucket de.mironsoft.app
# Trigger a specific WorkManager job immediately, bypassing constraints
adb shell cmd jobscheduler run -f de.mironsoft.app 1
# Simulate an idle maintenance window opening
adb shell dumpsys deviceidle step
# Reset the device back to normal power state after testing
adb shell dumpsys battery reset
adb shell dumpsys deviceidle unforce
Ein häufiger Debugging-Fehler: Entwickler testen nur im Debug-Build mit angeschlossenem Kabel, wodurch Doze Mode und Battery Optimizations vom System teilweise deaktiviert werden. Das Verhalten in einem Release-Build auf einem Gerät ohne Kabel und ohne aktives Debugging unterscheidet sich deutlich, und genau dieser Unterschied ist die Quelle der meisten Meldungen der Art "funktioniert lokal, aber nicht beim Kunden" bei Background Tasks.
8. Akku- und Betriebssystem-Richtlinien beachten
Aggressive Hintergrundprozesse fallen nicht nur dem Betriebssystem auf, sondern auch dem Nutzer: Sowohl iOS als auch Android zeigen in den Systemeinstellungen einen Akkubericht pro App, und eine App, die dort auffällig viel Hintergrundverbrauch verursacht, wird häufig deinstalliert, noch bevor der Nutzer den genauen Grund versteht. Android warnt Nutzer seit einigen Versionen zusätzlich aktiv vor Apps mit ungewöhnlich hohem Hintergrundverbrauch, direkt in den Systembenachrichtigungen.
Frugales Design bedeutet konkret: Intervalle so groß wie fachlich vertretbar wählen, Netzwerkaufrufe batchen statt einzeln zu feuern, und Hintergrundarbeit an Bedingungen wie WLAN-Verfügbarkeit oder Ladezustand koppeln, wo immer möglich. WorkManager unterstützt solche Constraints nativ über setRequiresCharging und setRequiredNetworkType, und react-native-background-fetch bietet vergleichbare Optionen direkt über die Konfiguration.
Der langfristige Effekt disziplinierter Background Tasks ist doppelt: weniger Akkubeschwerden in App-Store-Bewertungen und eine geringere Wahrscheinlichkeit, dass Play Store oder App Store die App wegen aggressiver Hintergrundprozesse ablehnen oder zurückstufen. Beide Stores beobachten Hintergrundverhalten inzwischen aktiv als Qualitätssignal, nicht nur als technische Formalität im Review-Prozess.
9. Migration: New Architecture und Expo Managed Workflow
Mit der New Architecture, also Fabric und TurboModules, ändert sich das grundlegende Ausführungsmodell für Background Tasks nicht: BGTaskScheduler, WorkManager und Headless JS funktionieren weiterhin über native Module, die über die neue JSI-Bridge angebunden werden, statt über die alte asynchrone Bridge. Bibliotheken wie react-native-background-fetch benötigen aber unter Umständen ein Update auf eine Version mit TurboModule-Unterstützung, bevor ein Umstieg auf die New Architecture ohne Regressionen möglich ist.
Im Expo Managed Workflow waren Hintergrundprozesse historisch eingeschränkter als im Bare Workflow, weil native Konfiguration wie Info.plist Background Modes oder AndroidManifest-Einträge für Headless Services nicht direkt zugänglich waren. Mit Config Plugins und expo-background-fetch beziehungsweise expo-task-manager lässt sich das inzwischen weitgehend abdecken, aber ein Prebuild-Schritt oder ein eigener Development Build bleibt notwendig, sobald native Background Modes über den reinen Expo Go Client hinausgehen.
Teams, die von Expo Go auf einen eigenen Development Build wechseln, sollten diesen Schritt einplanen, sobald Background Tasks Teil der Anforderungen werden: Expo Go selbst kann keine eigenen Background Modes registrieren, weil es ein generischer, von Expo vorkompilierter Client ist. Ein eigener Development Build mit den passenden Config Plugins ist die Voraussetzung, um BGTaskScheduler, WorkManager und Headless JS in einem Expo-Projekt überhaupt nutzen zu können.
BGTaskScheduler, WorkManager und Foreground Service im direkten Vergleich: Die drei zentralen Mechanismen für React Native Background Tasks unterscheiden sich deutlich in Zeitbudget, Garantie und Nutzer-Sichtbarkeit. Die Wahl des richtigen Mechanismus entscheidet direkt darüber, ob Hintergrundprozesse zuverlässig laufen oder vom System stillschweigend verzögert und übersprungen werden.
| Kriterium | iOS BGTaskScheduler | Android WorkManager | Android Foreground Service |
|---|---|---|---|
| Zeitbudget | Sekunden bis wenige Minuten | Minuten, planbar über Constraints | Unbegrenzt, solange Notification sichtbar |
| Trigger-Garantie | Vom System verzögerbar, keine feste Zeit | Respektiert Constraints, verzögert bei Doze | Startet sofort, läuft kontinuierlich weiter |
| Nutzer-Sichtbarkeit | Unsichtbar | Unsichtbar | Permanente Notification Pflicht |
| Typischer Einsatzzweck | Periodische Datenaktualisierung | Geplante Wartungsarbeit, Sync | Aktiver Download, Standortverfolgung |
| Battery-Optimierungs-Risiko | Gering, System-kontrolliert | Gering bei korrekten Constraints | Hoch bei Missbrauch für Hintergrundarbeit |
Mironsoft
React Native Architektur, Mobile Background Engineering und App-Audits
Hintergrundprozesse eurer React Native App zuverlässig im Griff?
Wir implementieren BGTaskScheduler und WorkManager sauber über react-native-background-fetch, richten Headless JS korrekt ein und verifizieren Background Tasks mit adb und Xcode, bevor eure App in Produktion geht.
Background-Implementierung
BGTaskScheduler für iOS, WorkManager für Android, sauber verdrahtet über react-native-background-fetch
Push-getriggerte Architektur
Backend-Trigger statt fragiler Timer, für zuverlässige Datenaktualisierung im Hintergrund
Testing & Battery-Audit
Verifikation mit adb, Doze-Simulation und Xcode auf echten Release-Builds, inklusive Report
10. Zusammenfassung
React Native Hintergrundprozesse folgen keiner einheitlichen Cross-Plattform-API, sondern zwei fundamental unterschiedlichen Betriebssystem-Modellen: BGTaskScheduler mit engen Zeitbudgets unter iOS, WorkManager und Foreground Services mit Doze-Einschränkungen unter Android. react-native-background-fetch überbrückt beide Welten mit einer gemeinsamen JavaScript-API, ersetzt aber nicht das Verständnis der zugrunde liegenden Plattform-Beschränkungen, die jeder Background Task respektieren muss.
Wer Background Tasks zuverlässig plant, kombiniert Foreground-Sync bei App-Aktivierung, Push-getriggerte Updates für zeitkritische Daten und periodische Hintergrundprozesse als Fallback-Netz für Wartungsarbeit. Testing mit Xcodes Simulate-Background-Fetch und adb-Kommandos für Doze Mode gehört fest in den Entwicklungsworkflow, ebenso wie ein frugales, an Systembedingungen gekoppeltes Design, das weder Nutzer noch App-Store-Review durch übermäßigen Hintergrundverbrauch auffällt.
React Native Hintergrundprozesse, das Wichtigste auf einen Blick
iOS: enge Zeitbudgets
BGAppRefreshTask und BGProcessingTask laufen nur wenige Sekunden bis Minuten, ergänzt durch Silent Push für zeitkritische Ereignisse.
Android: Doze respektieren
WorkManager für geplante Arbeit, Foreground Service nur für nutzersichtbare Aufgaben, keine Battery-Optimization-Ausnahme ohne triftigen Grund.
react-native-background-fetch & Headless JS
Gemeinsame API für beide Plattformen, Headless Task zwingend für Android nach App-Terminierung, immer mit finish() abschließen.
Push statt Timer
Push-getriggerte Architektur ist zuverlässiger als reine Hintergrund-Timer, Background Tasks bleiben als Fallback-Netz sinnvoll.