Blog

Scheduling Repeating Alarms

December 18, 2013

Scheduling Repeating Alarms

AlarmManager 클래스에 기반한 알람은 앱의 생명주기 이외의 상황에서도 작업을 수행할 수 있는 방법을 제공한다. 예를 들어, 일기예보 앱처럼 날씨정보를 다운받기 위해 하루에 한 번 구동 하는 앱을 실행하는데 사용될 수 있다.

알람은 다음과 같은 특징을 갖고있다:

  • 특정 시간이나 인터벌에 인텐트를 작동시킬 수 있다.
  • 서비스를 시작하고 작업을 실행하기 위해 브로드캐스트 리시버와 함께 사용할 수 있다.
  • 앱 밖에서도 작동되기 때문에, 단말 자체가 sleep 모드이거나 앱이 구동되지 않은 상태에서 이벤트나 액션을 트리거 할때도 사용할 수 있다.
  • 앱의 리소스를 적게 사용할 수 있다. 지속적으로 백그라운드 서비스를 실행시키거나 타이머에 의존하지 않고 작업을 스케줄할 수 있다.

앱이 살아있는 동안 작동될 것이 확실한 타이밍 작업의 경우, Timer와 Thread와 함께 Handler 클래스를 사용하는 것을 추천한다. 이 방법은 안드로이드에서 시스템 리소스 제어를 더욱 잘 할 수 있도록 한다.

Set a Repeating Alarm

위에서 설명한 바와 같이, 반복 알람은 지속적인 이벤트나 데이터 조회를 위한 좋은 방법이다. 반복 알람은 다음과 같은 특성이 있다:

  • 알람 타입. 더 많은 정보를 원한다면 Choose an alarm type 참고.
  • 트리거 타임 : 만약 트리거 타임이 과거로 설정되어있다면, 알람은 즉시 트리거된다.
  • 알람 간격 : 예를 들어, 하루에 한 번, 한 시간에 한 번, 5초에 한 번, 등.
  • 알람이 트리거 될 때 펜딩 인텐트가 작동된다. 같은 펜딩 인텐트를 사용하는 두 번째 알람을 셋팅하면, 그것은 첫번째 알람을 대체하게 된다.

반복 알람 설정시 옵션선택이 앱이 얼마나 시스템 리소스를 효율적으로 사용할지를 결정한다. 신중히 관리 된 알람도 배터리 수명에 큰 영향을 미칠 수 있으므로 앱을 설계할 때 다음 가이드라인을 따르도록 한다.

  • 알람 빈도를 최소화한다.
  • 필요 이상으로 디바이스를 깨우지 않는다 (Choose an alarm type에 나오듯, 이 행동은 알람 타입에 따라 결정된다)
  • 알람의 트리거 타임을 필요 이상으로 정확하게 설정하지 않는다.
  • 가능한 setRepeating() 대신 setInexactRepeating() 를 사용한다. setInexactRepeating() 를 사용할 때, 안드로이드 단말은 여러 부정확한 반복 알람을 동기화하고, 그것을 동시에 작동시킨다. 이것은 배터리의 소모를 줄일 수 있다.
  • 알람 동작이 정확한 트리거 시간 (예를들어, 알람이 오전 7시에 동작되고, 그 후 매 20분마다 동작된다) 보다 인터벌 간격을 기반으로 하는 경우 (예를 들어, 알람이 한 시간에 한 번씩 동작하는 경우) 라면, ELAPSED_REALTIME 알람 타입을 사용한다.

Choose an alarm type

반복되는 알람을 만들때 가장 먼저 고려해야 할 사항 중 하나는 어떤 타입의 알람으로 만들지 결정하는 것이다.

알람에는 일반적으로 “elapsed real time” 과 “real time clock” (RTC)라는 두 가지의 시간 타입이 있다. Elapsed real time은 “시스템 부팅 후 시간”을 사용하고, RTC는 UTC로 일반적인 시계가 사용하는 시간을 사용한다. 이것은 elapsed real time의 경우 타임존과 로케일에 영향을 받지 않기 때문에 시간 경과에 기반한 알람 (예를 들어, 30초에 한 번 작동하는 알람)을 셋팅하는 데 적합하며 RTC 타입은 현재 로케일에 의존하는 알람에 더 적합하다.

두 타입 모두 스크린이 꺼져있을 때 디바이스의 CPU를 깨우는 “wake up” 버전을 갖고있다. 이를통해 예약 된 시간에 알람을 작동시키도록 한다. 이것은 앱이 특정시간에 따라 어떤 기능을 수행해야 할 경우에 매우 유용하다. 예를 들어, 특정 작업을 수행할 수 있는 화면이 정해져 있을 경우에 유용하다. 만약 알람 타입에 wakeup 버전을 사용하지 않는다면, 디바이스가 다음으로 awake 한 상태에서 반복 알람이 작동될 것이다.

만약 알람이 단순히 일정 간격마다 (예를들어, 30분 마다) 구동되기를 원한다면, elapsed real time 타입 중 하나를 사용한다. 일반적으로 이 방법이 더 유용하다. 만약 알람이 하루 중 특정 시간에 동작되어야 한다면, 시계 기반의 real time clock 타입 중 하나를 선택한다. 하지만 real time clock은 가끔 문제가 될 수 있다는 것을 알아야 한다 — 앱이 다른 로케일로 잘 전달되지 않을 수 있고, 만약에 유저가 디바이스 시간을 변경하면 앱에서 예기치 못한 동작들이 발생할 수 있다.

타입 리스트는 다음과 같다:

  • ELAPSED_REALTIME — 디바이스를 깨우지 않은 상태에서 디바이스가 부딩 퇸 후 시간의 경과에 따라 펜딩 인텐트를 작동시킨다. 경과 시간에는 디바이스가 ‘sleep’ 상태였을 때도 포함된다.
  • ELAPSED_REALTIME_WAKEUP — 디바이스 부팅 후 일정 시간이 지난 후에 디바이스를 깨우고 펜딩 인텐트를 작동시킨다.
  • RTC — 지정된 시간에 펜딩 인텐트를 작동시키지만, 디바이스를 깨우지는 않는다.
  • RTC_WAKEUP — 지정된 시간에 펜딩 인텐트를 작동시키기 위해 디바이스를 깨운다.

ELAPSED_REALTIME_WAKEUP examples

다음은 ELAPSED_REALTIME_WAKEUP 를 사용하는 몇 가지 예시이다. 30분 후, 그리고 그 후 30분 간격으로 알람을 작동시키기 위해 디바이스를 깨운다.

// Hopefully your alarm will have a lower frequency than this!
alarmMgr.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,
AlarmManager.INTERVAL_HALF_HOUR,
AlarmManager.INTERVAL_HALF_HOUR, alarmIntent);
Wake up the device to fire a one-time (non-repeating) alarm in one minute:
private AlarmManager alarmMgr;
private PendingIntent alarmIntent;
…
alarmMgr = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
Intent intent = new Intent(context, AlarmReceiver.class);
alarmIntent = PendingIntent.getBroadcast(context, 0, intent, 0);
alarmMgr.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
SystemClock.elapsedRealtime() + 60 * 1000, alarmIntent;

RTC examples

다음은 RTC_WAKEUP 를 사용하는 몇 가지 예시이다. 오후 2시 즈음 알람을 작동시키기 위해 디바이스를 깨우고, 매일 같은 시간 반복되도록 한다.

// Set the alarm to start at approximately 2:00 p.m.
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(System.currentTimeMillis());
calendar.set(Calendar.HOUR_OF_DAY, 14);
// With setInexactRepeating(), you have to use one of the AlarmManager interval
// constants—in this case, AlarmManager.INTERVAL_DAY.
alarmMgr.setInexactRepeating(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(),
AlarmManager.INTERVAL_DAY, alarmIntent);
Wake up the device to fire the alarm at precisely 8:30 a.m., and every 20 minutes thereafter:
private AlarmManager alarmMgr;
private PendingIntent alarmIntent;
…
alarmMgr = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
Intent intent = new Intent(context, AlarmReceiver.class);
alarmIntent = PendingIntent.getBroadcast(context, 0, intent, 0);
// Set the alarm to start at 8:30 a.m.
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(System.currentTimeMillis());
calendar.set(Calendar.HOUR_OF_DAY, 8);
calendar.set(Calendar.MINUTE, 30);
// setRepeating() lets you specify a precise custom interval—in this case,
// 20 minutes.
alarmMgr.setRepeating(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(),
1000 * 60 * 20, alarmIntent);

Decide how precise your alarm needs to be

위에 기재한 바와 같이, 알람을 생성할 때 가장 첫 단계는 주로 알람 타입을 결정하는 것이다. 다음 단계는 알람이 얼마나 정확해야 하는지 결정하는 것이다. 대부분의 앱들에는 setInexactRepeating() 가 적합하다. 이 메소드를 사용하면 안드로이드는 여러개의 부정확한 반복 알람들을 동기화하여 동시에 작동시킨다. 이를통해 배터리 소모를 줄일 수 있게된다.

정확한 시간이 요구되는 앱들의 경우는 — 예를 들어, 정확히 오전 8:30 부터 시작하여 그 후 매 정시에 작동되어야 하는 알람 — 은 setRepeating()을 사용한다. 하지만 정밀한 알람은 리소스에 영향을 미칠수 있으니 사용하지 않는것이 좋다.

setInexactRepeating() 에서는 setRepeating() 와 같이 커스텀 인터벌을 지정할 수 없다. INTERVAL_FIFTEEN_MINUTES, INTERVAL_DAY 등의 인터벌 상수를 사용해야만 한다. 전체 목록을 보려면 AlarmManager 를 참조한다.

Cancel an Alarm

앱에 따라 알람 취소하는 기능을 추가하고 싶을 수 있을 것이다. 알람을 취소하기 위해서는, Alarm Manager 에서 cancel() 을 호출함으로써 더이상 작동을 원하지 않는 PendingIntent 을 전달한다.

// If the alarm has been set, cancel it.
if (alarmMgr!= null) {
alarmMgr.cancel(alarmIntent);
}

Start an Alarm When the Device Boots

기본적으로 디바이스를 종료하면 모든 알람은 취소된다. 이러한 상황이 벌어지는 것을 막기 위해, 유저가 디바이스를 재부팅했을 때 앱이 자동적으로 알람을 재구동 하도록 설계할 수 있다. 이것은 AlarmManager가 유저의 수동적 알람 재구동 필요 없이 지속적으로 작업을 할 수 있도록 해준다.

설계 단계는 다음과 같다:

1. 앱 매니페스트에 RECEIVE_BOOT_COMPLETED 권한을 설정한다. 이 액션은 시스템 부팅 완료 후 브로드캐스트인 ACTION_BOOT_COMPLETED 을 받을 수 있도록 해준다. (유저가 앱을 적어도 한 번 이상 실행했을 경우에만 적용된다)

<uses-permission android:name=”android.permission.RECEIVE_BOOT_COMPLETED”/>

2. 브로드캐스트를 전달 받을 수 있도록 BroadcastReceiver를 적용한다.

public class SampleBootReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(“android.intent.action.BOOT_COMPLETED”)) {
// Set the alarm here.
	}
}
}

3. 인텐트 필터로 ACTION_BOOT_COMPLETED 액션을 매니페스트에 등록한다.

<receiver android:name=”.SampleBootReceiver”
android:enabled=”false”>
<intent-filter>
<action android:name=”android.intent.action.BOOT_COMPLETED”></action>
</intent-filter>
</receiver>

매니페스트에서는 부트 리시버가 android:enabled=”false” 로 셋팅되어 있는 것을 알 수 있다. 그것은 앱이 명시적으로 사용되도록 설정하지 않는 이상 리시버가 호출되지 않음을 뜻한다. 이것은 부트 리시버가 불필요하게 호출되는 것을 막는다. 다음 방법으로 리시버를 활성화 할 수 있다 (예를 들어, 유저가 알람을 셋팅할 때)

ComponentName receiver = new ComponentName(context, SampleBootReceiver.class);
PackageManager pm = context.getPackageManager();
pm.setComponentEnabledSetting(receiver,
PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
PackageManager.DONT_KILL_APP);

만약 리시버를 이와 같은 방법으로 활성화하면, 유저가 디바이스를 재부팅 하여도 활성화 된 상태가 유지될 수 있다. 즉, 프로그래밍 방식으로 리시버를 활성화하는 것은 재부팅 등의 상황에서도 매니페스트 셋팅을 덮을 수 있다는 것이다. 앱이 비활성화 시킬 때까지 리시버는 활성화 된 상태일 것이다. 다음과 같은 방법으로 리시버를 비활성화 시킬 수 있다 (예를 들어, 유저가 알람을 취소할 때):

ComponentName receiver = new ComponentName(context, SampleBootReceiver.class);
PackageManager pm = context.getPackageManager();
pm.setComponentEnabledSetting(receiver,
PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
PackageManager.DONT_KILL_APP)

AlarmManager 의 set 과 setRepeating 메서드를 사용하고 있는 경우라면, API 19 부터는 이벤트가 발생하는 간격이 정확히 일치하지 않을 수 있다는 점을 염두해 두셔야 합니다. 킷캣에서는 배터리 효율을 위해 유사한 시간에 일어나는 알람 이벤트를 이벤트를 모아서 한번에 처리하는 식으로 동작하도록 구현되었습니다. 이전과 같이 앱이 비교적 정확한 시간 마다 발생하는 이벤트를 수신할 필요가 있는 경우 필요한 경우라면, 새롭게 추가된 setExact 메서드를 사용하시기 바랍니다.