Background Sync mit WorkManager
Background Sync hält App-Daten aktuell, ohne Akku und Netzwerk unnötig zu belasten. Du lernst WorkManager, Constraints und Offline-First sauber einzuordnen.
Background Sync sorgt dafür, dass deine App Daten mit einem Server abgleicht, auch wenn der Nutzer gerade nicht aktiv auf den Bildschirm schaut. In modernen Android-Apps ist das kein Freibrief für dauerhafte Hintergrundprozesse, sondern eine bewusst geplante Aufgabe: zuverlässig genug für Produktanforderungen, sparsam genug für Akku, Netzwerk und Systemleistung.
Was ist das?
Background Sync ist der Datenabgleich außerhalb einer direkten Nutzeraktion. Typische Beispiele sind das Hochladen lokal erstellter Notizen, das Aktualisieren eines Caches, das Nachziehen neuer Nachrichten-Metadaten oder das Synchronisieren von Entitäten, die der Nutzer offline bearbeitet hat. Wichtig ist dabei die Abgrenzung: Es geht nicht darum, eine App permanent wach zu halten. Es geht darum, Arbeit so zu planen, dass Android sie ausführen kann, wenn passende Bedingungen erfüllt sind.
Im Android-Kontext ist dafür meistens WorkManager die richtige Jetpack-API. WorkManager eignet sich für aufschiebbare, aber zuverlässige Arbeit. Wenn eine Synchronisierung nicht exakt in dieser Sekunde passieren muss, aber irgendwann abgeschlossen werden soll, passt dieses Modell. Das System darf den Zeitpunkt optimieren, etwa um mehrere Netzwerkoperationen zu bündeln oder Akku zu sparen.
Das mentale Modell ist: Deine UI zeigt Daten aus einer lokalen Quelle, etwa Room. Deine Data Layer entscheidet, wann lokale Änderungen an den Server gesendet oder entfernte Änderungen lokal gespeichert werden. Background Sync ist dann ein Werkzeug innerhalb dieser Architektur. Compose beobachtet nur den Zustand, zum Beispiel per Flow aus der Datenbank. Der Sync selbst gehört nicht in Composables und auch nicht als improvisierter Nebenjob in eine Activity.
Das ist besonders wichtig für Offline-First. Eine Offline-First-App behandelt die lokale Datenquelle als primäre Wahrheit für die Oberfläche. Der Nutzer kann lesen, filtern oder oft auch bearbeiten, obwohl das Netzwerk fehlt. Der Hintergrundabgleich bringt diese lokale Wahrheit später wieder mit der entfernten Datenquelle zusammen. So bleibt die App benutzbar, ohne Netzwerkfehler direkt zur UI-Blockade zu machen.
Wie funktioniert es?
WorkManager arbeitet mit WorkRequests. Ein WorkRequest beschreibt, welche Arbeit ausgeführt werden soll, welche Eingaben nötig sind und unter welchen Bedingungen sie laufen darf. Die eigentliche Arbeit liegt in einem Worker, häufig in einem CoroutineWorker, weil Netzwerk- und Datenbankzugriffe in Kotlin sauber mit suspend-Funktionen modelliert werden können.
Constraints sind die Bedingungen für die Ausführung. Du kannst zum Beispiel festlegen, dass ein Sync nur bei verfügbarer Netzwerkverbindung laufen darf. Für größere Uploads kann auch ein nicht niedriger Akkustand oder ein Ladezustand sinnvoll sein. Diese Constraints sind keine Dekoration, sondern Teil der Produktqualität. Eine App, die dauernd Daten über mobile Verbindung schiebt oder bei schwachem Akku arbeitet, fühlt sich schlecht an und kann im Play-Store-Kontext negativ auffallen.
Es gibt zwei häufige Arten von Sync-Arbeit. Ein OneTimeWorkRequest eignet sich für konkrete Aufgaben: Der Nutzer hat offline etwas erstellt, also soll ein Upload eingeplant werden. PeriodicWorkRequest eignet sich für wiederkehrende Pflegearbeiten, etwa einen Cache gelegentlich zu aktualisieren. Auch periodische Arbeit ist nicht exakt getaktet. Android garantiert dir keinen präzisen Sekundenplan, sondern einen systemfreundlichen Rahmen.
Ein Worker sollte kurz, idempotent und robust sein. Idempotent bedeutet: Wenn derselbe Sync erneut läuft, darf er keine falschen Duplikate, kaputten Zustände oder mehrfachen Seiteneffekte erzeugen. Das ist notwendig, weil Hintergrundarbeit fehlschlagen und wiederholt werden kann. Netzwerkprobleme, App-Neustarts oder Systementscheidungen sind normal. Dein Code muss damit rechnen.
Bei Coroutines gilt: Der Worker ruft suspend-Funktionen aus Repositorys oder Use Cases auf. Er sollte nicht direkt UI-Zustand anfassen. Fehler behandelst du bewusst: Ein temporärer Netzwerkfehler führt eher zu Result.retry(), ein dauerhaft ungültiger Request eher zu Result.failure(). Bei Erfolg gibst du Result.success() zurück. So lernt WorkManager, ob später ein weiterer Versuch sinnvoll ist.
In der Praxis
Stell dir eine Notizen-App vor. Der Nutzer erstellt offline eine Notiz. Du speicherst sie sofort lokal mit einem Status wie PENDING_UPLOAD. Die UI zeigt sie direkt aus der lokalen Datenbank an. Danach planst du einen Upload per WorkManager. Sobald Netzwerk verfügbar ist, lädt der Worker alle ausstehenden Notizen hoch und markiert sie lokal als synchronisiert.
class SyncNotesWorker(
appContext: Context,
params: WorkerParameters,
private val repository: NotesRepository
) : CoroutineWorker(appContext, params) {
override suspend fun doWork(): Result {
return try {
repository.uploadPendingNotes()
repository.refreshChangedNotes()
Result.success()
} catch (e: IOException) {
Result.retry()
} catch (e: HttpException) {
if (e.code() in 500..599) Result.retry() else Result.failure()
}
}
}
fun enqueueNotesSync(context: Context) {
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.setRequiresBatteryNotLow(true)
.build()
val request = OneTimeWorkRequestBuilder<SyncNotesWorker>()
.setConstraints(constraints)
.setBackoffCriteria(
BackoffPolicy.EXPONENTIAL,
30,
TimeUnit.SECONDS
)
.build()
WorkManager.getInstance(context)
.enqueueUniqueWork(
"notes-sync",
ExistingWorkPolicy.KEEP,
request
)
}
Der wichtige Punkt ist nicht die Menge an Code, sondern die Zuständigkeit. Die UI speichert oder liest Daten nicht direkt vom Netzwerk, nur weil ein Button gedrückt wurde. Die Repository-Schicht kennt lokale und entfernte Datenquellen. WorkManager startet den Abgleich, wenn Android die Bedingungen erfüllt. Dadurch entsteht eine klare Trennung zwischen Nutzeroberfläche, Data Layer und Hintergrundarbeit.
Eine praktische Entscheidungsregel lautet: Nutze WorkManager, wenn die Arbeit aufschiebbar ist und auch nach einem App-Neustart noch relevant bleibt. Nutze ihn nicht für Arbeit, die nur während eines sichtbaren Screens Sinn ergibt. Für sofortige UI-nahe Operationen passen ViewModel, Coroutines und Repository-Aufrufe besser. Für zuverlässigen Hintergrundabgleich ist WorkManager meist stabiler als eigene Timer, Handler oder dauerhaft laufende Services.
Eine typische Stolperfalle ist doppelter Sync. Wenn du bei jedem App-Start, jedem Screen-Wechsel und jedem Button-Klick neue WorkRequests ohne eindeutigen Namen einplanst, kann deine App mehrere identische Uploads parallel ausführen. Das führt zu unnötigem Traffic und manchmal zu Datenkonflikten. Verwende deshalb eindeutige Work-Namen, klare Policies und serverseitig stabile IDs für Objekte, die hochgeladen werden.
Eine zweite Stolperfalle ist zu viel Logik im Worker. Der Worker sollte orchestrieren, aber nicht die komplette Datenstrategie enthalten. Konfliktauflösung, Mapping, lokale Transaktionen und API-Aufrufe gehören in die Data Layer. So kannst du diese Logik in Unit-Tests prüfen, ohne WorkManager selbst starten zu müssen. Für den Worker testest du dann vor allem, ob er bei Erfolg, temporärem Fehler und dauerhaftem Fehler das richtige Resultat liefert.
Auch Performance gehört dazu. Background Sync darf die App nicht unnötig wecken, große Datenmengen ungeprüft laden oder auf dem Main Thread arbeiten. Nutze suspend-fähige APIs, begrenze Datenmengen und synchronisiere inkrementell, wenn dein Backend das unterstützt. Ein guter Sync ist oft unspektakulär: Er aktualisiert genau das, was nötig ist, und lässt das Gerät ansonsten in Ruhe.
Fazit
Background Sync ist eine Architekturentscheidung, keine einzelne API-Zeile. Du planst verschiebbare Arbeit mit WorkManager, schützt Gerät und Nutzer mit Constraints und hältst deine UI über eine lokale Offline-First-Datenquelle stabil. Prüfe dein Verständnis praktisch: Baue einen kleinen Worker für ausstehende lokale Änderungen, simuliere fehlendes Netzwerk, beobachte die WorkManager-Zustände im Debugger und kontrolliere im Code-Review, ob Sync-Logik sauber in der Data Layer liegt.