Bridge
Bridge is an Android background-work runtime built directly on the platform’s own primitives: an append-only event journal, JobWorkItem-multiplexed dispatch, death forensics via ApplicationExitInfo, and measured cost via HealthStats.
Two properties define it:
- Work interrupted by process death resumes where it stopped. A 40-chunk upload force-stopped at chunk 6 continues at chunk 6, not chunk 0. A durable coroutine killed mid-
delaywakes when the timer fires and replays to exactly the suspension point. - Every deferred or held job can explain why.
whyPending()returns a typed verdict backed by the platform’s own reporting (getPendingJobReasons, standby buckets, Doze state), never a staleRUNNING.
The API in one screen
/*
* Application.onCreate — register worker factories at every process start.
* Relaunching IS the recovery path: after a force-stop, this registration is
* what lets journaled work find its code again. initializeAsync keeps
* journal-open and reconciliation off the main thread; early callers
* suspend on Bridge.awaitReady().
*/
Bridge.initializeAsync(this) {
worker("sync") { SyncWorker() } // BridgeWorker: suspend fun run(ctx): RunResult
}
/*
* One enqueue, the whole surface. KEEP semantics per unique name:
* re-enqueueing a live name is a no-op, so unconditional
* enqueue-on-startup is safe. Enqueue before init completes throws —
* call from a coroutine after Bridge.awaitReady(), or use the
* synchronous Bridge.initialize(context) { ... } form.
*/
Bridge.enqueue(workRequest("nightly-sync", "sync") {
/* Platform constraints — compiled to real JobInfo, never emulated. */
network() // any connected network; unmetered() for Wi-Fi-class
charging()
batteryNotLow()
storageNotLow()
deviceIdle() // JobInfo.setRequiresDeviceIdle
contentTrigger("content://media/photos", descendants = true)
// JobInfo.TriggerContentUri — runs when content changes
/*
* Policy inputs — these feed Bridge's own engine, not just the platform.
* importance: LOW yields under bucket quota and thread pressure; HIGH
* never waits on thread pressure (only deadline work bypasses every gate). maxThreadPressure: dispatch only while runnable threads
* stay at or below the level (MEDIUM = cores x 2), overriding the
* importance-derived default in either direction. Every policy hold is
* journaled and visible in whyPending() — nothing is silently deferred.
*/
importance(Importance.LOW)
maxThreadPressure(PressureLevel.MEDIUM)
/*
* Scheduling shape. Retries ride the platform's exponential backoff
* (JobInfo.setBackoffCriteria: 30s initial, doubling, platform-capped);
* backoff(initialMs, policy) overrides that per request. maxAttempts is
* Bridge's cap on top: the attempt that exceeds it is journaled as
* terminal FAILED, visible in ledger().
*/
initialDelay(10 * 60_000L) // exact-path setMinimumLatency
maxAttempts(5)
mustCompleteBy(tomorrow6amMs) // escalates as the deadline nears:
// DEFAULT -> EXPEDITED -> while-idle alarm
})
/*
* Repeating work — each cycle is a journaled generation; cancelling the
* name ends the series. The 15-minute floor is the platform's, not Bridge's.
*/
Bridge.enqueue(workRequest("heartbeat", "sync") {
periodic(30 * 60_000L)
})
Every line above is covered in depth in Tier 2 — Runtime.
How this book is organized
| Chapter | Covers |
|---|---|
| Getting started | Modules, initialization, the first enqueue |
| Tier 0 — Glassbox | Diagnostics for any app, no migration |
| Tier 1 — Compat | The androidx.work-shaped façade |
| Tier 2 — Runtime | The constraint DSL, chunked work, periodic work |
| Diagnostics | whyPending, ledger, report, adb access |
| Tier 3 — Durable coroutines | Suspend blocks that survive process death |
| Tier 4 — Simulator | Testing device regimes on the JVM |
| Bridge vs WorkManager | Capability comparison and measured results |
| Migration | Moving from WorkManager in three reversible stages |
| Internals | How the runtime is built |
| Results | Raw measured numbers, citable |
Adoption model
Bridge is adopted incrementally. Each tier is useful on its own, and every step is reversible.
| Tier | Module | Provides | Adoption cost |
|---|---|---|---|
| 0 | bridge-glassbox | Diagnostics for any app’s existing jobs | Two lines; nothing to migrate |
| 1 | bridge-compat | androidx.work-shaped façade; chains resume at the failed link | An import change |
| 2 | bridge-runtime | Full engine: constraints, chunks, deadlines, periodic, diagnostics | Native API adoption |
| 3 | bridge-runtime | Durable coroutines surviving process death | Builds on Tier 2 |
| 4 | bridge-sim | JVM device regimes in milliseconds, for tests | Test-only dependency |