# How Cron-Based Scheduled Tasks Are Implemented in SmsForwarder: WorkManager and Cron Parser Integration

> Discover how SmsForwarder implements cron scheduled tasks using WorkManager and CronParser. Learn about CronWorker for execution and CronJobScheduler for timing.

- Repository: [pppscn/SmsForwarder](https://github.com/pppscn/SmsForwarder)
- Tags: internals
- Published: 2026-06-22

---

**SmsForwarder implements cron-based scheduled tasks using Android's WorkManager combined with the gatewayapps-crondroid library, where `CronWorker` handles execution and rescheduling while `CronJobScheduler` manages the timing and queuing of `OneTimeWorkRequest` instances.**

The SmsForwarder repository (pppscn/SmsForwarder) provides robust automation capabilities through cron-based scheduled tasks that execute even when the app is not actively running. This implementation leverages Android's WorkManager API alongside a specialized cron parsing library to handle reliable background execution with millisecond precision. Understanding this architecture reveals how the application ensures tasks trigger at exact intervals while preventing duplicate or overlapping executions.

## Cron Expression Parsing with gatewayapps-crondroid

The foundation of the scheduling system relies on the **gatewayapps-crondroid** library to parse standard cron expressions. The `CronExpression` class from `gatewayapps.crondroid` computes the next valid execution time based on user-provided strings.

In [`app/src/main/kotlin/cn/ppps/forwarder/fragment/condition/CronFragment.kt`](https://github.com/pppscn/SmsForwarder/blob/main/app/src/main/kotlin/cn/ppps/forwarder/fragment/condition/CronFragment.kt), users input cron expressions which are validated and stored as `CronSetting` objects. These settings are later deserialized in [`ConditionUtils.kt`](https://github.com/pppscn/SmsForwarder/blob/main/ConditionUtils.kt) using Gson to evaluate whether the current time matches the scheduled pattern.

## Task Execution Flow in CronWorker

Located in [`app/src/main/kotlin/cn/ppps/forwarder/workers/CronWorker.kt`](https://github.com/pppscn/SmsForwarder/blob/main/app/src/main/kotlin/cn/ppps/forwarder/workers/CronWorker.kt), the **CronWorker** class extends `CoroutineWorker` and serves as the primary execution engine for scheduled tasks.

### Retrieving and Validating Tasks

When the worker runs, it extracts the task ID from `inputData` and loads the corresponding task from the database. Before execution, it validates that all task conditions are satisfied through `ConditionUtils.checkCondition`:

```kotlin
val taskId = inputData.getLong(TaskWorker.TASK_ID, -1L)
val task = Core.task.getOne(taskId) ?: return Result.failure()

if (!ConditionUtils.checkCondition(task.id, conditionList)) return Result.failure()

```

### Updating Execution Times

Upon successful validation, the worker updates the task's `lastExecTime` and calculates the next execution window using `CronExpression.getNextValidTimeAfter`:

```kotlin
val now = Date()
task.lastExecTime = task.nextExecTime
val next = CronExpression(cronSetting.expression)
    .getNextValidTimeAfter(now).apply { time = time / 1000 * 1000 }
task.nextExecTime = next

```

### Enqueueing Action Workers

The actual task actions are handled by `ActionWorker`, which is queued immediately after the cron condition is met:

```kotlin
val data = Data.Builder()
    .putLong(TaskWorker.TASK_ID, task.id)
    .putString(TaskWorker.TASK_ACTIONS, task.actions)
    .build()
WorkManager.getInstance()
    .enqueue(OneTimeWorkRequestBuilder<ActionWorker>()
        .setInputData(data).build())

```

## Scheduling Mechanism in CronJobScheduler

The `CronJobScheduler` utility class in [`app/src/main/kotlin/cn/ppps/forwarder/utils/task/CronJobScheduler.kt`](https://github.com/pppscn/SmsForwarder/blob/main/app/src/main/kotlin/cn/ppps/forwarder/utils/task/CronJobScheduler.kt) manages the creation and cancellation of WorkManager requests.

When scheduling a task, it calculates the delay between the current time and `task.nextExecTime`, then constructs a `OneTimeWorkRequest`:

```kotlin
val task: Task = // obtained from database
CronJobScheduler.scheduleTask(task) // creates a delayed OneTimeWorkRequest

```

The scheduler uses `setInitialDelay` to defer execution until the exact cron time. If the delay is less than or equal to zero, the work executes immediately. Each request is enqueued using `beginUniqueWork` with `ExistingWorkPolicy.KEEP` to prevent duplicate executions:

- **Unique work policy**: Ensures only one instance of a specific task runs at a time
- **Immediate execution**: Triggered when `nextExecTime` has already passed
- **Delayed execution**: Uses `setInitialDelay` for future scheduling

## Condition Verification with ConditionUtils

Before `CronWorker` executes the main task logic, `ConditionUtils.checkCondition` in [`app/src/main/kotlin/cn/ppps/forwarder/utils/task/ConditionUtils.kt`](https://github.com/pppscn/SmsForwarder/blob/main/app/src/main/kotlin/cn/ppps/forwarder/utils/task/ConditionUtils.kt) verifies that the cron expression is currently satisfied. This acts as a safety check to ensure WorkManager delays align with the actual cron schedule:

```kotlin
val cronSetting = Gson().fromJson(condition.setting, CronSetting::class.java)
val now = Date()
val previous = Date(now.time - 1000) // one second before
val next = CronExpression(cronSetting.expression)
    .getNextValidTimeAfter(previous).apply { time = time / 1000 * 1000 }

if (now.time != next.time) return false // cron not satisfied

```

## Task Rescheduling and Cancellation

After each execution, `CronWorker` reschedules the task for its next occurrence. This process involves canceling any existing pending work for that task ID and creating a new schedule:

```kotlin
CronJobScheduler.cancelTask(task.id)
CronJobScheduler.scheduleTask(task)

```

This pattern ensures that tasks repeat according to their cron expressions without manual intervention. The cancellation mechanism prevents overlapping schedules when a task is updated or when execution times shift.

## Summary

- **CronWorker** handles the execution of scheduled tasks as a `CoroutineWorker`, validating conditions and enqueueing `ActionWorker` instances
- **CronJobScheduler** manages WorkManager requests, calculating delays and using `OneTimeWorkRequest` with unique work policies to prevent duplicates
- **ConditionUtils** performs runtime verification of cron expressions to ensure execution occurs only at valid scheduled times
- **CronExpression** from the gatewayapps-crondroid library parses expressions and calculates next execution times in milliseconds
- Tasks automatically reschedule themselves after each execution by calling `scheduleTask` within the worker's completion handler

## Frequently Asked Questions

### How does SmsForwarder ensure cron tasks run when the app is closed?

SmsForwarder utilizes Android's WorkManager API, which is designed for deferrable background work that persists across app restarts and device reboots. The `CronWorker` extends `CoroutineWorker`, ensuring tasks execute even when the app is not in the foreground by leveraging the system's job scheduling capabilities.

### What happens if a scheduled task misses its execution window?

When a task's `nextExecTime` has already passed (resulting in a delay ≤ 0), `CronJobScheduler` immediately queues the work for execution rather than skipping it. The `ConditionUtils.checkCondition` method further validates that the current time matches the cron pattern, ensuring the task only runs if the schedule is still valid.

### Can multiple instances of the same cron task run simultaneously?

No. The implementation uses `WorkManager.beginUniqueWork` with `ExistingWorkPolicy.KEEP` when scheduling tasks in `CronJobScheduler`. This policy ensures that if a pending work item for that task already exists, the new request is discarded, preventing duplicate or overlapping executions of the same cron task.

### How does the application handle daylight saving time or timezone changes in cron expressions?

The cron parsing relies on `java.util.Date` and millisecond-based time calculations. The `CronExpression.getNextValidTimeAfter` method computes the next valid time based on the current system time, automatically adjusting for timezone changes. However, users should verify that their cron expressions account for local time settings as stored in the `CronSetting` configuration.