Google은 흑인 공동체를 위한 인종 간 평등을 진전시키기 위해 노력하고 있습니다. Google에서 어떤 노력을 하고 있는지 확인하세요.

반복 알람 예약

알람(AlarmManager 클래스 기반)을 사용하면 애플리케이션이 사용되지 않을 때 시간 기반 작업을 실행할 수 있습니다. 예를 들어, 알람을 사용하여 일기예보를 다운로드하는 것과 같이 하루에 한 번 서비스를 시작하는 장기 실행 작업을 시작할 수 있습니다.

알람에는 다음 특징이 있습니다.

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

참고: 애플리케이션이 실행 중인 동안 발생하는 시간 기반 작업은 TimerThread와 함께 Handler 클래스 사용을 고려해보세요. 이 접근 방식을 사용하면 Android가 시스템 리소스를 더 효과적으로 제어할 수 있습니다.

장단점 이해하기

반복 알람은 제한된 유연성을 가진 비교적 단순한 메커니즘입니다. 특히 네트워크 작업을 트리거하는 앱에는 최선이 아닐 수도 있습니다. 알람을 잘못 설계하면 배터리 소모가 발생하고 서버에 큰 부담을 줄 수 있습니다.

앱이 실행 중이 아닌 상태에서 작업을 트리거하는 일반적인 시나리오는 데이터를 서버와 동기화하는 것입니다. 이것이 반복 알람을 사용하려는 한 가지 예입니다. 하지만, 앱 데이터를 호스팅하는 서버를 보유하고 있다면 동기화 어댑터와 함께 Google 클라우드 메시징(GCM)을 사용하는 것이 AlarmManager보다 더 좋은 방법입니다. 동기화 어댑터는 AlarmManager와 동일한 예약 옵션을 제공하지만 훨씬 더 유연합니다. 예를 들어, 동기화는 서버/기기에서 오는 '새 데이터' 메시지(자세한 내용은 동기화 어댑터 실행 참조), 사용자의 활동 또는 활동 없음, 시간 등을 기반으로 합니다. GCM 및 동기화 어댑터를 사용하는 시기 및 방법에 관한 자세한 내용은 이 페이지 상단의 링크된 동영상을 참조하세요.

기기가 잠자기 모드에서 유휴 상태일 때는 알람이 울리지 않습니다. 예약된 알람은 기기가 잠자기 모드를 종료할 때까지 지연됩니다. 기기가 유휴 상태일 때도 작업을 완료해야 하는 경우 사용할 수 있는 몇 가지 옵션이 있습니다. setAndAllowWhileIdle()setExactAndAllowWhileIdle()을 사용하여 알람을 실행할 수 있습니다. 또 다른 옵션은 백그라운드 작업을 한 번 또는 주기적으로 진행하도록 구성된 새 WorkManager API를 사용하는 것입니다. 자세한 내용은 WorkManager로 작업 예약을 참조하세요.

권장사항

반복 알람 디자인과 관련된 모든 선택은 앱이 시스템 리소스를 사용(또는 오용)하는 방식에 영향을 줄 수 있습니다. 예를 들어 서버와 동기화되는 인기 앱을 생각해보세요. 동기화 작업이 시계 시간을 기반으로 하고 앱의 모든 인스턴스가 오후 11시에 동기화된다면 서버의 부하로 지연 시간이 늘어나거나 '서비스 거부'가 발생할 수 있습니다. 알람 사용에 대한 권장사항은 다음과 같습니다.

  • 반복 알람의 결과로 트리거되는 모든 네트워크 요청에 임의성(지터)을 추가합니다.
    • 알람이 트리거될 때 로컬 작업을 수행합니다. '로컬 작업'은 서버에 도달하지 않거나 서버의 데이터를 요구하지 않는 모든 작업입니다.
    • 동시에 임의의 기간에 실행되는 네트워크 요청이 포함된 알람을 예약합니다.
  • 알람 빈도를 최소한으로 유지합니다.
  • 불필요하게 기기의 절전 모드를 해제하지 마세요(이 동작은 알람 유형 선택에 설명한 알람 유형에 따라 결정됨).
  • 알람 트리거 시간을 필요한 수준보다 정밀하게 설정하지 마세요.

    setRepeating() 대신 setInexactRepeating()을 사용하세요. setInexactRepeating()을 사용하면 Android는 여러 앱의 반복 알람을 동기화하고 동시에 실행합니다. 이렇게 하면 시스템이 기기의 절전 모드를 해제해야 하는 총횟수가 줄어들어 배터리 소모가 감소합니다. Android 4.4(API 레벨 19)부터 모든 반복 알람의 시간이 부정확합니다. setInexactRepeating()setRepeating()을 개선한 것이지만 비슷한 시기에 앱의 모든 인스턴스가 서버에 도달하면 여전히 서버에 부담이 될 수 있습니다. 따라서 네트워크 요청의 경우 위에서 설명한 것처럼 알람에 임의성을 추가합니다.

  • 가능하면 시계 시간을 기준으로 알람을 설정하지 않도록 합니다.

    정확한 트리거 시간을 기준으로 하는 반복 알람은 확장되지 않습니다. 가능하면 ELAPSED_REALTIME을 사용하세요. 다른 알람 유형은 다음 섹션에서 자세히 설명합니다.

반복 알람 설정

위에서 설명한 대로 반복 알람은 정기적인 이벤트 또는 데이터 조회를 예약하는 데 적합합니다. 반복 알람은 다음과 같은 특성을 갖습니다.

  • 알람 유형. 자세한 내용은 알람 유형 선택을 참조하세요.
  • 트리거 시간. 지정한 트리거 시간이 과거이면 이 알람은 즉시 트리거됩니다.
  • 알람의 간격. 예를 들어, 하루에 한 번, 매 시간, 5분마다 등이 있습니다.
  • 알람이 트리거되면 실행되는 대기 중인 인텐트. 동일한 대기 중인 인텐트를 사용하는 두 번째 알람을 설정하면 이 알람은 원래 알람을 대체합니다.

PendingIntent를 취소하려면, FLAG_NO_CREATEPendingIntent.getService()에 전달하여 인텐트의 인스턴스(있는 경우)를 가져온 다음 그 인텐트를 AlarmManager.cancel()에 전달합니다.

Kotlin

    val alarmManager =
        context.getSystemService(Context.ALARM_SERVICE) as? AlarmManager
    val pendingIntent =
        PendingIntent.getService(context, requestId, intent,
                                    PendingIntent.FLAG_NO_CREATE)
    if (pendingIntent != null && alarmManager != null) {
      alarmManager.cancel(pendingIntent)
    }
    

자바

    AlarmManager alarmManager =
        (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
    PendingIntent pendingIntent =
        PendingIntent.getService(context, requestId, intent,
                                    PendingIntent.FLAG_NO_CREATE);
    if (pendingIntent != null && alarmManager != null) {
      alarmManager.cancel(pendingIntent);
    }
    

알람 유형 선택

반복 알람 사용 시 가장 먼저 고려할 사항 중 하나는 알람 유형입니다.

알람에는 '실제 경과 시간'과 '실시간 시계'(RTC)의 두 가지 일반적인 시계 유형이 있습니다. 실제 경과 시간은 '시스템 부팅 이후 시간'을 참조로 사용하며 실시간 시계는 UTC(실제 시간) 시간을 사용합니다. 실제 경과 시간은 시간대/언어의 영향을 받지 않기 때문에 경과 시간에 기반한 알람(예: 30초마다 실행되는 알람)을 설정하는 데 적합합니다. 실시간 시계 유형은 현재 언어에 따라 달라지는 알람에 더 적합합니다.

두 가지 유형 모두 화면이 꺼져 있을 때 기기에서 CPU의 절전 모드를 해제하는 'wakeup' 버전이 있습니다. 이렇게 하면 예약된 시간에 알람이 실행됩니다. 이는 특정 작업을 수행하는 시간이 제한된 경우와 같이 앱에 시간 종속성이 있는 경우에 유용합니다. Wakeup 버전의 알람 유형을 사용하지 않으면 기기가 다음에 켜질 때 모든 반복 알람이 실행됩니다.

특정 간격으로(예: 30분마다) 알람을 실행해야 한다면 실제 경과 시간 유형 중 하나를 사용합니다. 일반적으로 유형이 더 좋습니다.

알람이 하루 중 특정 시간에 실행된다면 시간 기반의 실시간 시계 유형 중 하나를 선택하세요. 그러나 참고로 이 접근 방식은 사용자가 기기의 시간 설정을 변경한 경우 앱이 다른 언어로 잘 번역하지 못할 수도 있고 알람이 앱에서 예상치 못한 동작을 일으킬 수도 있는 단점이 있습니다. 또한 실시간 시계 알람 유형을 사용하면 위에서 설명한 것처럼 제대로 확장되지 않습니다. 가능하다면 '실제 경과 시간' 사용을 권장합니다.

다음은 유형 목록입니다.

  • ELAPSED_REALTIME - 기기가 부팅된 후 경과한 시간을 기반으로 대기 중인 인텐트를 실행하지만 기기의 절전 모드는 해제하지 않습니다. 경과 시간에는 기기가 대기 상태였던 시간이 포함됩니다.
  • ELAPSED_REALTIME_WAKEUP - 기기를 부팅한 후 지정된 시간이 경과하면 기기의 절전 모드를 해제하고 대기 중인 인텐트를 실행합니다.
  • RTC - 지정된 시간에 대기 중인 인텐트를 실행하지만 기기의 절전 모드는 해제하지 않습니다.
  • RTC_WAKEUP - 지정된 시간에 기기의 절전 모드를 해제하여 대기 중인 인텐트를 실행합니다.

실제 경과 시간 알람의 예

다음은 ELAPSED_REALTIME_WAKEUP 사용의 몇 가지 예입니다.

다음과 같이 30분 후와 그 이후 30분마다 기기의 절전 모드를 해제하여 알람을 실행합니다.

Kotlin

    // Hopefully your alarm will have a lower frequency than this!
    alarmMgr?.setInexactRepeating(
            AlarmManager.ELAPSED_REALTIME_WAKEUP,
            SystemClock.elapsedRealtime() + AlarmManager.INTERVAL_HALF_HOUR,
            AlarmManager.INTERVAL_HALF_HOUR,
            alarmIntent
    )
    

자바

    // Hopefully your alarm will have a lower frequency than this!
    alarmMgr.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,
            SystemClock.elapsedRealtime() + AlarmManager.INTERVAL_HALF_HOUR,
            AlarmManager.INTERVAL_HALF_HOUR, alarmIntent);
    

다음과 같이 1분 후에 기기의 절전 모드를 해제하여 일회성 알람(반복 없음)을 실행합니다.

Kotlin

    private var alarmMgr: AlarmManager? = null
    private lateinit var alarmIntent: PendingIntent
    ...
    alarmMgr = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
    alarmIntent = Intent(context, AlarmReceiver::class.java).let { intent ->
        PendingIntent.getBroadcast(context, 0, intent, 0)
    }

    alarmMgr?.set(
            AlarmManager.ELAPSED_REALTIME_WAKEUP,
            SystemClock.elapsedRealtime() + 60 * 1000,
            alarmIntent
    )
    

자바

    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_WAKEUP 사용의 몇 가지 예입니다.

대략 오후 2시에 기기의 절전 모드를 해제하여 알람을 실행하고 하루에 한 번씩 동일한 시간에 반복합니다.

Kotlin

    // Set the alarm to start at approximately 2:00 p.m.
    val calendar: Calendar = Calendar.getInstance().apply {
        timeInMillis = System.currentTimeMillis()
        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.timeInMillis,
            AlarmManager.INTERVAL_DAY,
            alarmIntent
    )
    

자바

    // 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);
    

정확히 오전 8시 30분에 기기의 절전 모드를 해제하여 알람을 실행하고 그 후 20분마다 반복합니다.

Kotlin

    private var alarmMgr: AlarmManager? = null
    private lateinit var alarmIntent: PendingIntent
    ...
    alarmMgr = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
    alarmIntent = Intent(context, AlarmReceiver::class.java).let { intent ->
        PendingIntent.getBroadcast(context, 0, intent, 0)
    }

    // Set the alarm to start at 8:30 a.m.
    val calendar: Calendar = Calendar.getInstance().apply {
        timeInMillis = System.currentTimeMillis()
        set(Calendar.HOUR_OF_DAY, 8)
        set(Calendar.MINUTE, 30)
    }

    // setRepeating() lets you specify a precise custom interval--in this case,
    // 20 minutes.
    alarmMgr?.setRepeating(
            AlarmManager.RTC_WAKEUP,
            calendar.timeInMillis,
            1000 * 60 * 20,
            alarmIntent
    )
    

자바

    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);
    

알람이 얼마나 정확해야 하는지 결정

위에서 설명한 것처럼 알람을 만드는 첫 번째 단계는 알람 유형을 선택하는 것입니다. 차이는 알람이 얼마나 정확해야 하는가입니다. 대부분의 앱은 setInexactRepeating()을 쓰는 것이 더 좋습니다. 이 메서드를 사용하면 Android는 여러 개의 부정확한 반복 알람을 동기화하고 동시에 실행합니다. 이렇게 하면 배터리 소모를 줄일 수 있습니다.

드물게 정확한 시간을 요구하는 앱(예: 알람이 정확히 오전 8시 30분에 실행되고 그 후 매시 정각마다 실행됨)은 setRepeating()을 사용합니다. 하지만 가능하면 정확한 알람을 사용하지 않도록 합니다.

setInexactRepeating()을 사용하는 경우 setRepeating()과 같은 방법으로는 맞춤 간격을 지정할 수 없습니다. INTERVAL_FIFTEEN_MINUTES, INTERVAL_DAY 및 기타 간격 상수 중 하나를 사용해야 합니다. 전체 목록은 AlarmManager를 참조하세요.

알람 취소

앱에 따라 알람을 취소하는 기능을 포함할 수도 있습니다. 알람을 취소하려면 알람 관리자의 cancel()을 호출하여 취소할 PendingIntent를 전달합니다. 예:

Kotlin

    // If the alarm has been set, cancel it.
    alarmMgr?.cancel(alarmIntent)
    

자바

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

기기가 다시 시작되면 알람 시작

기본적으로 기기를 종료하면 모든 알람이 취소됩니다. 이런 상황을 방지하려면 사용자가 기기를 재부팅할 때 자동으로 반복 알람을 다시 시작하도록 애플리케이션을 디자인해야 합니다. 이렇게 하면 사용자가 알람을 수동으로 다시 시작하지 않아도 AlarmManager가 작업을 계속할 수 있습니다.

단계는 다음과 같습니다.

  1. 다음과 같이 애플리케이션의 매니페스트에 RECEIVE_BOOT_COMPLETED 권한을 설정합니다. 이 권한을 사용하면 시스템 부팅이 끝난 후에 앱은 브로드캐스팅되는 ACTION_BOOT_COMPLETED를 받습니다(사용자가 최소 한 번 이상 앱을 시작한 경우에만 동작함).
        <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
  2. 다음과 같이 BroadcastReceiver를 구현하여 브로드캐스트를 받습니다.

    Kotlin

        class SampleBootReceiver : BroadcastReceiver() {
    
            override fun onReceive(context: Context, intent: Intent) {
                if (intent.action == "android.intent.action.BOOT_COMPLETED") {
                    // Set the alarm here.
                }
            }
        }
        

    자바

        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>

    manifest에 부팅 수신기는 android:enabled="false"로 설정되어 있습니다. 즉, 애플리케이션이 명시적으로 사용하지 않으면 수신기는 호출되지 않습니다. 이렇게 하면 부팅 수신기가 불필요하게 호출되는 것을 방지할 수 있습니다. 다음과 같이 수신기를 사용할 수 있습니다(예: 사용자가 알람을 설정한 경우).

    Kotlin

        val receiver = ComponentName(context, SampleBootReceiver::class.java)
    
        context.packageManager.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_ENABLED,
                PackageManager.DONT_KILL_APP);
        

    이런 방식으로 한 번 수신기를 사용하면 사용자가 기기를 재부팅해도 수신기는 사용할 수 있는 상태로 남아 있습니다. 즉, 수신기를 프로그래매틱 방식으로 사용하면 재부팅해도 manifest 설정이 재정의됩니다. 수신기는 앱이 중지할 때까지 사용할 수 있는 상태로 남아있습니다. 다음과 같이 수신기를 중지할 수 있습니다(예: 사용자가 알람을 취소한 경우).

    Kotlin

        val receiver = ComponentName(context, SampleBootReceiver::class.java)
    
        context.packageManager.setComponentEnabledSetting(
                receiver,
                PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
                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);
        

잠자기 및 앱 대기가 미치는 영향

Android 6.0(API 수준 23)에는 기기 배터리 수명을 연장하기 위해 잠자기 및 앱 대기가 도입되었습니다. 기기가 잠자기 모드일 때 모든 표준 알람은 기기가 잠자기 모드를 종료하거나 유지보수 기간이 시작될 때까지 지연됩니다. 잠자기 모드에서 알람을 실행해야 한다면 setAndAllowWhileIdle() 또는 setExactAndAllowWhileIdle()을 사용하면 됩니다. 앱이 유휴 상태이면 앱 대기 모드로 전환되며 이는 사용자가 일정 시간 동안 앱을 사용하지 않았고 앱에 포그라운드 프로세스가 없다는 의미입니다. 앱이 앱 대기 중일 때 알람은 잠자기 모드에서와 같이 연기됩니다. 이 제한은 앱이 더 이상 유휴 상태가 아니거나 기기가 전원에 연결되어 있을 때 해제됩니다. 이러한 모드가 앱에 미치는 영향에 관한 자세한 내용은 잠자기 및 앱 대기의 최적화를 참조하세요.