Monday, February 23, 2026

Android java: Work Manager and Alarm Manager

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:

  1. WorkManager.enqueueUniqueWork() for one time work
  2. WorkManager.enqueueUniquePeriodicWork() for periodic work 

There are 4 option to register unique work

  1. REPLACE existing work with the new work. This option cancels the existing work.
  2. KEEP existing work and ignore the new work.
  3. 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
  4. 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(...);