SoFunction
Updated on 2025-05-04

A summary of several ways to implement timing tasks on Android (with source code)

1. Project introduction

1. Background and meaning

In Android applications, the requirements for scheduled tasks are almost everywhere: from timed refreshing data, timed backups, timed push notifications, to silent downloads at night, and looping execution of certain business logic, the system requires the system to trigger code execution at a specified time or interval. Due to the Android system's own life cycle management, Doze mode, power optimization and other mechanisms, the implementation of timing tasks must not only ensure accuracy, but also take into account both power saving and resource utilization. Therefore, several common implementation methods have their own focus and usage scenarios.

This article will comprehensively sort out the main solutions for implementing timing tasks on Android from multiple dimensions such as principles, best practices, advantages and limitations, and supplement it with complete and runnable sample code. The structure of this article is as follows:

  1. Common scenarios and requirements for timing tasks

  2. Related basic knowledge and constraints

  3. Plan 1:()andRunnable

  4. Plan 2:Timer / TimerTask

  5. Plan 3:ScheduledThreadPoolExecutor

  6. Plan 4:AlarmManager

  7. Plan 5:JobScheduler

  8. Plan 6:WorkManager

  9. Plan 7: Front Desk Service (Service + Handler / AlarmManager

  10. Environment and dependency

  11. Complete code integration (a block of code, separated files with comments)

  12. Solution comparison and selection suggestions

  13. Performance and power saving optimization

  14. Project summary and expansion ideas

  15. FAQ

2. Related basic knowledge and system constraints

  1. Main thread and child thread

    • Handler: On the main thread or the specified threadLooperUp scheduleRunnable

    • TimerTask / ScheduledThreadPoolExecutor: Execute timed tasks in the background thread pool, pay attention to the life cycle.

  2. System power saving mechanism

    • Doze mode(Android 6.0+) will delay or batch processing to wake up regularly;

    • App StandbyBattery SaverWill restrict background scheduling;

  3. Process and component life cycle

    • The process is recycled,ServiceDestroy, the timing needs to be persisted or coordinated with the system scheduler;

  4. Accuracy and power consumption

    • High frequency and high precision wake-up will consume a lot of power;

    • The application scenario determines what precision and scheduler to use;

  5. Cross-restart and persistence

    • AlarmManagerIt can be set to take effect after the device restarts (needs to register dynamically or statically.BOOT_COMPLETED);

    • JobSchedulerandWorkManagerIt can be automatically restored after restart.

3. Plan 1: ()

3.1 Principle

HandlerBy binding to itLooper(usually the main thread) sends a delay message and executesRunnable. Commonly usedShort timeLow frequency, timing operations that interact closely with the UI.

3.2 Sample code

// Used in Activity or Serviceprivate val handler = Handler(())
private val task = object : Runnable {
  override fun run() {
    // Execute timing tasks    refreshUI()
    // Scheduling again    (this, 5000)
  }
}
 
override fun onStart() {
  ()
  (task, 5000) // Execute for the first time in 5 seconds}
 
override fun onStop() {
  ()
  (task)  // Stop scheduling}

3.3 Pros and cons

  • advantage: Easy to use, easy to execute custom logic;

  • shortcoming: Relying on the process to survive, the process is dead or the device is sleeping, and the execution cannot be guaranteed; high-frequency scheduling consumes power;

4. Plan 2: Timer/TimerTask

4.1 Principle

Scheduled one or more in a separate threadTimerTask,based on, suitable for simple background timing.

4.2 Sample code

private var timer: Timer? = null
 
fun startTimer() {
  timer = Timer().apply {
    scheduleAtFixedRate(object : TimerTask() {
      override fun run() {
        // Background thread execution        performBackgroundWork()
      }
    }, 0, 10_000) // Once in 10 seconds  }
}
 
fun stopTimer() {
  timer?.cancel()
  timer = null
}

4.3 Pros and cons

  • advantage: Easy to execute across threads, suitable for simple background timing;

  • shortcomingTimerTaskAn exception will cause subsequent tasks to be unable to be executed; life cycles need to be managed manually;

5. Plan 3: ScheduledThreadPoolExecutor

5.1 Principle

based on, create a fixed-size thread pool and schedule single or periodic tasks.

5.2 Sample Code

private val scheduler = (1)
 
fun startScheduledTask() {
  ({
    performBackgroundWork()
  }, 0, 15, ) // Perform every 15 minutes}
 
fun stopScheduledTask() {
  ()
}

5.3 Pros and cons

  • advantage: Controllable thread pool size, task exceptions will not affect other tasks;

  • shortcoming: Also affected by the process life cycle, it cannot be restarted across the process;

6. Plan 4: AlarmManager

6.1 Principle

System-level scheduling, usingAlarmManagerCan be triggered at a specified timePendingIntent, wake up or start the component (BroadcastReceiverServiceActivity), supports cross-process and restart.

6.2 Sample Code

// Registered broadcast receiver: AlarmReceiverclass AlarmReceiver: BroadcastReceiver() {
  override fun onReceive(ctx: Context, intent: Intent) {
    // Execute tasks    performWork(ctx)
  }
}
 
// exist<receiver android:name=".AlarmReceiver" />
 
// Set Alarm in the codeval am = getSystemService(Context.ALARM_SERVICE) as AlarmManager
val pi = (this, 0,
    Intent(this, AlarmReceiver::), 0)
// Accurate alarm clock (API 19+ may be merged)(
  AlarmManager.RTC_WAKEUP,
  () + 60_000,  // 1 minute later  pi
)
  • Periodic taskssetRepeating()Or inonReceiveRegister again;

  • Cross-restart recovery: Need to monitorBOOT_COMPLETEDRe-register the alarm clock.

6.3 Pros and cons

  • advantage: System-level wake-up, which can ensure execution in reboot and Doze mode;

  • shortcoming: Frequent alarm clocks will consume severe power; API 19+ may be saved and merged by the system;

7. Plan 5: JobScheduler

7.1 Principle

Android 5.0+ native API manages background tasks that meet the criteria (network, charging, idleness, etc.). The system is scheduled according to the policy, and no need for developers to manually re-register.

7.2 Sample Code

class MyJobService: JobService() {
  override fun onStartJob(params: JobParameters): Boolean {
    // Execute in the background thread    doWork {
      jobFinished(params, false)
    }
    return true // There is also background thread work  }
  override fun onStopJob(params: JobParameters) = false
}
 
// Scheduled in Activity or Applicationval tm = getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler
val job = (1, ComponentName(this, MyJobService::))
  .setRequiresCharging(false)
  .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
  .setPeriodic(15 * 60 * 1000) // Minimum 15 minutes  .build()
(job)

7.3 Pros and cons

  • advantage: The system automatically optimizes scheduling, saves power; supports conditional triggering;

  • shortcoming: API 21+, minimum cycle of 15 minutes;

8. Plan 6: WorkManager

8.1 Principle

Google's recommended background task library, compatible with API 14+, internally selected according to the system versionJobScheduler / AlarmManager / FirebaseJobDispatcher, supports constraints, chains, unique tasks, delays, cycles, persistence, retry and other functions.

8.2 Sample Code

class MyWorker(ctx: Context, params: WorkerParameters): Worker(ctx, params) {
  override fun doWork(): Result {
    performWork(applicationContext)
    return ()
  }
}
 
// Scheduling in codeval request = PeriodicWorkRequestBuilder<MyWorker>(1, )
  .setConstraints(()
    .setRequiredNetworkType()
    .build())
  .build()
(this).enqueueUniquePeriodicWork(
  "my_hourly_work",
  ,
  request
)

8.3 Pros and cons

  • advantage: API compatibility, automatic selection of the best scheduler, persistence, and easy to use;

  • shortcoming: The scheduling does not guarantee accuracy and timeliness, and most scenarios are delayed by several minutes or longer;

9. Plan 7: Front Desk Service

9.1 Principle

Start aFront Desk ServicestartForeground()), useHandlerorScheduledExecutorExecute tasks within it to ensure that processes and Service are not killed by the system.

9.2 Sample Code

class ForegroundTimerService: Service() {
  private val handler = Handler(())
  private val task = object: Runnable {
    override fun run() {
      performWork(this@ForegroundTimerService)
      (this, 5*60*1000)
    }
  }
  override fun onCreate() {
    ()
    startForeground(1, buildNotification())
    (task)
  }
  override fun onDestroy() {
    (task)
    ()
  }
  override fun onBind(intent: Intent?) = null
}

9.3 Pros and cons

  • advantage: The process is permanent, not easy to be recycled, suitable for high-reliability long-term tasks;

  • shortcoming: Continuous display of notifications, consumes power, affects user experience;

10. Environment and dependence

// app/
plugins {
  id ''
  id 'kotlin-android'
}
 
android {
  compileSdk 34
  defaultConfig {
    applicationId ""
    minSdk 21
    targetSdk 34
  }
}
 
dependencies {
  implementation ':work-runtime-ktx:2.8.1'
}

11. Complete code integration

// =======================================================
// document:// Description: Declare Service and BroadcastReceiver// =======================================================
<manifest xmlns:andro
    package="">
  <application android:name=".App">
    <!-- AlarmManager Receiver -->
    <receiver android:name=".AlarmReceiver"/>
    <!-- Foreground Service -->
    <service android:name=".ForegroundTimerService"
        android:exported="false"/>
    <!-- JobScheduler Service -->
    <service android:name=".MyJobService"
        android:permission=".BIND_JOB_SERVICE"/>
  </application>
</manifest>
 
// =======================================================
// document:// Description: Application, initialize WorkManager// =======================================================
package 
import 
class App : Application()
 
// =======================================================
// document:// Description: AlarmManager timed task reception// =======================================================
package 
import 
import 
import 
 
class AlarmReceiver : BroadcastReceiver() {
  override fun onReceive(ctx: Context, intent: Intent) {
    ("AlarmManager triggered")
  }
}
 
// =======================================================
// document:// Description: JobScheduler Service// =======================================================
package 
import 
import 
import .*
 
class MyJobService: JobService() {
  private val scope = CoroutineScope()
  override fun onStartJob(params: JobParameters): Boolean {
     {
      ("JobScheduler triggered")
      jobFinished(params, false)
    }
    return true
  }
  override fun onStopJob(params: JobParameters) = false
}
 
// =======================================================
// document:// Description: Front Desk Service + Handler// =======================================================
package 
import 
import 
import 
import 
import .*
 
class ForegroundTimerService: Service() {
  private val handler = Handler(())
  private val task = object : Runnable {
    override fun run() {
      ("ForegroundService task executed")
      (this, 5*60*1000)
    }
  }
  override fun onCreate() {
    ()
    startForeground(1, buildNotification())
    (task)
  }
  override fun onDestroy() {
    (task)
    ()
  }
  override fun onBind(intent: Intent?) = null
 
  private fun buildNotification(): Notification {
    val pi = (
      this,0, Intent(this, MainActivity::),0)
    return (this, TaskUtils.CHANNEL_ID)
      .setContentTitle("Timed Task Service")
      .setSmallIcon(.ic_launcher_foreground)
      .setContentIntent(pi)
      .build()
  }
}
 
// =======================================================
// document:// Description: Tool class: Log and Scheduling Registration Method// =======================================================
package 
import 
import 
import 
import 
import 
import 
import 
import .*
import 
 
object TaskUtils {
  const val CHANNEL_ID = "task_service_channel"
 
  fun scheduleAlarm(ctx: Context){
    val am = (Context.ALARM_SERVICE)
        as AlarmManager
    val pi = (
      ctx,0, Intent(ctx,AlarmReceiver::),0)
    (
      AlarmManager.RTC_WAKEUP,
      ()+60_000, pi)
  }
 
  fun scheduleJob(ctx: Context){
    val js = (Context.JOB_SCHEDULER_SERVICE)
      as JobScheduler
    val job = (1,
      ComponentName(ctx, MyJobService::))
      .setPeriodic(15*60*1000)
      .build()
    (job)
  }
 
  fun scheduleWork(){
    val req = PeriodicWorkRequestBuilder<MyWorker>(
      1, ).build()
    ()
      .enqueueUniquePeriodicWork(
        "my_hourly_work",
        ,
        req)
  }
}
 
// =======================================================
// document:// Description: WorkManager Worker// =======================================================
package 
import 
import 
import 
 
class MyWorker(ctx: Context, params: WorkerParameters)
  : Worker(ctx, params) {
  override fun doWork(): Result {
    ("WorkManager triggered")
    return ()
  }
}
 
// =======================================================
// document:// Description: Demonstrate each solution trigger and start// =======================================================
package 
import 
import 
import .*
import 
import 
 
class MainActivity : AppCompatActivity() {
  private lateinit var b: ActivityMainBinding
  override fun onCreate(s: Bundle?) {
    (s)
    b = (layoutInflater); setContentView()
 
     {
      (runnable, 5000)
    }
     { startTimer() }
     { startScheduled() }
     { (this) }
     { (this) }
     { () }
     {
      startService(Intent(this, ForegroundTimerService::))
    }
  }
 
  // Handler example  private val handler = Handler(())
  private val runnable = object: Runnable {
    override fun run() {
      (" triggered")
    }
  }
 
  // Timer example  private var timer: Timer? = null
  private fun startTimer(){
    timer?.cancel()
    timer = Timer().apply {
      schedule(object: TimerTask(){
        override fun run() {
          ("TimerTask triggered")
        }
      }, 0, 10000)
    }
  }
 
  // ScheduledThreadPoolExecutor Example  private val scheduler = (1)
  private fun startScheduled(){
    ({
      ("ScheduledThreadPoolExecutor triggered")
    },0,30,)
  }
}

12. Plan comparison and selection suggestions

plan API version Accuracy Power consumption Cross-restart Applicable scenarios
() API 1 High (in thread) high Short-term, lightweight cycle updates within the interface
Timer / TimerTask API 1 Higher Higher Simple background tasks
ScheduledThreadPoolExecutor API 1 Higher Higher Backend tasks that require thread pool management
AlarmManager API 1 Can be accurate to milliseconds Higher Accurate wake-up and restart at specified time
JobScheduler API 21+ Batch scheduling, inaccurate Low Conditional triggering, system optimization batch execution
WorkManager API 14+ Approximate period (minutes) Low Chainable, constrainable, recommended
Foreground Service + Handler API 1 High (internal scheduling) high Highly reliable, long-term background tasks, but affects UX
  • forExtremely high accuracy requirementsandOne-timeReminder, useAlarmManager

  • forContinuous cycleBackground tasks that do not require second-level accuracy, recommendedWorkManagerorJobScheduler

  • forRefresh within the UI for a short time,use

  • forProcess residentCore tasks that need to be continuously executed can be consideredFront Desk Service

13. Performance and power saving optimization

  1. Combined alarm: Avoid setting too many alarms and using batch or merge wake-up policies.

  2. Avoid Doze restrictions: Use for non-critical missionsWorkManager, let the system schedule uniformly;

  3. Dynamic adjustment cycle: Reduce the wake-up frequency according to the network, charging status, and user interaction;

  4. Quickly complete short tasks:existJobServiceComplete as soon as possible to avoid constant application;

14. Project summary and expansion ideas

This article comprehensively sorts out the seven main solutions for Android to implement timing tasks, from the simplestHandlerTimer, to the system levelAlarmManagerJobScheduler, and then to the best compatibilityWorkManagerAnd a high-reliability front desk service to help you according toApplication scenariosAccuracyandPower consumptionSelection in three dimensions. At the same time, it provides complete and operational sample code, covering the entire process of registration, triggering, processing and cancellation, to help you quickly implement the timing task function.

Expand ideas

  • Mixed scheduling: Combining multiple solutions in the same scenario, for example, throughWorkManagerManage long-term tasks and pass them at critical momentsAlarmManagerAccurate wake-up.

  • Adaptive scheduling: Dynamically switch scheduling accuracy according to the App background/foreground status.

  • Visual management: Provides timed task list, run log and scheduling status monitoring within the application.

15. FAQ

  1. Why is AlarmManager not accurate?

    • Android 19+ system will batch merge the alarm clock and can be usedsetExactAndAllowWhileIdle()Forced precision, but frequent wake-up will be restricted by Doze.

  2. Why is the minimum cycle of JobScheduler 15 minutes?

    • The minimum system cycle is 15 minutes to avoid over-wake and power consumption.

  3. Will WorkManager consume a lot of power?

    • No, the system will merge scheduling and execute only when the constraints are met, suitable for most background tasks.

  4. Why does the front desk service affect the user experience?

    • Because notifications will be displayed continuously, and the resident process consumes power and it is difficult for users to turn off.

  5. Is it necessary to register dynamically BOOT_COMPLETED?

    • If usedAlarmManagerThe alarm clock needs to be re-registered after restarting;JobSchedulerandWorkManagerIt will automatically recover.

The above is the detailed content of several ways to implement timing tasks (with source code). For more information about Android timing tasks, please pay attention to my other related articles!