Java Android Scheduling: Work Manager and Alarm Manager
WorkManager:
- Constraints: You can specify conditions like "only run when charging" or "only on Wi-Fi".
- Persistence: Tasks survive device reboots and app crashes.
- Backward Compatibility: It automatically chooses the best underlying API (JobScheduler, AlarmManager, etc.) based on the device's API level.
- Limitation: It does not guarantee exact timing. The system may delay execution to optimize battery life.
AlarmManager
- Precision: Can wake the device from Doze mode to trigger a notification exactly when scheduled.
- Lifecycle Independent: Operates outside your app's lifecycle once set.
- It is resource-intensive because it wakes the device.
- It does not support execution constraints (like network requirements).
- For long-running tasks triggered by an alarm, Google recommends handing off the work to WorkManager from the alarm's BroadcastReceiver.
| Feature | WorkManager | AlarmManager |
| Timing | Deferrable (inexact) | Precise (exact) |
| Guaranteed? | Yes, even after reboot | Not inherently (must reset on reboot) |
| Constraints | Battery, Network, Storage | None (time-only) |
| Power Efficiency | High (optimized by OS) | Low (wakes device) |
| Minimum Interval | 15 minutes for periodic work | None (can be immediate) |
| Supported Version | Known stable on Android 13+ | All |
If you need to access shared preferences, use createDeviceProtectedStorageContext(), this will allow you to access shared preferences in any state of boot.
public static SharedPreferences getMySharedPreferences(Context context) {
Context appContext = context.getApplicationContext();
Context storageContext;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
// This context can read data even while the phone is locked (State 2)
storageContext = appContext.createDeviceProtectedStorageContext();
} else {
storageContext = appContext;
}
return storageContext.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
}
This is code for on Boot Receiver
public class MyBootReceiver extends BroadcastReceiver {
// simple state to prevent double running
private static boolean isRunning = false;
@Override
public void onReceive(Context context, Intent intent) {
// state of boot device
String action = intent.getAction();
if (action == null) return;
// prevent android 13+ to use alarm manager
if (MySharePreferences.isUseWorkManager()) {
return;
}
// --- ADD THE CHECK HERE ---
// If the backup feature is disabled, stop immediately.
if (!MySharePreferences.isEnable(context)) {
return;
}
switch (action) {
case Intent.ACTION_LOCKED_BOOT_COMPLETED:
// Phone is locked. Use Device Protected Storage to schedule the next alarm.
// Do NOT try to read contacts or write files here.
// TODO e.g rescheduleAlarmNow(context);
break;
case Intent.ACTION_USER_UNLOCKED:
case Intent.ACTION_BOOT_COMPLETED:
// FULL BOOT / UNLOCKED.
// This is where you run your overdue check logic.
// TODO all storage is accessible
handleBootBackupCheck(context);
break;
case "android.intent.action.ALARM_MATCHED":
executeEvent(context);
break;
}
}
/*
* route to handling full boot
*/
private void handleBootBackupCheck(Context context) {
if (isRunning) return; // Prevent double execution
isRunning = true;
...
It is suggested to use WorkManager for android 13+. WorkManager does not required to reschedule the task on boot.
There are 2 types of unique job:
- WorkManager.enqueueUniqueWork() for one time work
- WorkManager.enqueueUniquePeriodicWork() for periodic work
There are 4 option to register unique work
- REPLACE existing work with the new work. This option cancels the existing work.
- KEEP existing work and ignore the new work.
- APPEND the new work to the end of the existing work. This policy will cause your new work to be chained to the existing work, running after the existing work finishes
- APPEND_OR_REPLACE functions similarly to APPEND, except that it is not dependent on prerequisite work status. If the existing work is CANCELLED or FAILED, the new work still runs.
To create on time unique work
public static void setWorkManager(Context contextRoot, long delayInMinutes) {
//Log.e("dedetok", "setWorkManager "+delayInMinutes); // debug
Context context = contextRoot.getApplicationContext();
OneTimeWorkRequest myWorkRequest = new OneTimeWorkRequest.Builder(MyWorker.class)
.setInitialDelay(delayInMinutes, TimeUnit.MINUTES)
.build();
WorkManager.getInstance(context).enqueueUniqueWork(
MySharePreferences.PREF_NAME, // unique name
ExistingWorkPolicy.REPLACE, // REPLACE (Version 2026.2) Or KEEP, APPEND, UPDATE (not stoping running task
myWorkRequest
);
}
To check unique work schedule by unique name
workManager.getWorkInfoById(syncWorker.id); // ListenableFuture<WorkInfo>
// by name
workManager.getWorkInfosForUniqueWork("sync"); // ListenableFuture<List<WorkInfo>>
// by tag
workManager.getWorkInfosByTag("syncTag"); // ListenableFuture<List<WorkInfo>>
To get id from unique name
WorkRequest request = new OneTimeWorkRequest.Builder(FooWorker.class).build();
workManager.enqueue(request);
LiveData<WorkInfo> status = workManager.getWorkInfoByIdLiveData(request.getId());
status.observe(...);