AlarmManager
AlarmManager는 특정한 시간과 주기로 Intent를 실행하는 기능을 제공한다.
예를 들어, 정해진 시간에 사용자에게 Notification을 보내려고 할 때 AlarmManager를 활용할 수 있다. 원하는 시간에 AlarmManager를 통해 broadcast를 전송하도록 구현하고, 그 broadcast를 받은 receiver는 Notification을 발행하도록 구현하면 된다.
특징
- 지정된 시간과 간격으로 Intent를 실행한다.
- 애플리케이션 외부에서 작동한다. 따라서 앱을 실행하고 있지 않을 때나 Doze 모드인 경우(Doze 모드를 깨우고) 특정 이벤트를 트리거할 수 있다. (Doze 모드는 절전 모드와 비슷한 개념)
- 리소스를 효율적으로 사용할 수 있다. 애플리케이션 내의 백그라운드 서비스를 사용하지 않고, 안드로이드 시스템의 백그라운드를 사용하기 때문이다.
만약 AlarmManager를 통해 지정된 시간에 서버에 접근하는 등의 네트워크 작업을 작동시키려고 한다면, 배터리 소모가 발생하고 서버에 부담을 줄 수 있다.
앱 데이터를 호스팅하는 서버를 보유하고 있다면 동기화 어댑터와 GCM를 사용하는 것이 더 효율적인 방법이라고 한다.
구현 방법
- AlarmManager 시간 유형
- 알람 발생 시 트리거 될 인텐트
일정 주기로 AlarmManager의 알람을 작동시키기 위해서는 위 네 가지의 항목이 인자로 들어간다.
(* 여기서 알람 발생은, Notification의 의미가 아닌 Event의 의미와 유사함)
AlarmManager 시간 유형
시간 기준 | Doze 모드인 경우 이벤트 발생 여부 | |
ELAPSED_REALTIME | 기기가 부팅된 후 경과한 시간 기준 | X |
ELAPSED_REALTIME_WAKEUP | 기기가 부팅된 후 경과한 시간 기준 | O |
RTC | 실제 시간 기준 | X |
RTC_WAKEUP | 실제 시간 기준 | O |
WAKEUP 버전인 경우, 기기가 Doze 모드여도 예약된 시간에 근접하게 알람을 발생시킬 수 있다.
반면에 WAKEUP 버전이 아니면 기기가 Doze 모드인 경우 예약된 시간과 다르게, 기기가 켜지고 Doze 모드가 해제되었을 때 알람을 발생시킨다.
알람 발생시 트리거 될 인텐트
alarmIntent = Intent(context, AlarmReceiver::class.java).let { intent ->
PendingIntent.getBroadcast(context, 0, intent, 0)
}
일반 Intent가 아닌 PendingIntent가 필요
PendingIntent는 말그대로 보류 인텐트, 지금 당장 인텐트를 실행하는 것이 아닌 특정 시간에 인텐트를 실행시킬 수 있도록 도와주는 객체다.
AlarmManager 알람 등록 함수의 종류
AlarmManager의 특징으로 Doze 모드를 깨우고 알람을 발생시킬 수 있다고 했다.
하지만 Doze 모드를 깨우는 건 권장하고 있지 않기 때문에, 기본적으로 Doze 모드에는 알람의 발생을 미룬다.
Doze 모드를 깨우는 함수와 깨우지 않는 함수를 나누어서 알아보자.
Doze 모드를 깨우지 않음
- set() - 일회성 알람 등록. API 버전이 올라가면서 더이상 정확한 시간을 보장하지 않는다.
- setRepeating() - 특정 주기로 알람을 등록.
- setInexactRepeating() - 특정 주기로 알람을 등록. 정확한 시간을 보장하지 않는다.
Doze 모드를 깨우고 알람 발생 (일회성 알람)
- setAndAllowWhileIdle() - 정확한 시간을 보장하지 않는다.
- setExactAndAllowWhileIdle() - 현재 일반적으로 사용하는 함수
정확한 시간을 요구하는 기능이 아니라면 setInexactRepeating(), setAndAllowWhileIdle() 등의 함수를 사용하는 것을 권장하고 있다. 이러한 함수들은 시스템이 다른 앱에 알람을 보낼 때 함께 보내기 때문에 시스템 자원을 효율적으로 사용할 수 있다. (시스템이 대기상태에서 깨는 시간 최소화)
----------------------------------------------------------------------------------------------------------------------
나는 앱에서 1분마다 타이머를 초기화하려고 AlarmManager를 사용해 알람을 설정했지만, 정확히 1분마다 알람이 발생하지 않는 문제가 발생했다... 로그를 찍어보니 1분에 정확히 타이머가 초기화 되지 않고, 1분40초, 2분 20초 이렇게 알람이 지연되거나 예상한 시점에 발생하지 않았다.
package com.rocket.cosmic_detox.presentation
import android.app.AlarmManager
import android.app.Application
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.util.Log
import com.rocket.cosmic_detox.BuildConfig
import com.rocket.cosmic_detox.presentation.receiver.TimerResetReceiver
import dagger.hilt.android.HiltAndroidApp
import java.util.Calendar
@HiltAndroidApp
class CosmicDetoxApplication : Application() {
override fun onCreate() {
super.onCreate()
// 1분마다 알람 설정
scheduleRepeatingAlarm(this)
}
// 1분마다 알람을 설정하는 함수
private fun scheduleRepeatingAlarm(context: Context) {
val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
val intent = Intent(context, TimerResetReceiver::class.java) // 리셋할 리시버 설정
val pendingIntent = PendingIntent.getBroadcast(
context,
0,
intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
// 현재 시간을 기준으로 1분 후에 알람 설정
val calendar = Calendar.getInstance().apply {
timeInMillis = System.currentTimeMillis()
add(Calendar.MINUTE, 1)
}
// 1분마다 알람이 발생하도록 설정
alarmManager.setRepeating(
AlarmManager.RTC_WAKEUP,
calendar.timeInMillis,
1 * 60 * 1000L, // 1분 간격
pendingIntent
)
Log.d("CosmicDetoxApplication", "1분마다 알람이 설정되었습니다.")
}
}
다음 코드와 같이 alarmManager의 setRepeating을 사용하여 1분마다 알람(타이머가 초기화) 되는 로직을 구현 했었다.
이와 같은 문제를 해결하기 위해 (위에 작성된 자료)들을 찾을 수 있었고,
원인들을 정리해 보았다.
Android 시스템은 배터리 효율성과 성능 최적화를 위해 알람의 정확도를 제한하는 메커니즘을 적용한다. 특히 Doze 모드와 배터리 절약 모드가 활성화된 경우, 알람이 정확히 설정된 시간에 발생하지 않을 수 있습니다.
몇 가지 주요 원인:
- Doze 모드와 절전 모드: 기기가 절전 상태로 들어가면 **AlarmManager.setRepeating()**과 같은 메서드는 정확한 시간에 알람을 발생시키지 않을 수 있다.
- Android 6.0 (Marshmallow, API 23) 이상: Doze 모드에서 앱이 백그라운드로 전환되면, 일정 시간이 지난 후에 정확한 알람을 보장하지 않을 수 있다.
- 정확한 알람 설정: 정확한 시간에 알람을 발생시키기 위해서는 **setExactAndAllowWhileIdle() 메서드를 사용해야 한다 !!
그래서 나는
정확한 시간에 알람을 발생시키며, Doze 모드에서도 정확하게 알람을 발생시키고 시스템의 절전 상태에서도 정확하게 알람을 발생하도록 보장하는 alarmManager의 setExactAndAllowWhileIdle()로 수정하여 적용시켜줬다.
수정된 코드
fun scheduleExactAlarm(context: Context) {
val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
val intent = Intent(context, MidnightResetReceiver::class.java)
val pendingIntent = PendingIntent.getBroadcast(
context,
0,
intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
// 1분 후에 알람 설정
val calendar = Calendar.getInstance().apply {
timeInMillis = System.currentTimeMillis()
add(Calendar.MINUTE, 1) // 1분 후 알람 설정
}
// 정확한 알람 설정 (Doze 모드에서도 정확하게 발생)
alarmManager.setExactAndAllowWhileIdle(
AlarmManager.RTC_WAKEUP,
calendar.timeInMillis,
pendingIntent
)
Log.d("TimerService", "1분 후에 알람이 설정되었습니다.")
}
Android 12(API 31)부터 정확한 알람을 사용하려면 특정 권한이 필요하다. 이를 위해 SCHEDULE_EXACT_ALARM 권한을 요청해야 한다.
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"/>
MainActivity에 사용자 권한을 요청하는 함수 작성
fun requestExactAlarmPermission(context: Context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
if (!context.getSystemService(AlarmManager::class.java).canScheduleExactAlarms()) {
// Android 12 이상에서 정확한 알람 권한 요청
val intent = Intent(Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM)
context.startActivity(intent)
}
}
}
----------------------------------------------------------------------------------------------------------------------
추가로 나는 자정마다 초기화 하려면 어떻게 수정
참고 자료
https://developer.android.com/develop/background-work/services/alarms/schedule?hl=ko
알람 예약 | Background work | Android Developers
이 페이지는 Cloud Translation API를 통해 번역되었습니다. 알람 예약 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. 알람 (AlarmManager 기반) 클래스)를 사용하면
developer.android.com
https://thdbs523.tistory.com/374
[안드로이드/코틀린] AlarmManager 개념 알아보고 구현하기
AlarmManager AlarmManager는 특정한 시간과 주기로 Intent를 실행하는 기능을 제공한다. 예를 들어, 정해진 시간에 사용자에게 Notification을 보내려고 할 때 AlarmManager를 활용할 수 있다. 원하는 시간에 Alar
thdbs523.tistory.com
'안드로이드 프로그래밍 > 트러블슈팅' 카테고리의 다른 글
[트러블 슈팅] Android 12 : 정확한 알람 권한 , 시스템에서 자동으로 권한을 부여하는 경우 (0) | 2024.09.23 |
---|---|
[트러블슈팅] ScrollView와 RecyclerView의 충돌 -> NestedScrollView (0) | 2024.08.27 |
[Android / 트러블슈팅] 동적으로 크기가 변하는 뷰 대응하기 (0) | 2024.07.30 |