AlarmManager 를 사용하면 어플리케이션이 사용중이지 않을 때에도,
시간 기반 작업을 생성할 수 있다.
하지만 효율성의 문제로 여러가지 권장사항이 존재한다.
-> 네트워크 작업은 동기화 어댑터와 함께 서버에서 직접 사용하는 것이 훨씬 더 유연하고, 만약 정확한 시간에 호출되어 동기화하는 알람이라면 그 앱의 모든 유저들을 동기화 하게 되어 서버의 과부하가 발생한다.
-> 시스템의 리소스가 빨리 소모되어 배터리 효율이 저하함.
-> 정밀하게 설정하는 setRepeating() 대신 setInexactRepeating() 을 사용하면 여러 앱의 반복 알람을 동기화하고 동시에 실행하기 때문에 시스템이 절전모드를 해제해야하는 횟수가 줄어들어 배터리 효율이 좋아지기 때문.
-> Real time(실제시간)은 UTC 시간을 사용하기 때문에 사용자가 설정한 시간대, 언어의 영향을 받아 오동작할 가능성이 있기 때문에 가능하다면 Elapsed time(기기 부팅 후 경과시간)을 사용해야함
https://developer.android.com/training/scheduling/alarms?hl=ko
위 개발자 문서에서 더 자세한 내용을 확인할 수 있다.
New -> Other -> BroadcastReceiver 를 클릭하여 추가해준다.
class MyReceiver : BroadcastReceiver() {
lateinit var notificationManager: NotificationManager
override fun onReceive(context: Context, intent: Intent) {
notificationManager = context.getSystemService(
Context.NOTIFICATION_SERVICE) as NotificationManager
createNotificationChannel()
deliverNotification(context)
}
클래스에서 lateinit으로 notificationManager를 선언해주고
Broadcast가 수신되면 자동으로 호출되는 메서드인 onReceive 에서 초기화해준다.
또한 Notification 을 띄우기 위한 Channel 등록을 위한 createNotificationChannel 메소드와
Notification 등록을 위한 deliverNotification 메소드를 구현해준다.
class Constant {
companion object {
// 아이디 선언
const val NOTIFICATION_ID = 0
const val CHANNEL_ID = "notification_channel"
// 알림 시간 설정
const val ALARM_TIMER = 5
}
}
다른 클래스에서 사용할 상수들을
따로 정리하여 저장해놓았다.
오레오 버전 이상에서는
노티피케이션을 띄우기 위하여 의무적으로 채널을 등록해야 한다.
// Notification 을 띄우기 위한 Channel 등록
fun createNotificationChannel(){
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val notificationChannel = NotificationChannel(
CHANNEL_ID, // 채널의 아이디
"채널 이름입니다.", // 채널의 이름
NotificationManager.IMPORTANCE_HIGH
/*
1. IMPORTANCE_HIGH = 알림음이 울리고 헤드업 알림으로 표시
2. IMPORTANCE_DEFAULT = 알림음 울림
3. IMPORTANCE_LOW = 알림음 없음
4. IMPORTANCE_MIN = 알림음 없고 상태줄 표시 X
*/
)
notificationChannel.enableLights(true) // 불빛
notificationChannel.lightColor = Color.RED // 색상
notificationChannel.enableVibration(true) // 진동 여부
notificationChannel.description = "채널의 상세정보입니다." // 채널 정보
notificationManager.createNotificationChannel(
notificationChannel)
}
}
채널을 등록해주고
// Notification 등록
private fun deliverNotification(context: Context){
val contentIntent = Intent(context, MainActivity::class.java)
val contentPendingIntent = PendingIntent.getActivity(
context,
NOTIFICATION_ID, // requestCode
contentIntent, // 알림 클릭 시 이동할 인텐트
PendingIntent.FLAG_UPDATE_CURRENT
/*
1. FLAG_UPDATE_CURRENT : 현재 PendingIntent를 유지하고, 대신 인텐트의 extra data는 새로 전달된 Intent로 교체
2. FLAG_CANCEL_CURRENT : 현재 인텐트가 이미 등록되어있다면 삭제, 다시 등록
3. FLAG_NO_CREATE : 이미 등록된 인텐트가 있다면, null
4. FLAG_ONE_SHOT : 한번 사용되면, 그 다음에 다시 사용하지 않음
*/
)
val builder = NotificationCompat.Builder(context, CHANNEL_ID)
.setSmallIcon(R.drawable.ic_alarm) // 아이콘
.setContentTitle("타이틀 입니다.") // 제목
.setContentText("내용 입니다.") // 내용
.setContentIntent(contentPendingIntent)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setAutoCancel(true)
.setDefaults(NotificationCompat.DEFAULT_ALL)
notificationManager.notify(NOTIFICATION_ID, builder.build())
}
노티피케이션을 등록해준다.
class MainActivity : AppCompatActivity() {
private lateinit var binding : ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 뷰바인딩
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
val alarmManager = getSystemService(ALARM_SERVICE) as AlarmManager
val intent = Intent(this,MyReceiver::class.java)
val pendingIntent = PendingIntent.getBroadcast(
this, NOTIFICATION_ID, intent,
PendingIntent.FLAG_UPDATE_CURRENT
)
// 토글버튼 활성화 시 알림을 생성하고 토스트 메세지로 출력
binding.toggleButton.setOnCheckedChangeListener { _, check ->
val toastMessage = if (check) {
val triggerTime = (SystemClock.elapsedRealtime() // 기기가 부팅된 후 경과한 시간 사용
+ ALARM_TIMER * 1000) // ms 이기 때문에 초단위로 변환 (*1000)
alarmManager.set(
AlarmManager.ELAPSED_REALTIME_WAKEUP,
triggerTime,
pendingIntent
) // set : 일회성 알림
"$ALARM_TIMER 초 후에 알림이 발생합니다."
} else {
alarmManager.cancel(pendingIntent)
"알림 예약을 취소하였습니다."
}
/*
1. ELAPSED_REALTIME : ELAPSED_REALTIME 사용. 절전모드에 있을 때는 알람을 발생시키지 않고 해제되면 발생시킴.
2. ELAPSED_REALTIME_WAKEUP : ELAPSED_REALTIME 사용. 절전모드일 때도 알람을 발생시킴.
3. RTC : Real Time Clock 사용. 절전모드일 때는 알람을 발생시키지 않음.
4. RTC_WAKEUP : Real Time Clock 사용. 절전모드 일 때도 알람을 발생시킴.
*/
Toast.makeText(this, toastMessage, Toast.LENGTH_SHORT).show()
}
}
}
토글버튼 활성화 시
정한 시간 뒤에 알림이 발생한다는 토스트가 뜨고,
알림이 발생한다.
노티피케이션의 속성으로 들어가면 세부내용을 볼 수 있다.
// 토글버튼 활성화 시 알림을 생성하고 토스트 메세지로 출력
binding.toggleButton.setOnCheckedChangeListener { _, check ->
val toastMessage = if (check) {
val triggerTime = (SystemClock.elapsedRealtime() // 기기가 부팅된 후 경과한 시간 사용
+ ALARM_TIMER * 1000) // ms 이기 때문에 초단위로 변환 (*1000)
alarmManager.setExact(
AlarmManager.ELAPSED_REALTIME_WAKEUP,
triggerTime,
pendingIntent
) // set : 일회성 알림
"$ALARM_TIMER 초 후에 알림이 발생합니다."
} else {
alarmManager.cancel(pendingIntent)
"알림 예약을 취소하였습니다."
}
set을 사용하면, 비교적 정확하지 않은 시간에 알람이 발생한다.
alarmManager에서
set이 아닌 setExact를 사용하면 정확한 시간에 알람이 발생한다.
binding.toggleButton2.setOnCheckedChangeListener { _, check ->
val toastMessage = if (check) {
val repeatInterval = AlarmManager.INTERVAL_FIFTEEN_MINUTES
/*
1. INTERVAL_FIFTEEN_MINUTES : 15분
2. INTERVAL_HALF_HOUR : 30분
3. INTERVAL_HOUR : 1시간
4. INTERVAL_HALF_DAY : 12시간
5. INTERVAL_DAY : 1일
*/
val triggerTime = (SystemClock.elapsedRealtime()
+ repeatInterval)
alarmManager.setInexactRepeating(
AlarmManager.ELAPSED_REALTIME_WAKEUP,
triggerTime, repeatInterval,
pendingIntent
) // setInexactRepeating : 반복 알림
"${repeatInterval/60000}분 마다 알림이 발생합니다."
} else {
alarmManager.cancel(pendingIntent)
"알림 예약을 취소하였습니다."
}
Toast.makeText(this, toastMessage, Toast.LENGTH_SHORT).show()
}
setInexactRepeating 를 사용하여
반복되는 알람을 설정할 수 있다.
하지만 반복주기는 정해진 5가지의 주기로밖에 설정할 수 없다.
binding.toggleButton2.setOnCheckedChangeListener { _, check ->
val toastMessage = if (check) {
val repeatInterval : Long = ALARM_TIMER * 1000L
val triggerTime = (SystemClock.elapsedRealtime()
+ repeatInterval)
alarmManager.setRepeating(
AlarmManager.ELAPSED_REALTIME_WAKEUP,
triggerTime, repeatInterval,
pendingIntent
)
"${repeatInterval/1000}초 마다 알림이 발생합니다."
} else {
alarmManager.cancel(pendingIntent)
"알림 예약을 취소하였습니다."
}
Toast.makeText(this, toastMessage, Toast.LENGTH_SHORT).show()
}
setRepeating 를 사용하면
주기도 맘대로 정할 수 있다.
// realtime
binding.toggleButton3.setOnCheckedChangeListener { _, check ->
val toastMessage = if (check) {
val repeatInterval : Long = ALARM_TIMER * 1000L
val calendar = Calendar.getInstance().apply {
timeInMillis = System.currentTimeMillis()
set(Calendar.HOUR_OF_DAY,5)
set(Calendar.MINUTE,30)
}
alarmManager.setRepeating(
AlarmManager.RTC_WAKEUP,
calendar.timeInMillis,
repeatInterval,
pendingIntent)
"알림이 발생합니다."
} else {
alarmManager.cancel(pendingIntent)
"알림 예약을 취소하였습니다."
}
Toast.makeText(this, toastMessage, Toast.LENGTH_SHORT).show()
}
원하는 날짜에도 알림을 발생시킬 수 있습니다.
class BootReceiver : BroadcastReceiver() {
companion object {
const val TAG = "BootReceiver"
}
override fun onReceive(context: Context, intent: Intent) {
Log.d(TAG, "Received intent : $intent")
if (intent.action == "android.intent.action.BOOT_COMPLETED") {
// Register alarm
}
}
}
부트리시버를 만들어
부팅시 알림을 다시 호출해줍니다.
https://github.com/HanYeop/AndroidStudio-Practice2/tree/master/AlarmEx
참고
https://codechacha.com/ko/android-alarmmanager/
https://developer.android.com/training/scheduling/alarms?hl=ko