[Android] AlarmManager 파헤치기 (1)

양현진·2022년 4월 20일
4

Oh My Android

목록 보기
4/22
post-thumbnail

기상을 도와주는 어플이나 네이버 캘린더같이 지정한 일정이 다가오면 알람이 발생하는 어플들이 있다. AlarmManager를 알기전엔 기상 어플 같은 경우는 백그라운드에서 타이머가 돌고있고, 해당 시각이 되면 Notification이나 Activity(다른 앱 위에 그리기)를 띄우고 네이버같이 대형회사들은 해당 날짜 및 시각을 등록하면 서버에 데이터를 전송하고 시간이 되면 FCM로 알림을 발생하게 하는줄 알았다.

하지만 요즘 Google의 정책 방향이 사용자가 쉽게 알 수 없는 Background Service를 제한하고 있어 결국 서버를 통해 알림을 받아와야하나 라는 생각을 했다. 이러한 제약을 해결하기 위해 AlarmManager의 사용은 적절하다고 볼 수 있다. 광고나 채팅같이 유동적인 시각이 아닌, 사용자나 개발자가 지정한 시간에 알람이 발생할 수 있도록 해준다. 예를 들어, 하루에 알맞은 식사 시간을 반복적으로 알려주는 앱을 만든다면 AlarmManager를 사용하여 App이 꺼져 있어도 Notification이 알리는 작업을 시작할 수 있다.

참고로 AlarmManager는 우리가 아는 알람기능이라 생각할 수 있는데, 정확히는 지정된 시간에 이벤트를 발생시키는 기능이다. AlarmManager의 이벤트 후 알람어플처럼 시끄럽게 만들던가 하는거지 AlarmManager를 썼다고 해서 알람이 울리는 것이 아니다.

AlarmManager 특징 및 장단점, 주의점과 사용법에 대해 알아보자!

특징 및 장점

  • 지정된 시간 또는 정해진 간격으로 인텐트를 실행
  • Broadcast Receiver와 함께 사용하여 서비스 또는 그 외 작업 실행
  • App 외부에서 작동하므로 앱이 실행 중이 아니거나 기기가 대기 상태인 경우에도 이벤트 발생
  • 백그라운드를 사용하지 않기에 리소스를 최소화

여기서 눈 여겨 볼 부분이 App 외부에서 작동하므로 앱이 실행 중이 아니거나 기기가 대기 상태인 경우에도 이벤트 발생백그라운드를 사용하지 않기에 리소스를 최소화 다.

AlarmManager는 어플 내부에서 WokerThread를 두고 타이머를 돌리는 방식이 아닌, 백그라운드에서 동작하는 방식이다. 어 근데 위에서 방금 Google이 백그라운드에 대한 작업을 제한한다 하기도 했고 백그라운드를 사용하지 않는다 했는데 얘는 Google이 만든거니 특별대우를 하는걸까? 답은 아니다. 여기서 말하는 백그라운드는 시스템에서 돌아가는 작업을 의미한다.

개별 어플에서 돌아가는 Service가 아닌, 개발하면서 종종 보는 단어인 "google-play-service"라는 플랫폼 위에서 동작하게 된다. 얘는 OS랑 비슷해서 어떤 앱이 켜지던 말던 상시 돌아가는 아이다. 원래 동작중인 플랫폼 위에 알람을 등록시키니 리소스를 최소화 할 수 있다고 말하는 것이다. 또 Android API 23부터 도입된 Doze모드라는 상태가 생겼는데, 이는 AlarmManager와 좀 다른 이야기니 이곳에서 포스팅해야겠다.

암튼 절전모드라고 생각하면 되는데 이 Doze모드에선 상당히 제한적인 백그라운드 작업만이 가능하다. 그리고 Doze모드를 몇 안되게 깨부수고 작업을 진행할 수 있는 애가 AlarmManager이다.

단점

  • 서버를 이용하여 반복 작업을 수행하는 경우 배터리와 서버에 큰 부담
  • 호스팅 서버가 있다면 동기화 어댑터와 GCM이 더 효과적 - 더 유연한 기능 제공
  • App이 실행 발생하는 시간 기반 작업은 Timer & Thread를 사용하는게 더 효과적

AlarmManager는 시스템쪽 작업이라 강력한 기능을 제공하지만 그만큼 위험부담도 더 하다. AlarmManager를 통해 반복적으로 서버와 통신을 진행하면 휴대폰이 절전을 위해 Doze모드로 진입해야할 상황에서도 AlarmManager가 이를 깨워 배터리 성능에 영향을 미친다. AlarmManager는 주로 서버가 아닌 로컬작업에 용이하며 로컬에서도 어플이 실행중인 상태에서만 작업을 진행한다면 Thread를 생성하여 Timer를 돌리는 것이 더 효율적이다.

주의점

  • 기본적으론 기기가 잠자기(Doze)모드 일 땐 발생하지 않음
    아까 AlarmManager는 Doze모드까지 깨울 수 있다고 했는데, Google에서 시원찮았는지 여러 메서드를 만들어놓고 일부만 가능하도록 했다. 이는 사용법이랑 연관된 내용이라 다음에서 자세히 말하고 주의점을 요약하자면 AlarmManager를 가능하다면 알람을 설정할 때 정확한 시간 및 Doze모드를 깨우도록 설정하는 것은 권고하지 않는 방법이다.

사용법

// AlarmManager
fun setAlarmManager(data: MutableMap.MutableEntry<Int, AlarmData>) {
    val pendingIntent = Intent(context, AlarmReceiver::class.java).let { intent ->
        PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_IMMUTABLE)
    }

    val calendar = Calendar.getInstance().apply {
        timeInMillis = System.currentTimeMillis()
        set(Calendar.HOUR_OF_DAY, 6)
        set(Calendar.MINUTE, 30)
    }

    alarmManager.setExactAndAllowWhileIdle(
        AlarmManager.RTC_WAKEUP,
        calendar.timeInMillis,
        pendingIntent
    )
}
// BroadCastReceiver
override fun onReceive(context: Context, intent: Intent) {
	  val receiveData = intent.getStringExtra(APP_NAME)
    Toast.makeText(context, receiveData, Toast.LENGTH_SHORT).show()
}

AlarmManager의 이벤트는 BroadCastRecevier에서 받을 수 있다. 그러니 AndroidManifest에서 해당 클래스를 등록해줘야한다.


PendingIntent 생성

PendingIntent란?
Pending은 우리말로 “보류중” 이라는 뜻. 이를 intent와 합치면 “보류중인 intent”의미가 되는 PendingIntent가 나온다. 의미대로 지금 당장 사용하기 보다, 추후 특정 이벤트에서 발동되는 intent이다. 목적은 외부 애플리케이션에 권한을 허가하여 안에 들어있는 Intent를 마치 본인 앱의 프로세스에서 실행하는 것처럼 사용하게 하는, 다른 컴포넌트에 위임처리를 하는 기능이다.
미래 정해진 시각에 이벤트가 발생하는 AlarmManager에게 필수 친구.

Component별 메서드

그림과 같이 Activity, BroadCast, Service에다 intent를 등록할 수 있다. AlarmManager를 써야하니 getBroadcast를 사용한다.

requestCode

  • 개별적인 작업을 요청하기 위해선 각 요청마다 다른 Int값 주입

1개의 알람등록일때는 상관없지만 여러개의 알람을 등록해야 할때 requstCode를 똑같이 해버리면 가장 늦게 등록된 알람만이 발생하게 되니 주의하자.

flag

  • FLAG_IMMUTABLE - 불변
  • FLAG_MUTABLE - 가변

PendingIntent엔 다양한 FLAG들이 있지만, Android 31부턴 단 2가지의 FLAG만 가능하다(그 외 crash). 이마저도 FLAG_MUTABLE을 사용하게 되면 warning이 발생한다. Google Develop 문서에도 flag가 0이던데 참 신기


시간 지정

  • ELAPSED_REALTIME
    시스템 부팅 시간 이후 (SystemClock.elapsedRealtime)을 기준
  • ELAPSED_REALTIME_WAKEUP
    Doze 해제
  • RTC
    UTC 시계 시간 (System.currentTimeMillis)를 기준
  • RTC_WAKEUP
    Doze 해제

시간을 지정하는 방법에는 2가지가 있다. 첫번째는 UTC로 각 나라별 시간을 기준으로 하는 방법과 휴대폰이 부팅된 시점을 기준으로 하는 방법이다. 전자는 AlarmManager.RTC로 주로 몇시몇분몇초같이 평소 사는 시간을 지정하여 알람을 발생하는 방법, 예를 들면 네이버 캘린더나 알람어플이 있고, 후자는 AlarmManager.ELASPSED_REALTIME으로 타이머나 스톱워치같이 시간을 셀 때 사용할 수 있다. Google은 RTC를 사용하면 국가 또는 언어에 영향을 받을 수 있고 ELAPSED를 권고하는데 뭐 사용하기 나름일듯 하다. 각 Flag에 맞게 System.currentTimeMillis() 또는 SystemClock.elapsedRealtime()을 뒤 파라미터로 넣으면 된다.


AlarmManager 세팅

위 언급했던 Doze모드에 대해 일부만 깨울 수 있다고 했다. 그럼 어떤 메서드로 깨울 수 있는지 살펴보자. AlarmManager엔 다양한 set메서드들이 있다. 하지만 API버전이 올라가면서, 정확한 알림을 보장했던 메서드들이 보장을 못해지고, Doze모드가 생기면서 Doze모드에선 알람이 지연되어 사실상 전 메서드들은 찬밥이다.

  • set
  • setReating
  • setWindow
  • setExact
  • setAlarmClock
  • setInexactRepeating
  • setAndAllowWhileIdle
  • setExactAndAllowWhileIdle

  1. set, setWindow, setExact
  • API 버전이 올라가면서 더 이상 정확한 시간을 보장할 수 없는 메서드 들이다. 굳이 정확한 시각에 알람이 발생하지 않아도 되는 기능이라면 이들을 사용하는걸 Google에선 권고한다
  1. setReating, setInexactRepeating
  • 이들은 알람 등록과 동시에 추가적으로 반복예약을 할 수 있는 애들이다. 예를 들자면 6시00분에 setReating을 등록하고 추가 반복시간을 1시간으로 두면 6시에 울리고 그 후 1시간마다 반복하게 된다. 둘의 차이는 이름에서 알듯이 정확하냐 안 정확하냐의 차이인데, 지금 setExactAndAllowWhileIdle도 정확하지 않는걸 몇번 경험하고 나서 과연 setReating이 정확할까 싶다.
  1. setAlarmClock, setAndAllowWhileIdle, setExactAndAllowWhileIdle
  • 이들이 바로 Doze모드를 깨우는 애들이다. setAndAllowWhileIdle, setExactAndAllowWhileIdle차이는 위와 비슷하다. 그럼 setAlarm은 뭐당가?

setAlarmClock

  • 해당 앱만 Doze에서 깨우는 setExactAndAllowWhileIdle와는 달리 setAlarmClock은 비슷한 시간대의 모든 앱을 Doze모드에서 깨우고, 정확도도 정확하다. 하지만 여러 앱의 백그라운드 작업이 재활성화되어 배터리 자원을 상대적으로 많이 잡아먹어 의도된 목적으로만 사용해야한다.

setExactAndAllowWhileIdle

  • 현재 쓰고있는 메서드. 정확한 알람시간을(Exact) 보장하고 Doze모드(Idle)동안에도 알람을 발생시킨다.

이렇게 많은 메서드를 만든 이유는 다 배터리 성능때문이다. 예를 들어 setInexactRepeating()을 사용하면 지정한 정확한 시각에 알람을 받지 못하지만, 시스템이 다른 앱의 알람 이벤트를 보낼 때 함께 보낼 수 있다. 즉, 시스템이 대기상태에서 깨는 시간을
최소화하여 배터리 등 시스템 자원을 효율적이게 사용할 수 있다.

다음 포스팅은 1주일동안 나를 애먹였던 AlarmManager가 동작하지 않는 경우와 터미널로 등록 확인여부를 알 수 있는 방법을 써볼라한다.

끗!

profile
Android Developer

6개의 댓글

comment-user-thumbnail
2022년 9월 15일

set 함수 테스트중이었는데 .... 아래와 같은 상황이어서 멘붕이었습니다. 이 정보 너무 감사해요 !! ㅜㅜ

  • 51분 알람이 51분에 울림
  • 52분 알람이 안울림
  • 53분 알람이 52분에 울림
  • 54분 알람이 52분에 울림
1개의 답글
comment-user-thumbnail
2022년 9월 25일

여러개의 알람을 설정하고 싶어서 pendingIntent.getBroadcast(requestCode 0),pendingIntent.getBroadcast(requestCode 1),pendingIntent.getBroadcast(requestCode 2)
이런식으로 여러개 만들었는데 나중에 등록된 알람만 작동을 하는데 또 수정해야할 부분이 있을까요 ㅠㅠ

2개의 답글
comment-user-thumbnail
2023년 2월 8일

좋은 포스팅 너무 감사합니다 :)

답글 달기