본문 바로가기

안드로이드/안드로이드

[안드로이드] Alarm Manager

Alarm Manager란 말 그대로 설정한 시간에 설정한 작업을 수행할 수 있는 기능이다.

 

즉, 특정 시간에 앱을 사용하지 않아도 어떠한 작업을 할 수 있게 해주는 기능이며 특징은 다음과 같다.

 

  • 지정된 시간에 또는 정해진 간격으로 인텐트를 실행
  • Broadcast receiver와 함께 알람을 사용하여 서비스를 시작하고 다른 작업을 실행할 수 있다.
  • 애플리케이션 외부에서 작동하므로 알람을 사용하면 앱이 실행 중이 아니거나 기기가 대기 상태인 경우에도 이벤트나 작업을 트리거할 수 있다.
  • 알람은 앱의 리소스 요구사항을 최소화하는 데 도움이 됩니다. 타이머 또는 계속 실행 중인 백그라운드 서비스를 사용하지 않고 작업을 예약할 수 있다.

이외의 권장사항 또는 장단점은 공식문서를 참고 바랍니다.

https://developer.android.com/training/scheduling/alarms?hl=ko

 

반복 알람 예약  |  Android 개발자  |  Android Developers

반복 알람 예약 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. 알람(AlarmManager 클래스 기반)을 사용하면 애플리케이션이 사용되지 않을 때 시간 기반 작업을

developer.android.com

 

사용 방법

 

1. 옵션

Alarm manager에는 몇가지 옵션들이 있다.

그 중 대표적인 옵션 2개만 정의하며, 보다 자세한 내용은 다음 공식문서를 참고 바랍니다.

https://developer.android.com/develop/background-work/services/alarms/schedule?hl=ko#inexact

 

알람 예약  |  Background work  |  Android Developers

이 페이지는 Cloud Translation API를 통해 번역되었습니다. Switch to English 알람 예약 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. 알람 (AlarmManager 클래스 기반)

developer.android.com

Doze 모드 : 충전중이지 않으며, 화면이 꺼진상태로 장시간 대기중인 상태 또는 절전 모드

 

  • setInexactRepeating()

주기적인 시간 단위( ex. 1시간 마다)로 반복 알람 

해당 옵션은 Doze모드일 때에는 동작하지 않는다.

public void setInexactRepeating (
	int type,  // 알람 유형
        long triggerAtMillis,  // 트리거 시간
        long intervalMillis,   // 알람 간격
        PendingIntent operation // 인텐트
)

 

  • setExactAndAllowWhileIdle()

정확한 알림 시간을 보장하며, Doze모드일 때에도 동작한다.

public void setExactAndAllowWhileIdle (
    	int type, // 알람 유형
        long triggerAtMillis,  // 트리거 시간
        PendingIntent operation // 인텐트
)

 

그러나 Doze모드가 장시간 지속될 경우 운영체제에서 알림 시간을 변경할 수 있어 다음날 제시간에 알림이 안올 경우가 있다.

 

  • AlarmClock()
AlarmManager.AlarmClockInfo(long triggerTime, PendingIntent showIntent)
AlarmManager.setAlarmClock(AlarmClockInfo, PendingIntent)

 

AlarmClock은 setExactAndAllowWhileIdle()와 비슷한 기능을 제공하며, Doze모드도 깨우는 기능이 탑재되어 있다.

그러나 반복 기능은 제공되지 않는 일회성 알림으로, 한 번 설정한 알림이 울리면 다음엔 울리지 않는다.

 

 

2. 알람 유형

  • ELAPSED_REALTIME : 기기 부팅 시간 기준으로 대기 중인 Intent 실행, 절전모드는 해제하지 않는다.
  • ELAPSED_REALTIME_WAKEUP : 위와 동일하나 절전모드는 해제
  • RTC : 지정된 시간에 대기 중인 Intent 실행, 절전모드는 해제하지 않는다.
  • RTC_WAKEUP : 위와 동일하나 절전모드는 해제

 

3. 코드

 

Alarm.kt

class Alarm(private val context: Context) {
    private val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as? AlarmManager
	
    fun setAlarm(hour : Int, minute : Int, alarm_code : Int, content : String, checkedDayList:MutableList<Boolean>){
        val intent = Intent(context, AlarmReceiver::class.java).apply {
            putExtra("alarm_rqCode", alarm_code)
            putExtra("content", content)
            putExtra("checkedDayList", checkedDayList.toBooleanArray())
        }
		
        // 알람 동작 시 실행 할 인텐트
        val pendingIntent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S){
            PendingIntent.getBroadcast(context,alarm_code,intent,PendingIntent.FLAG_IMMUTABLE)
        }else{
            PendingIntent.getBroadcast(context,alarm_code,intent,PendingIntent.FLAG_UPDATE_CURRENT)
        }
		
        // 트리거 시간 ( 실제 시간 )
        val calendar : Calendar = Calendar.getInstance().apply {
            timeInMillis = System.currentTimeMillis()
            set(Calendar.HOUR_OF_DAY, hour)
            set(Calendar.MINUTE, minute)
            set(Calendar.SECOND, 0)
        }

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            alarmManager?.setExactAndAllowWhileIdle(
                AlarmManager.RTC_WAKEUP,
                calendar.timeInMillis,
                pendingIntent
            )
            //val alarmClock = AlarmManager.AlarmClockInfo(calendar.timeInMillis, pendingIntent)
            //alarmManager?.setAlarmClock(alarmClock, pendingIntent)
        }
    }

    fun cancelAlarm(alarm_code : Int){
        val intent = Intent(context, AlarmReceiver::class.java)
        val pendingIntent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S){
            PendingIntent.getBroadcast(context,alarm_code,intent,PendingIntent.FLAG_IMMUTABLE)
        }else{
            PendingIntent.getBroadcast(context,alarm_code,intent,PendingIntent.FLAG_UPDATE_CURRENT)
        }
        alarmManager?.cancel(pendingIntent)
    }
}

 

Alarm Manager를 통해 작업을 예약하였다면, 예약 시간에 등록해 두었던 인텐트가 실행된다.

여기서의 인텐트는 AlarmReceiver.kt 이며, 코드는 지정해 놓은 시간에 해야할 일을 알람으로 알려주는 코드다.

 

여기서는 본인이 해야 할 작업에 대한 코드를 작성해 주면 된다.

 

AlarmReceiver.kt

class AlarmReceiver : BroadcastReceiver() {
    private val CHANNEL_ID = "TodayAlarm"
    private val CHANNEL_NAME = "Alarm"

    override fun onReceive(context: Context?, intent: Intent?) {
        val today = Calendar.getInstance().get(Calendar.DAY_OF_WEEK)
        val checkedDayList = intent?.extras!!.getBooleanArray("checkedDayList")

        if(!checkedDayList!![today-1]) return

        val notificationManager: NotificationManager =
            context?.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val importance = NotificationManager.IMPORTANCE_DEFAULT
            notificationManager.createNotificationChannel(
                NotificationChannel(CHANNEL_ID, CHANNEL_NAME, importance)
            )
        }

        val intent2 = Intent(context, AlarmService::class.java)
        val requestCode = intent.extras!!.getInt("alarm_rqCode")
        val content = intent.extras!!.getString("content")

        val pendingIntent =
            if (Build.VERSION.SDK_INT>=Build.VERSION_CODES.S)
                PendingIntent.getActivity(context,requestCode,intent2,PendingIntent.FLAG_IMMUTABLE)
            else PendingIntent.getActivity(context,requestCode,intent2,PendingIntent.FLAG_UPDATE_CURRENT);


        val builder = NotificationCompat.Builder(context, CHANNEL_ID)
            .setSmallIcon(R.mipmap.todo_icon)
            .setContentTitle("정각에 해야지")
            .setContentText(content)
            .setAutoCancel(false)
            .setShowWhen(true)
            .setColor(ContextCompat.getColor(context, R.color.purple_200))
            .setPriority(NotificationCompat.PRIORITY_DEFAULT)
            .setContentIntent(pendingIntent)
            .build()

        notificationManager.notify(1, builder)
    }
}