Giới thiệu
AlarmManager cho phép lên lịch thực thi code tại thời điểm tương lai (đơn lần hoặc lặp lại). Kết hợp với Service để chạy background task. Lưu ý: Android 8+ (API 26) hạn chế background service - nên dùng WorkManager cho deferred work, Foreground Service cho task dài, hoặc AlarmManager + setExactAndAllowWhileIdle cho alarm chính xác.
Các bước thực hiện
Bước 1: Tạo dự án mới
Android Studio → File → New Project → Empty View Activity.
Bước 2: Thêm dependencies (build.gradle.kts)
dependencies {
// WorkManager (khuyến nghị thay AlarmManager cho background work)
implementation("androidx.work:work-runtime-ktx:2.9.0")
}
Bước 3: Tạo Service (MyAlarmService.kt)
package com.example.alarmmanagerdemo
import android.app.Service
import android.content.Intent
import android.os.IBinder
import android.util.Log
import androidx.annotation.Nullable
class MyAlarmService : Service() {
override fun onCreate() {
super.onCreate()
Log.d("MyAlarmService", "onCreate")
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
Log.d("MyAlarmService", "onStartCommand - StartId: $startId")
// Thực hiện công việc tại đây (network, database, etc.)
// Quan trọng: Chạy trên MAIN THREAD - cần chuyển sang background thread
doWork()
// START_NOT_STICKY: Không restart nếu bị kill
// START_STICKY: Restart nếu bị kill (dùng cho media player, tracker)
return START_NOT_STICKY
}
private fun doWork() {
// Ví dụ: Gửi notification, sync data, upload log...
// Chạy trên background thread:
Thread {
try {
Thread.sleep(2000) // Giả lập work
Log.d("MyAlarmService", "Work completed")
} catch (e: InterruptedException) {
e.printStackTrace()
}
}.start()
}
@Nullable
override fun onBind(intent: Intent?): IBinder? = null
override fun onDestroy() {
super.onDestroy()
Log.d("MyAlarmService", "onDestroy")
}
}
Bước 4: Khai báo Service trong AndroidManifest.xml
<service
android:name=".MyAlarmService"
android:exported="false"
android:foregroundServiceType="dataSync" />
<!-- foregroundServiceType bắt buộc từ Android 14 (API 34) nếu dùng Foreground Service -->
Bước 5: Layout chính (res/layout/activity_main.xml)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="https://schemas.android.com/apk/res/android"
xmlns:tools="https://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="24dp"
android:gravity="center"
tools:context=".MainActivity">
<Button
android:id="@+id/btnStartAlarm"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Đặt Alarm (3 giây)" />
<Button
android:id="@+id/btnCancelAlarm"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:text="Hủy Alarm" />
<Button
android:id="@+id/btnScheduleWork"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:text="Dùng WorkManager (Khuyến nghị)" />
</LinearLayout>
Bước 6: MainActivity.kt - AlarmManager
package com.example.alarmmanagerdemo
import android.app.AlarmManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.widget.Button
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import com.example.alarmmanagerdemo.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private var pendingIntent: PendingIntent? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.btnStartAlarm.setOnClickListener { setAlarm() }
binding.btnCancelAlarm.setOnClickListener { cancelAlarm() }
binding.btnScheduleWork.setOnClickListener { scheduleWithWorkManager() }
}
private fun setAlarm() {
val intent = Intent(this, MyAlarmService::class.java)
// FLAG_IMMUTABLE bắt buộc từ Android 12 (API 31)
val flags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
PendingIntent.FLAG_IMMUTABLE
} else {
0
}
pendingIntent = PendingIntent.getService(this, 0, intent, flags)
val alarmManager = getSystemService(Context.ALARM_SERVICE) as AlarmManager
val triggerTime = System.currentTimeMillis() + 3000 // 3 giây sau
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { // API 23+
// Exact alarm - cần permission SCHEDULE_EXACT_ALARM (Android 12+)
if (alarmManager.canScheduleExactAlarms()) {
alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, triggerTime, pendingIntent!!)
Toast.makeText(this, "Đã đặt alarm exact (3s)", Toast.LENGTH_SHORT).show()
} else {
// Hỏi user cấp quyền Settings -> Alarms & reminders
Toast.makeText(this, "Cần quyền 'Alarms & reminders' cho alarm chính xác", Toast.LENGTH_LONG).show()
// Intent(Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM).also { startActivity(it) }
}
} else {
// API < 23
alarmManager.set(AlarmManager.RTC_WAKEUP, triggerTime, pendingIntent!!)
Toast.makeText(this, "Đã đặt alarm (3s)", Toast.LENGTH_SHORT).show()
}
}
private fun cancelAlarm() {
val alarmManager = getSystemService(Context.ALARM_SERVICE) as AlarmManager
pendingIntent?.let {
alarmManager.cancel(it)
Toast.makeText(this, "Đã hủy alarm", Toast.LENGTH_SHORT).show()
} ?: run {
Toast.makeText(this, "Chưa có alarm để hủy", Toast.LENGTH_SHORT).show()
}
}
private fun scheduleWithWorkManager() {
// Xem phần WorkManager bên dưới
Toast.makeText(this, "Xem code WorkManager bên dưới", Toast.LENGTH_SHORT).show()
}
}
Hiện đại hơn: WorkManager (Khuyến nghị cho Background Work)
WorkManager đảm bảo task chạy ngay cả khi app bị kill, device reboot, doze mode. Thay thế AlarmManager cho hầu hết background work.
Worker Class
package com.example.alarmmanagerdemo
import android.content.Context
import android.util.Log
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
import kotlinx.coroutines.delay
class MyWorker(context: Context, params: WorkerParameters) : CoroutineWorker(context, params) {
override suspend fun doWork(): Result {
Log.d("MyWorker", "Work started")
delay(2000) // Giả lập work
Log.d("MyWorker", "Work completed")
return Result.success()
}
}
Lên lịch Work (trong MainActivity)
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkManager
import java.util.concurrent.TimeUnit
private fun scheduleWithWorkManager() {
val workRequest = OneTimeWorkRequestBuilder()
.setInitialDelay(3, TimeUnit.SECONDS)
.addTag("my_alarm_work")
.build()
WorkManager.getInstance(this).enqueue(workRequest)
Toast.makeText(this, "Đã lên lịch WorkManager (3s)", Toast.LENGTH_SHORT).show()
}
// Hủy work
// WorkManager.getInstance(this).cancelAllWorkByTag("my_alarm_work")
AlarmManager vs WorkManager
| Đặc điểm | AlarmManager | WorkManager |
|---|---|---|
| Độ chính xác thời gian | Cao (setExactAndAllowWhileIdle) | Không đảm bảo exact (window) |
| Survive reboot | Không (cần BOOT_COMPLETED receiver) | Có (tự reschedule) |
| Survive app kill | Có (alarm ở system) | Có |
| Doze mode / App Standby | setExactAndAllowWhileIdle hoạt động | Tự handle, delay nếu cần |
| Network constraint | Manual check | Built-in (setRequiredNetworkType) |
| Chaining/Periodic | Manual | Built-in (PeriodicWorkRequest) |
| Khuyến nghị dùng | Alarm clock, reminder chính xác | Sync data, backup, upload log, deferred work |
Lưu ý quan trọng (Android 12+ / API 31+)
PendingIntent.FLAG_IMMUTABLEhoặcFLAG_MUTABLE**bắt buộc**.setExactAndAllowWhileIdle()cần quyềnSCHEDULE_EXACT_ALARM(user cấp trong Settings → Alarms & reminders).- App target SDK 31+ phải khai báo
android:exportedcho Service/Receiver. - Foreground Service (Android 14+): cần
foregroundServiceTypetrong Manifest. - Không dùng AlarmManager cho polling/network sync định kỳ - dùng WorkManager.