Sensitive capabilities require the user’s permission at runtime, and background work must be scheduled through WorkManager to run reliably under Android’s battery restrictions. Two things every real app needs.
Access to sensitive data and hardware — location, camera, contacts — is gated by permissions. You declare them in the manifest, but dangerous ones must also be granted by the user at runtime, and the user can revoke them anytime. Your app must handle both the "granted" and "denied" paths gracefully rather than assuming access, which is a common source of crashes.
// 1) declare in the manifest:
// <uses-permission android:name="android.permission.CAMERA" />
// 2) request dangerous permissions AT RUNTIME
class MainActivity : ComponentActivity() {
private val requestCamera = registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { granted ->
if (granted) openCamera() else showRationale() // handle BOTH paths
}
fun onTakePhoto() {
requestCamera.launch(Manifest.permission.CAMERA)
}
}
// the user can DENY or later REVOKE — never assume you have a permission.To protect battery, Android aggressively limits what an app can do when it is not in the foreground — it may defer or kill background work, especially in Doze mode. This means a naive background thread or Service is unreliable: the system can stop it. Guaranteed background work needs to be scheduled through the platform’s job scheduler so it survives app restarts and runs within the battery rules.
Naive approach (unreliable):
start a Thread / Service to sync in the background
-> Android may kill it, defer it, or never run it (Doze, background limits)
-> your sync silently doesn't happen
The platform reality:
background execution is HEAVILY restricted to save battery.
You must SCHEDULE deferrable work through the system's job scheduler so it:
- survives app kills + device reboots
- runs under constraints (only on Wi-Fi, only when charging)
- retries with backoff on failure
The tool for this is WorkManager (next).WorkManager is the recommended API for deferrable, guaranteed background work — uploading logs, syncing data, periodic cleanup. You define a Worker with the job, set constraints (network, charging), and enqueue it; the system runs it at an appropriate time and retries on failure, persisting across reboots. It is the correct answer to "do this later, reliably" on modern Android.
class SyncWorker(ctx: Context, params: WorkerParameters) : CoroutineWorker(ctx, params) {
override suspend fun doWork(): Result {
return try {
syncData() // your background job (can suspend)
Result.success()
} catch (e: Exception) {
Result.retry() // WorkManager retries with backoff
}
}
}
// enqueue it with constraints — runs only on an unmetered network
val work = OneTimeWorkRequestBuilder<SyncWorker>()
.setConstraints(Constraints(requiredNetworkType = NetworkType.UNMETERED))
.build()
WorkManager.getInstance(context).enqueue(work)
// survives app kills + reboots, respects battery. The right tool for "sync later".Android offers several mechanisms and the trick is matching the tool to the timing requirement. Coroutines handle async work while the screen is open; WorkManager handles deferrable work that must eventually run; a foreground service handles ongoing work the user is actively aware of. Picking correctly keeps your app reliable and within Google’s policies.
Match the tool to the requirement:
work while a screen is open -> coroutines (viewModelScope) [Kotlin course]
deferrable, must eventually run -> WorkManager
(sync, upload, periodic cleanup; survives reboot, respects battery)
ongoing + user is watching NOW -> foreground service (music, navigation)
(persistent notification required)
exact-time alarm -> AlarmManager (use sparingly)
Wrong choice = dropped work or a policy violation. Most apps: coroutines for
in-screen async + WorkManager for the rest.