예전 프로젝트에서 이 AlarmManager를 이용해 사용자에게 Notification을 띄워줬었는데,
내용을 정리했던 공책이 이사 도중 사라졌...ㅎㅎ😭
역시 인터넷 정리가 제일 좋은건가..
그래서 이번에는 공식문서 중 이 AlarmManager 사용방법에 대해 다루는 Schedule Alarm에 대해 학습해보겠습니다.
👇🏻 공식 문서 링크
반복 알람 예약 | Android 개발자 | Android Developers
반복 알람 예약 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. 알람(AlarmManager 클래스 기반)을 사용하면 애플리케이션이 사용되지 않을 때 시간 기반 작업을
developer.android.com
왜 AlarmManager를 사용하였는가?
제가 담당하여 구현하였던 기능은 정확히 10분 뒤에 앱이 종료된 시점에서도 사용자에게 Notification 알림만 띄워주는 기능이었습니다.
위 3가지 사항을 제공해주는 것이 바로 AlarmManager였습니다.
- 정확한 시간에
- 앱이 종료되어도 상관없이
- Background 작업없이 Notification 알림만
🌹 만약 오래걸릴 수 있는 작업(ex. DB 작업)이라면 AlarmManager가 아닌 WorkManager를 사용해야 합니다.
1. AlarmManger의 특징
- 지정된 시간 또는 정해진 간격으로(ex. 24시간마다) Intent를 실행합니다.
- BroadCase와 함께 사용하여 특정 Broadcast가 발생하였을 때, 특정 알람을 생성하도록 구현할 수도 있습니다.
- 앱과는 별개로 안드로이드 시스템에 등록하여 발생시키는 것이기 때문에, 앱의 라이프사이클과는 관계없이 수행되게 됩니다.
- timer를 사용하지 않고도 일정을 등록할 수 있기 때문에 리소스를 절약할 수 있습니다.
🛠 만약! 앱이 살아있는 도중에만 특정 시간 이후에 특정 이벤트를 발생시키고 싶으시다면 Timer를 사용하셔야 합니다!
2. Exact Alarm
시스템이 미래의 정확한 시간에 알림을 발생시키도록 할 수 있습니다.
2.1 exact alarm 설정하는 방법
2.1.1 setExact
- Doze Mode와 같이 배터리를 절약하기 위한 어떠한 동작이 있지 않는한 거의 정확한 시간에 알람을 호출합니다.
- 시간이 약간 달라져도 사용자에게 큰 영향이 가지 않는다면 이 메서드를 사용할 수 있습니다.
2.1.2 setExactAndAllowWhileIdle
- Doze Mode와 같이 배터리를 절약하기 위한 어떠한 동작이 수행중이어도 거의 정확한 시간에 알람을 호출합니다.
저희 프로젝트에서는 이 메서드를 사용하여 알람을 생성하였습니다. 🤗
val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
alarmManager.setExactAndAllowWhileIdle()
2.1.3 setAlarmClock
- 정말 중요한 알람일 경우 사용할 수 있는 메서드입니다.
- 시스템은 해당 알람을 호출하는 시간을 조정하지도 않고, 알람 호출이 필요한 경우 저전력 모드를 무시하기도 합니다.
🌹 설명에서도 알 수 있듯이, setAlarmClock은 배터리 수명과 같은 기기의 resource를 상당히 잡아먹을 수 있음에 주의해야 합니다.
위에서 보았듯, Exact Alarm은 많은 리소스를 소비할 수 있기 때문에 정말 정확한 시간에 발생해야 하는 알람이 아닌 이상
Inexact Alarm을 사용하는 것을 권장합니다.
예를 들어, Doze Mode에서도 알람이 정확한 시간에 호출되도록 하는 것은 setExactAndAllowWhileIdle이지만, 가능한한
setAndAllowWhileIdle() 사용을 권장하고 있습니다. ☺️
2.2 Permission 추가
2.2.1 Android12(API32) 이상
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"/>
2.2.2 Android13(API33) 이상
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"/>
또는
<uses-permission android:name="android.permission.USE_EXACT_ALARM"/>
위 두 Permission은 비슷해보일 수 있으나, 몇 가지에서 차이점이 있습니다.
USE_EXACT_ALARM | SCHEDULE_EXACT_ALARM |
자동적으로 허용 | 사용자에게 허용할 것인지 물어본다. |
사용자에 의해 취소될 수 없다 | 취소되었는지 확인하는 작업이 필요하다. |
사용 범위가 한정적이다. | 사용 가능범위가 넓다. |
딱 보았을 때, USE_EXACT_ALARM을 사용하게 되면 개발자가 해야하는 업무가 줄어들 수 있을거 같다는 생각이 드네요.
그와는 반대로 SCEDULE_EXACT_ALARM은 사용자가 허용해야 하며, 사용자와 시스템에 의해 권한이 취소될 수 있습니다.
이 SCHEDULE_EXACT_ALARM이 취소된 이후, 아무런 조치를 취해주지 않으면 앱이 멈추고 모든 알람이 함께 취소됩니다.
2.2.3 사용자가 권한을 취소하는 경우
이러한 경우에는 다른 권한을 체크하는 것과 동일하게 canScheduleExactAlarms 메서드를 통해 해당 권한의 취소여부를 확인한 후
적절한 조치를 취해주면 됩니다.
2.2.4 시스템에 의해 권한이 취소되는 경우
이럴 경우에는 시스템이 생성한 Broadcast를 받아 처리할 수 있습니다.
🛠 ACTION_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED
지금까지 정확한 시간에 알람을 발생시킬 수 있도록 알람을 등록하는 메스드와 이러한 메서드를 호출하기 위한 권한에 대해 알아보았습니다.
그 다음으로 정해야 하는 것은 언제 알람을 발생시킬 것인가 시간을 정하는 것입니다! 🔥
3. Alarm Type
시간은 그냥 Nms 후에 울리도록 해두면 되지 무엇이 더 있는 것일까?
하지만 그것이 아니었습니다.. 😭
안드로이드에서는 두 가지 타입의 clock type을 제공해주고 있습니다.
- elasped real time: system이 시작 된 이후(booting된 이후)부터 시간 계산 시작
- real time clock(RTC): UTC 시간 사용
안드로이드에서는 1번 Elased Real Time을 사용하는 것을 권장하고 있습니다.
왜냐하면 real time은 time zone과 locale의 영향을 받으므로 마~안약에 사용자가 시간대를 이동할 경우, 변경될 수도 있기 때문입니다.
🌹 하지만 시간대 위치에 따라 시간을 계산해야 한다면 RTC를 사용할 수 있습니다. 결국 Use Case에 따라 적절히 선택하시면 될듯해요.
위 두 버전은 모두 wakeup 버전을 추가로 가지고 있습니다. (예를 들어, elased_realtime_wakeup)
이는 절약 모드 또는 Doze Mode에서도 알람이 울릴 수 있도록 해줍니다.
정리하자면
- ELAPSED_REALTIME
- ELAPSED_REALTIME_WAKEUP
- RTC
- RTC_WAKEUP
저는 프로젝트에서 특정 시간에 알람이 실행되도록 하기 위해 아래와 같이 구현하였습니다. 🤗
alarmManager.setExactAndAllowWhileIdle(
AlarmManager.ELAPSED_REALTIME_WAKEUP, // 절전모드 작동
triggerTime,
pendingIntent
)
4. Alarm 취소
// 알람 등록시 전달했던 PendingIntent 전달
alarmManager?.cancel(pendingIntent)
5. 기기 재시작 시 알람 시작하기★
기본적으로, 기기가 종료되면 등록해두었던 알람들이 모두 제거됩니다.
🌹 진짜인가 싶어, 직접 기기를 여러번 껐다켰는데 알람이 동작하지 않기는 하였습니다.
이러한 상황에 대처하기 위해, 기기가 재시작되었을 때 Broadcase를 받아 알림을 재생성주는 로직이 추가적으로 필요합니다. 😱
5.1 Permission 등록
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
5.2 Broadcast Receiver 등록
<receiver
android:name=".worker.system.BootCompleteReceiver"
android:exported="false">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.intent.action.QUICKBOOT_POWERON" />
</intent-filter>
</receiver>
5.3 Alarm을 재생성해주는 로직
class BootCompleteReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent?) {
intent?.let {
if (intent.action == "android.intent.action.BOOT_COMPLETED") {
OneTimeWorkRequestBuilder<ResetAlarmWorker>()
.addTag(ResetAlarmWorker.TAG)
.build()
.run {
WorkManager.getInstance(context)
.beginWith(this)
.enqueue()
}
}
} ?: return
}
}
6. 추가
제가 했던 토이 프로젝트에서 Alarm을 등록했던 코드입니다.
object DeliveryAlarmManager {
fun create(context: Context, deliveryAlarmModel: DeliveryAlarmModel) {
val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
val intent = Intent(context, DeliveryAlarmReceiver::class.java)
intent.putExtra(DeliveryAlarmReceiver.KEY_ORDER_ITEM, deliveryAlarmModel)
val pendingIntent = PendingIntent.getBroadcast(
context,
deliveryAlarmModel.orderId.toInt(),
intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
val extraTime =
deliveryAlarmModel.expectedTime - (System.currentTimeMillis() - deliveryAlarmModel.createdAt)
val triggerTime = SystemClock.elapsedRealtime() + extraTime
alarmManager.setExactAndAllowWhileIdle(
AlarmManager.ELAPSED_REALTIME_WAKEUP, // 절전모드에서는 작동 안함
triggerTime,
pendingIntent
)
}
}
안드로이드에서 Alarm을 생성하고 등록하는 방법에 대해 정~말 간단하게 알아보았습니다.
감사합니다. 😌
'Android > Android' 카테고리의 다른 글
[Android] Retrofit의 ThreadPool : 왜 Retrofit은 Dispatcher 변경을 하지 않아도 되는것일까.. (0) | 2023.03.10 |
---|---|
[Android/Compose] Compose에서 Paging3 적용기 (0) | 2023.03.03 |
[Hilt] Component와 Scope 무엇이 다른지 디버깅으로 다 찍어보자! (0) | 2023.02.11 |
[Android:Codelab] Dependency Injection and Test Doubles - 5 (0) | 2023.01.26 |
[Android:Codelab] Dependency Injection and Test Doubles - 4 (0) | 2023.01.25 |