Computer >> Hướng Dẫn Máy Tính >  >> Lập Trình >> Android

Cách khởi động Service bằng AlarmManager trên Android

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ểmAlarmManagerWorkManager
Độ chính xác thời gianCao (setExactAndAllowWhileIdle)Không đảm bảo exact (window)
Survive rebootKhông (cần BOOT_COMPLETED receiver)Có (tự reschedule)
Survive app killCó (alarm ở system)Có
Doze mode / App StandbysetExactAndAllowWhileIdle hoạt độngTự handle, delay nếu cần
Network constraintManual checkBuilt-in (setRequiredNetworkType)
Chaining/PeriodicManualBuilt-in (PeriodicWorkRequest)
Khuyến nghị dùngAlarm clock, reminder chính xácSync data, backup, upload log, deferred work

Lưu ý quan trọng (Android 12+ / API 31+)

  • PendingIntent.FLAG_IMMUTABLE hoặc FLAG_MUTABLE **bắt buộc**.
  • setExactAndAllowWhileIdle() cần quyền SCHEDULE_EXACT_ALARM (user cấp trong Settings → Alarms & reminders).
  • App target SDK 31+ phải khai báo android:exported cho Service/Receiver.
  • Foreground Service (Android 14+): cần foregroundServiceType trong Manifest.
  • Không dùng AlarmManager cho polling/network sync định kỳ - dùng WorkManager.