본문 바로가기

Android

[Android] AlarmManager

개요

AlarmManager는 앱과 안드로이드 시스템 알람 서비스의 연결해 주는 역할을 한다.

사용자가 지정한 시간에 앱에게 broadcast를 보내고 설정된 작업들을 실행한다.

 

구글은 배터리 사용 관련해서 AlarmManager에 제약을 걸었다.

알람은 더 이상 지정한 정확한 시간에 울리지 않는다.

즉, 시스템이 기기 상태에 따라 최적화된 시간으로 알람을 미룰 수 있다.

 

사용자가 기기를 재부팅 한 뒤에는 설정되어 있던 모든 알람이 취소된다.

그래서 반드시 재부팅 뒤에는 설정되어 있던 모든 알람을 다시 설정해야 한다.

 

사용방법

  1. BroadcastReceiver()를 상속받은 새로운 클래스 생성
  2. 리시버로 전달될 Intent 구현
  3. 알람은 일정 시간 뒤에 실행되기에 PendingIntent 구현
  4. AlarmManager의 API 사용하여 알람을 설정
  5. 알람 취소 방법
  6. 기기 재부팅 시 알람 재설정

1. BroadcastReceiver() 를 상속받은 새로운 클래스 생성

알람이 실행될 때 작업을 위한 리시버 클래스 즉, BroadcastReceiver() 를 상속받은 클래스를 생성한다.

실제 작업할 내용은 onReceive() 메서드에 담으면 된다.

class BookAlarmReceiver : BroadcastReceiver() {
  
    override fun onReceive(context: Context, intent: Intent) {
        // TODO 알람이 실행되었을 때 실제 동작하는 부분
    }
}

 

2. 리시버로 전달될 Intent 구현

알람을 설정할 Activity나 Fragment에서 1번에서 설정한 리시버 클래스의 인텐트를 구현한다.

val bookAlarmIntent = Intent(requireContext(), BookAlarmReceiver::class.java)

 

3. 알람은 일정 시간 뒤에 실행되기에 PendingIntent 구현

알람은 일정 시간이 흐른 뒤에 실행되기 때문에 2번에서 생성한 인텐트를 통해 PendingIntent를 구현

val bookAlarmPendingIntent = PendingIntent.getBroadcast(
    requireContext(),
    0,
    bookAlarmIntent,
    PendingIntent.FLAG_UPDATE_CURRENT
)

파라미터 설명 PendingIntent.getBroadcast(1, 2, 3, 4)

  • context: Context
  • requestCode: Int - 해당 알람에 해당 고유한 ID이다. 알람을 여러개 생성 시 다른 ID를 할당해야 한다.
  • intent: Intent - 리시버로 전달될 Intent
  • flags: Int - 새롭게 또는 존재하는 PendingIntent 에 대한 처리 방침으로 5개의 flag가 있다.
flags 설명
FLAG_ONE_SHOT 한번만 실행
FLAG_NO_CREATE 생성된 PendingIntent가 없으면 null을 반환하고, 아니면 생성
FLAG_CANCEL_CURRENT PendingIntent가 이미 등록되어 있으면 취소하고, 새로운 PendingIntent 생성
FLAG_UPDATE_CURRENT PendingIntent가 이미 등록되어 있으면 유지하고, Intent의 extra data는 새로 전달된 Intent로 교체
FLAG_IMMUTABLE 생성된 PendingIntent는 변하지 않음. 새로운 인텐트의 arguments들은 무시

 

4. AlarmManager의 API 사용하여 알람을 설정

  • 알람을 실행하는 방법은 두가지로 한번만 동작하는 것과 주기적으로 동작 하는 알람이 있다.
  • 알람 유형에 따라 시간을 설정할 수 있다.
    • 실제 경과 시간(ELAPSED_REALTIME) : 시스템 부팅 이후 시간을 참조, 시간대/언어의 영향을 받지 않음(경과 시간에 기반한 알람 적합)
    • 실시간 시계(RTC) : 실제 시간을 참조, 사용자가 시간 설정을 변경한 경우 그 시간을 따라감
알람 유형 설명
ELAPSED_REALTIME_WAKEUP 기기 부팅된 후 경과 시간 기반으로 인텐트 실행, 절전모드 해제하여 디바이스를 깨움
ELAPSED_REALTIME 기기 부팅된 후 경과 시간 기반으로 인텐트 실행
RTC_WAKEUP 지정된 시간에 인텐트 실행, 절전모드 해제하여 디바이스를 깨움
RTC 지정된 시간에 인텐트 실행

 

4-1. 한번만 동작하는 알람 (실제 경과 시간)

// 1분 뒤에 기기 절전모드 해제하여 한번 알람을 실행
alarmManager.set(
    AlarmManager.ELAPSED_REALTIME_WAKEUP,
    SystemClock.elapsedRealtime() + 60 * 1000,
    bookAlarmPendingIntent
)

alarmManager.set(1, 2, 3) 파라미터 설명

  • type: Int - 알람 유형으로 '실시간 시계(RTC)''실제 경과 시간(ELAPSED_REALTIME)'가 있다. (자세한 설명은 하단에 있음)
  • triggerAtMillis: Long - 알람을 실행할 시간으로 milliseconds 단위로 입력
  • operation: PendingIntent - 알람이 울렸을 때 실행할 작업을 정의한 pendingIntent

4-2. 주기적으로 동작하는 알람 (실제 경과 시간)

// 1시간 뒤에 알람이 울리고, 1시간 단위로 주기적 알람을 실행 (기기 절전모드 해제하지 않음)
alarmManager.setInexactRepeating(
    AlarmManager.ELAPSED_REALTIME,
    SystemClock.elapsedRealtime() + AlarmManager.INTERVAL_HOUR,
    AlarmManager.INTERVAL_HOUR,
    bookAlarmPendingIntent
)

alarmManager.setInexactRepeating(1, 2, 3, 4) 파라미터 설명 / (1, 2, 4)는 위와 동일

  • intervalMillis: Long - 주기적으로 실행할 시간으로 milliseconds 단위로 입력

4-3. 한번만 동작하는 알람 (실시간 시계)

// 2시30분에 울리는 알람 시간 설정
val calendar: Calendar = Calendar.getInstance().apply {
    timeInMillis = System.currentTimeMillis()
    set(Calendar.HOUR_OF_DAY, 14)
    set(Calendar.MINUTE, 30)
}

// 2시30분에 기기 절전모드 해제하여 한번 알람을 실행
alarmManager.set(
    AlarmManager.RTC_WAKEUP,
    calendar.timeInMillis,
    bookAlarmPendingIntent
)

 

4-4. 주기적으로 동작하는 알람 (실시간 시계)

// 1분 뒤 기기 절전모드 해제하여 하루 단위 주기적 알람을 실행
alarmManager.setInexactRepeating(
    AlarmManager.RTC_WAKEUP,
    System.currentTimeMillis() + 60 * 1000,
    AlarmManager.INTERVAL_DAY,
    bookAlarmPendingIntent
)

 

5. 알람 취소

설정되어 있는 알람을 취소하는 방법은 AlarmManager.cancel() 호출하여 취소할 PendingIntent 전달

alarmManager.cancel(bookAlarmPendingIntent)

 

6. 기기 재부팅 시 알람 재설정

  • 기기가 종료되면 모든 알람이 취소됨
  • 해결 방법으로는 재부팅 시 자동으로 기존 알람 다시 설정

6-1. 애플리케이션 AndroidManifest.xml 파일에서 RECEIVE_BOOT_COMPLETED 권한 설정

<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />

이 권한을 사용하면 시스템 부팅이 끝난 후에 앱은 브로드캐스팅되는 ACTION_BOOT_COMPLETED 을 받음(최소 한 번 이상 앱을 실행한 경우에만 동작)

 

6-2. BroadcastReceiver 를 구현하여 브로드캐스트 받는다

// 알람 재설정을 위한 리시버를 새로 생성하거나, 기존에 생성한 리시버를 사용할 수 있다.
// 아래 예제에서는 기존 리시버 사용하여 알람 재설정
class BookAlarmReceiver : BroadcastReceiver() {
  
    override fun onReceive(context: Context, intent: Intent) {
        if (intent.action == "android.intent.action.BOOT_COMPLETED") {
            // TODO 재부팅 시 기존 알람 재설정하는 부분
        } else {
            // TODO 알람이 실행되었을 때 실제 동작하는 부분 
        }
    }
}

 

6-3. AndroidManifest.xml 파일에서 ACTION_BOOT_COMPLETED 작업 필터링 하는 인텐트와 함께 수신기 추가

<receiver
    android:name=".BookAlarmReceiver"
    android:enabled="true"
    android:exported="false">
    <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED" />
    </intent-filter>
</receiver>

 


알람이 얼마나 정확해야 하는가

  • 알람 실행 시 위에서 설명한 방법은 시스템 환경에 알맞게 대략적인 시간에 실행되는 알람이다
  • 위 방법은 Android에서 귄하는 방식으로 여러개의 부정확한 반복 알람을 동기화하고 동시에 실행하기에 배터리 소모를 줄일 수 있다
  • 알람을 정확한 시간에 실행하는 방법
    • 한번 실행 시 : setExact() 사용
    • 주기적 실행 시 : setRepeating() 사용

Doze(잠자기) 와 App Standby(앱 대기)가 알람에 미치는 영향

  • Android 6.0(API 23)부터 기기 배터리 수명 연장을 위해 Doze와 App Standby 도입됨
  • 기기가 Doze(잠자기)모드일 때 모든 표준 알람은 기기가 Doze모드를 종료하거나 유지보수 기간이 시작될 때까지 지연됨
  • 기기가 Doze모드일 때 알람을 실행하는 방법setAndAllowWhileIdle() 또는 setExactAndAllowWhileIdle() 사용
  • 앱이 Idle(유휴상태)이면 App Standby모드로 전환됨, 즉 사용자가 일정 시간 앱을 사용하지 않았고 앱에 foreground process가 없다는 의미이다
  • 기기가 App Standby(앱 대기)일 때 알람은 Doze모드와 같이 알람이 연기됨
  • 이 제한은 앱이 더 이상 Idle가 아니거나 기기가 전원에 연결되어 있을 때 해제됨

 

참고

https://developer.android.com/training/scheduling/alarms