[Android] AlarmManager 정리

박주호·2023년 1월 3일
0

Hi, Android

목록 보기
1/7
post-thumbnail

작년하반기에 간단한 알람 앱을 제작하며 알람매니저를 처음 사용해보았습니다.
사용하기 전엔 알람매니저만 사용하면 알아서 척척 알람이 생성되는 줄 알았으나..

그 길은 멀고도 험했으니..

알람매니저가 무엇이며 어떤식으로 작동하는지 포스팅해보겠습니다.

⏰ AlarmManager란 무엇인가요?

AlarmManager(이하 알람매니저)는 시스템 알람 서비스에 대한 액세스 즉 접근을 제공합니다.
시스템 알람을 통해 미래 특정 시점에 애플리케이션이 실행되도록 예약할 수 있게 만들어주는 클래스입니다.
알람이 울리면 등록된 Intent가 시스템에 의해 BroadCast되고 앱이 시작됩니다.
단, 등록된 알람은 디바이스를 종료하고 재부팅하면 지워진다고 명시되었습니다.
(재부팅 시 지워지는 부분은 Manifest에 Boot receiver를 등록해주어 해결)

이렇게 실행 된 앱은 등록된 알람의 receiver 메소드에서 CPU wake lock을 유지하고 지정해둔 작업이 처리되기 전 까지 디바이스를 켜둡니다. 이렇듯 알람매니저는 해당 애플리케이션이 현재 실행되지 않더라도 특정 시간에 특정 작업(알림을 띄운다던지, 소리를 낸다던지 등등)을 실행하기 위해 사용합니다.

여기서 중요한 것은 특정시간 이라는 것 입니다.
알람 매니저처럼 백그라운드에서 작업을 처리하기 위해 사용하는 Coroutine이나 WorkManager는 언제하고
올바른 알람매니저 사용 가이드는 무엇일까요?

📍 AlarmManager는 언제 사용해야 되나요?

알람매니저를 언제 사용해야하는지에 대해 공식문서에서는 가이드 라인을 제공하고 있습니다.

작업이 주어졌을 때
1. 사용자 활동을 요구하는가?
2. 반드시 정시에 실행되어야 하는가?

두가지 질문을 던져보면 무슨 API를 사용해야 하는지 알 수 있습니다.
그림에 나온 것을 토대로 사용자 활동이 요구되지 않으면서 Exact(정시)에
실행되어야 한다면 AlarmManager를 사용하면 됩니다.
그 외에는 코루틴, 워크매니저, 포그라운드 서비스 등을 이용해야합니다.

알람매니저를 제외한 다른 백그라운드 작업에 대해선 분량이 길어지니 추후 포스팅할 예정입니다

📍 AlarmManager 더 알아보기

알람매니저에는 다음과 같은 특징이 있습니다.

  • 지정된 시간 혹은 간격으로 인텐트를 실행합니다.

  • Broadcast Receivcer와 함께 알람매니저를 사용하여 서비스를 시작하고 다른 작업을 실행할 수 있습니다.

  • 애플리케이션 외부에서 작동하므로 알람매니저를 사용하면 앱이 실행중이 아니거나 기기가 대기 상태인 경우에도 이벤트나 작업을 트리거할 수 있습니다.

  • 알람은 앱의 리소스 요구사항을 최소화하는데 도움이 됩니다. 타이머 혹은 지속적으로 실행하는 백그라운드 서비스를 사용하지 않고 작업을 예약할 수 있습니다.

  • 앱이 실행되고 있는 경우에는 알람매니저보다는 Timer나 Thread & Handler 클래스 사용하는 것이 시스템 리소스를 더 효과적으로 제어할 수 있습니다.

  • 반복된 알람에 좋은 선택일 수 있지만 대상이 네트워크 작업이라면 배터리 소모가 커지고 서버에도 부담을 줄 수 있습니다. 만약 호스팅 서버를 가지고 있다면 GCM(Google Cloud Messaging)을 사용하는 것이 더 좋은 방법입니다.

📍 AlarmManager 사용해보기

알람 매니저를 사용한 코드를 살펴보겠습니다.

앞서 사용할 엔티티는 다음과 같습니다.

@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)

profile
항상 배우려는 자세로

0개의 댓글