작년하반기에 간단한 알람 앱을 제작하며 알람매니저를 처음 사용해보았습니다.
사용하기 전엔 알람매니저만 사용하면 알아서 척척 알람이 생성되는 줄 알았으나..
그 길은 멀고도 험했으니..
알람매니저가 무엇이며 어떤식으로 작동하는지 포스팅해보겠습니다.
AlarmManager(이하 알람매니저)는 시스템 알람 서비스에 대한 액세스 즉 접근을 제공합니다.
시스템 알람을 통해 미래 특정 시점에 애플리케이션이 실행되도록 예약할 수 있게 만들어주는 클래스입니다.
알람이 울리면 등록된 Intent가 시스템에 의해 BroadCast되고 앱이 시작됩니다.
단, 등록된 알람은 디바이스를 종료하고 재부팅하면 지워진다고 명시되었습니다.
(재부팅 시 지워지는 부분은 Manifest에 Boot receiver를 등록해주어 해결)
이렇게 실행 된 앱은 등록된 알람의 receiver 메소드에서 CPU wake lock을 유지하고 지정해둔 작업이 처리되기 전 까지 디바이스를 켜둡니다. 이렇듯 알람매니저는 해당 애플리케이션이 현재 실행되지 않더라도 특정 시간에 특정 작업(알림을 띄운다던지, 소리를 낸다던지 등등)을 실행하기 위해 사용합니다.
여기서 중요한 것은 특정시간
이라는 것 입니다.
알람 매니저처럼 백그라운드에서 작업을 처리하기 위해 사용하는 Coroutine이나 WorkManager는 언제하고
올바른 알람매니저 사용 가이드는 무엇일까요?
알람매니저를 언제 사용해야하는지에 대해 공식문서에서는 가이드 라인을 제공하고 있습니다.
작업이 주어졌을 때
1. 사용자 활동을 요구하는가?
2. 반드시 정시에 실행되어야 하는가?
두가지 질문을 던져보면 무슨 API를 사용해야 하는지 알 수 있습니다.
그림에 나온 것을 토대로 사용자 활동이 요구되지 않으면서 Exact(정시)에
실행되어야 한다면 AlarmManager를 사용하면 됩니다.
그 외에는 코루틴, 워크매니저, 포그라운드 서비스 등을 이용해야합니다.
알람매니저를 제외한 다른 백그라운드 작업에 대해선 분량이 길어지니 추후 포스팅할 예정입니다
알람매니저에는 다음과 같은 특징이 있습니다.
지정된 시간 혹은 간격으로 인텐트를 실행합니다.
Broadcast Receivcer와 함께 알람매니저를 사용하여 서비스를 시작하고 다른 작업을 실행할 수 있습니다.
애플리케이션 외부에서 작동하므로 알람매니저를 사용하면 앱이 실행중이 아니거나 기기가 대기 상태인 경우에도 이벤트나 작업을 트리거할 수 있습니다.
알람은 앱의 리소스 요구사항을 최소화하는데 도움이 됩니다. 타이머 혹은 지속적으로 실행하는 백그라운드 서비스를 사용하지 않고 작업을 예약할 수 있습니다.
앱이 실행되고 있는 경우에는 알람매니저보다는 Timer나 Thread & Handler 클래스 사용하는 것이 시스템 리소스를 더 효과적으로 제어할 수 있습니다.
반복된 알람에 좋은 선택일 수 있지만 대상이 네트워크 작업이라면 배터리 소모가 커지고 서버에도 부담을 줄 수 있습니다. 만약 호스팅 서버를 가지고 있다면 GCM(Google Cloud Messaging)을 사용하는 것이 더 좋은 방법입니다.
알람 매니저를 사용한 코드를 살펴보겠습니다.
앞서 사용할 엔티티는 다음과 같습니다.
@Entity
data class Alarm(
@PrimaryKey(autoGenerate = true)
val id: Int = 0,
@ColumnInfo var use: Boolean,
@ColumnInfo val hour: Int,
@ColumnInfo val minute: Int,
@ColumnInfo val repetition: DayOfWeek,
@ColumnInfo val sound: Int,
@ColumnInfo val vibration: Boolean
)
알람 데이터는 위 항목들로 구성되어 있습니다.
// 1. 선언 및 초기화
private val alarmManager by lazy { requireContext().getSystemService(Context.ALARM_SERVICE) as AlarmManager }
// 2. 등록할 알람 설정
private fun onAlarm(alarm: Alarm){
Timber.e("onAlarm: ${alarm.id} ${alarm.hour}시 ${alarm.minute}분 알람 울림")
val calender = Calendar.getInstance().apply {
set(Calendar.HOUR_OF_DAY, alarm.hour)
set(Calendar.MINUTE, alarm.minute)
set(Calendar.SECOND, 0)
// 지나간 시간의 경우 다음날 알람으로 울리도록
if (before(Calendar.getInstance())) {
add(Calendar.DATE, 1) // 하루 더하기
}
}
val pendingIntent = PendingIntent.getBroadcast(
requireContext(),
alarm.id,
Intent(requireContext(), AlarmReceiver::class.java),
PendingIntent.FLAG_IMMUTABLE
)
// 잠자기 모드에서도 허용 하는 방법
// alarmManager.setAndAllowWhileIdle()
// alarmManager.setExactAndAllowWhileIdle()
// Doze 모드
alarmManager.setExactAndAllowWhileIdle( // 정확성 보장 및 Doze 모드에서도 알람 발생
AlarmManager.RTC_WAKEUP, // 실제 시간 기준 + Doze 해제
calender.timeInMillis,
pendingIntent
)
}
정해진 시간을 사용할 것이기 때문에 캘린더 클래스에서 인스턴스를 불러와 각 항목들(시, 분, 초)을 세팅 해줍니다.
그리고 PendingIntent(지연된 intent)를 정의하여
현재 참조되고 있는 액티비티 정보와 알람아이디 그리고 알람이 실행되면 수신받을 리시버를 등록해줍니다. 여러 알람을 만들 경우 알람아이디가 중복되면 울리지 않을 수 있으니 고유값으로 설정해주어야 합니다.
PendingIntent에 대한 더 자세한 설명은 링크를 확인해주세요.
알람매니저 문서를 읽어보면 setExactAndAllowWhileIdle 메소드를 이용해 Doze(잠자기)모드이더라도 호출이 되도록 설정이 가능합니다.
단 잠자기 모드를 해제하게되면 리소스를 사용하고 배터리 손실을 일으킬 수 있기때문에
자주 사용하는 경우 지양해야합니다.
// 3. 알람 해제
private fun offAlarm(alarm: Alarm){
Timber.e("offAlarm: ${alarm.id} ${alarm.hour}시 ${alarm.minute}분 알람 종료")
val pendingIntent = PendingIntent.getBroadcast(
requireContext(),
alarm.id,
Intent(requireContext(), AlarmReceiver::class.java),
PendingIntent.FLAG_IMMUTABLE
)
alarmManager.cancel(pendingIntent)
pendingIntent.cancel()
}
알람 해제시에도 PendingIntent를 사용하여 cancel 시켜줍니다.
이때도 alarm.id가 동일해야 지정된 알람을 취소할 수 있습니다.
// 알람 OnOff 처리하기 위한 메소드
fun setUseAlarm(alarm: Alarm){
if(alarm.use){
onAlarm(alarm)
} else{
offAlarm(alarm)
}
}
추가 메소드를 이용해 분기처리해주면 좀 더 용이하게 처리가 가능합니다.
생성한 리시버에 onReceive코드에 notification등의 작업을 넣어주면 우리가 생각하는 알람이 가능합니다.
이렇게 알람매니저에 대해 간단히 알아보았습니다.
개인프로젝트로 간단히 만들어본 것이기 때문에 설명이 부족하거나 틀린 부분이 있을 수 있습니다.
조언과 지적 모두 환영입니다.
감사합니다!
https://developer.android.com/guide/background#principle
https://developer.android.com/reference/android/app/AlarmManager
https://developer.android.com/training/scheduling/alarms?hl=ko
https://developer.android.com/reference/android/app/AlarmManager#setExactAndAllowWhileIdle(int,%20long,%20android.app.PendingIntent)