Android - AlarmManagerにアラームを登録する方法、および例

By JS | Last updated: June 20, 2020

AlarmManagerを通じて決まった時間にアラームを受け取ることができます。 Appが実行中でない時でさえ決められた時間にイベントを受けてどのようなタスクを処理することができます。

Alarmの特徴は次のとおりです。

  • 指定された時間に、一定の間隔ごとにAppがアラームイベントを受け取るように設定することができます
  • イベントは、インテントを意味し、通常はBroadcastReceiverでインテントが配信されます
  • AlarmManagerがイベントを送信するために、私のAppが実行されてなくても、アラームを受けどのような操作を処理するように実装することができます

制限

複数のアプリがAlarmManagerを介して、10分ごとにアラームを受けると考えてみてください。 ユーザーはアプリを終了してもアプリは、アラームを受けて再び生かされます。このような行動は、システムをbusyに作成し、バッテリーを急速に消費することになります。

このような理由で、プラットフォームは、短い時間間隔でアラームをできない受けるか、またはDoze modeのときにアラームを提供しないようにします。もちろん、必要に応じAlarmManagerが提供するAPIでアラームが発生するように要求することができますが、正確な時間にアラームが発生する必要がない場合、このような機能を使用しないようにお勧めします。

また、アラームを登録する際に、比較的正確な時間にアラームを発生させるようにすることができます。あえてなぜこのようにすべきか奇妙に見えるかもしれないが、このように実装すると、システムが少ないリソースを使用して、アラームを発生させることができます。したがって、可能であれば、デバイスのバッテリーのために、このようなAPIを利用することをお勧め。

Android developerでアラームが正確ではないことができ、複数のタスクが同じ時間にアラームを設定して、ネットワーク操作の遅延が発生する可能性があるためAlarmManagerを使用するよりも、他の方法を使用することを推奨しています。

推奨事項

以下の推奨事項は、アプリのために、デバイスを使用するユーザーのために考慮すべき内容です。

  • ネットワーク操作ではなく、ローカルの作業のためにAlarmManagerを使用することをお勧めします
  • 可能であれば、スリープ状態のときに、アラームを受信するように設定しないでください。システムリソースの消費が早くなって、ユーザーが不便です
  • 可能であればアラームを設定するときに、正確な時刻に設定しないでください。 setInexactRepeating()を使用すると、正確な時間にアラームを受けないが、システムが他のアプリのアラームイベントを送信するときに一緒に過ごすことができます。つまり、システムがsleep状態で破る時間を最小限に抑え、バッテリーなどのシステム資源を少なくすることができます
  • 可能であればReal time(1970年を基準とする実際の時間)に設定しないで、Elapsed time(機器が起動された後、経過した時間)に時間を設定します。 Real timeはUTC時刻を使用するため、ユーザーが設定したタイムゾーン、言語の影響を受けることができます。誤動作する可能性があるので、可能であればElapsed timeを使用してください

1回のアラーム登録

アラームを特定の時間に一度だけ受けるように設定することができます。アラームを受けた時、再びアラームを登録すれば、繰り返しアラームを受け取ることができます。

アラームイベントはブロードキャストに転送されます。まず、以下のようにBroadcastReceiverを作成します。 インテントを受け取ると、ログに出力してNotificationを離すように実装しました。

class AlarmReceiver : BroadcastReceiver() {

    companion object {
        const val TAG = "AlarmReceiver"
        const val NOTIFICATION_ID = 0
        const val PRIMARY_CHANNEL_ID = "primary_notification_channel"
    }

    lateinit var notificationManager: NotificationManager

    override fun onReceive(context: Context, intent: Intent) {
        Log.d(TAG, "Received intent : $intent")
        notificationManager = context.getSystemService(
                Context.NOTIFICATION_SERVICE) as NotificationManager

        createNotificationChannel()
        deliverNotification(context)
    }

    private fun deliverNotification(context: Context) {
        val contentIntent = Intent(context, MainActivity::class.java)
        val contentPendingIntent = PendingIntent.getActivity(
            context,
            NOTIFICATION_ID,
            contentIntent,
            PendingIntent.FLAG_UPDATE_CURRENT
        )
        val builder =
            NotificationCompat.Builder(context, PRIMARY_CHANNEL_ID)
                .setSmallIcon(R.drawable.ic_alarm)
                .setContentTitle("Alert")
                .setContentText("This is repeating alarm")
                .setContentIntent(contentPendingIntent)
                .setPriority(NotificationCompat.PRIORITY_HIGH)
                .setAutoCancel(true)
                .setDefaults(NotificationCompat.DEFAULT_ALL)

        notificationManager.notify(NOTIFICATION_ID, builder.build())
    }

    fun createNotificationChannel() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val notificationChannel = NotificationChannel(
                PRIMARY_CHANNEL_ID,
                "Stand up notification",
                NotificationManager.IMPORTANCE_HIGH
            )
            notificationChannel.enableLights(true)
            notificationChannel.lightColor = Color.RED
            notificationChannel.enableVibration(true)
            notificationChannel.description = "AlarmManager Tests"
            notificationManager.createNotificationChannel(
                    notificationChannel)
        }
    }
}

上記のコードは、レシーバの実装であり、まだAlarmManager関連するコードを実装していない。ここでNotificationを登録するコードがほとんどだが、Notificationは、この記事の主題を抜け出すため簡単に説明します。

Android Oreo以上からNotificationを浮かせたとき、最初にchannelを登録する必要があります。 createNotificationChannel()は、チャネルを登録するコードです。 チャンネルが登録されると、 deliverNotification()では通知を登録します。

Notificaitonについて詳しく知りたい方はアンドロイドの様々なNotification種類と実装方法を参照してください。

最後に、私が作ったレシーバを次のように AndroidManifest.xmlに登録する必要があります。 exported属性をfalseに設定すると、レシーバは、私のアプリから配信されるインテントのみ受け取ることができます。 trueに設定すると、他のアプリから配信されるインテントも受け取ることになります。他のアプリからのイベントを受けないため、falseに設定します。

<receiver android:name=".AlarmReceiver"
    android:exported="false">

アラーム登録

今アラームを登録するコードを実装します。

以下は、ToggleButtonを押したときにアラームを登録したり、解除するコードです。

val alarmManager = getSystemService(ALARM_SERVICE) as AlarmManager

val intent = Intent(this, AlarmReceiver::class.java)  // 1
val pendingIntent = PendingIntent.getBroadcast(     // 2
            this, AlarmReceiver.NOTIFICATION_ID, intent,
        PendingIntent.FLAG_UPDATE_CURRENT)

onetimeAlarmToggle.setOnCheckedChangeListener(OnCheckedChangeListener { _, isChecked ->
    val toastMessage = if (isChecked) {   // 3
        val triggerTime = (SystemClock.elapsedRealtime()  // 4
                + 60 * 1000)
        alarmManager.set(   // 5
                AlarmManager.ELAPSED_REALTIME_WAKEUP,
                triggerTime,
                pendingIntent
        )
        "Onetime Alarm On"
    } else {
        alarmManager.cancel(pendingIntent)    // 6
        "Onetime Alarm Off"
    }
    Log.d(TAG, toastMessage)
    Toast.makeText(this, toastMessage, Toast.LENGTH_SHORT).show()
})

//1のように重要なコードをコメントとして表記し、それぞれについて以下に説明しました。

1.アラーム条件が満たされたとき、レシーバに渡されるインテントを設定します。 2. AlarmManagerがインテントを持っているが、一定の時間が経ったの背後に伝達するため、PendingIntentにする必要があります。 PendingIntentのrequestCode引数として NOTIFICATION_IDを伝えました。複数PendingIntentを使用する場合requestCodeを別の方法でくれるが、この例では、1つのアラームのみを登録するので、 NOTIFICATION_IDを使用しました。 flagは下から再び説明します。 3. ToggleButtonが押されると isChecked=true、再び押されるisChecked=falseになります。 4. Elapsed timeを使用し、現在の時刻から60秒後にアラームが発生するように設定しました。時間はmsに設定下野します。 5. set()を利用して、引数を渡します。 ELAPSED_REALTIME_WAKEUPは、以下に再度説明します。 6.アラームを解除するときは、登録したPendingIntentを引数として渡します。

2回でPendingIntentを作成するときにflagを設定するのに、4つのflagがあり意味は次のとおりです。

  • FLAG_UPDATE_CURRENT:現在PendingIntentを維持し、代わりにインテントのextra dataは、新たに配信されたIntentに交換
  • FLAG_CANCEL_CURRENT:現在のインテントがすでに登録されている場合は削除して、再登録します
  • FLAG_NO_CREATE:既に登録されているインテントがある場合は、何もしない
  • FLAG_ONE_SHOT:一度使用されると、その次に再利用されません

5番からタイプを設定したが、次のようなタイプがあります。

  • ELAPSED_REALTIME:機器が起動された後、経過した時間を目安に、相対的な時間を使用して、アラームを発生させます。機器が省電力モード(doze)にあるときは、アラームを発生させず解除されると発生します
  • ELAPSED_REALTIME_WAKEUPELAPSED_REALTIMEと同じですが、省電力モードのときにアラームを発生させます
  • RTC:Real Time Clockを使用して、アラームを発生させます。省電力モードのときは、アラームを発生させません
  • RTC_WAKEUPRTCと同じですが、省電力モードのときにアラームを発生させます

上記のコードを実行してみると、結果は次のとおりです。

06-20 15:19:40.031  3529  3529 D MainActivity: Onetime Alarm On
06-20 15:20:40.038  3529  3529 D AlarmReceiver: Received intent : Intent { flg=0x14 cmp=com.codechacha.alarmmanager/.AlarmReceiver (has extras) }

Android AlarmManager

より正確な時間にアラーム発生させる

上記の例では、 AlarmManager.set() APIを使用しました。

set() APIはSDK API 19未満では、正確に設定された時間にアラームが発生するが、API 19以上ではあまり正確な時間にアラームが発生するように変更されました。

API 19以上で正確な時間にアラームを発生させるには setExact()を使用します。

alarmManager.setExact(
        AlarmManager.ELAPSED_REALTIME_WAKEUP,
        triggerTime,
        pendingIntent
)
  • set(int type, long triggerAtMillis, PendingIntent operation)
  • setExact(int type, long triggerAtMillis, PendingIntent operation)

デバイスがスリープモードの時にも動作するように作成

デバイスがスリープモード(Doze)のときは、 setExact()で登録されたアラームは発生しません。 次のAPIを使用すると、スリープモードでもアラームが発生します。

  • setAndAllowWhileIdle(int type、long triggerAtMillis、PendingIntent operation): set()と同じですが、省電力モードでも動作するAPIです
  • setExactAndAllowWhileIdle(int type、long triggerAtMillis、PendingIntent operation): setExact()と同じですが、省電力モードでも動作するAPIです

繰り返しアラーム登録

繰り返しアラームを受信するように実装することもできます。

以下はToggleButtonをクリックすると、繰り返しアラームを登録したり、解除する例です。

periodicAlarmToggle.setOnCheckedChangeListener(OnCheckedChangeListener { _, isChecked ->
    val toastMessage: String
    toastMessage = if (isChecked) {
        val repeatInterval: Long = AlarmManager.INTERVAL_FIFTEEN_MINUTES
        val triggerTime = (SystemClock.elapsedRealtime()    // 1
                + repeatInterval)
        alarmManager.setInexactRepeating(     // 2
                AlarmManager.ELAPSED_REALTIME_WAKEUP,
                triggerTime, repeatInterval,
                pendingIntent)
        "Periodic Alarm On"
    } else {
        alarmManager.cancel(pendingIntent)    // 3
        "Periodic Alarm Off"
    }
    Log.d(TAG, toastMessage)
    Toast.makeText(this, toastMessage, Toast.LENGTH_SHORT).show()
})
  1. Elapsed timeにアラームが発生する時間を設定しました。 Intervalは INTERVAL_FIFTEEN_MINUTESを使用しました。詳細は、以下で再び説明します。
  2. setInexactRepeating()でアラームを登録すれば、正確ではないが、概ね同じような時間にアラームを発生させます。

3.アラームを解除します。

1番からIntervalは、事前に定義された INTERVAL_FIFTEEN_MINUTES定数を使用しました。 その理由は、 setInexactRepeating()を使用すると、特定の時間にアラームを設定することができず、決められた時間だけ使用することができるからです。

使用できる時間は、次のように事前に定義されています。

  • INTERVAL_FIFTEEN_MINUTES:15
  • INTERVAL_HALF_HOUR:30
  • INTERVAL_HOUR:1시
  • INTERVAL_HALF_DAY:12시
  • INTERVAL_DAY:1

上記の実行してみると、結果は次のとおりです。

06-20 15:52:53.611  6752  6752 D MainActivity: Periodic Alarm On
06-20 16:08:09.532  6752  6752 D AlarmReceiver: Received intent : Intent { flg=0x14 cmp=com.codechacha.alarmmanager/.AlarmReceiver (has extras) }
06-20 16:23:12.312  6752  6752 D AlarmReceiver: Received intent : Intent { flg=0x14 cmp=com.codechacha.alarmmanager/.AlarmReceiver (has extras) }
....

正確な時間にアラームが発生するように設定

正確な時間にアラームが鳴るようにするには、 setInexactRepeating()を使用してはいけません。

setRepeating()は、API 19未満では、正確な時間にアラームが発生することを保証します。しかし、バッテリーなどの問題でAPI 19以上では、正確な時間にアラームが発生していることを保証しません。 また、AlarmManagerは繰り返しアラームを登録する際に、正確な時間を確保するためのAPIを提供していません。

setRepeating()setInexactRepeating()の違いは、 setInexactRepeating()はIntervalで設定できる時間が限られのに setRepeating()は希望の時間を設定することができます。

以下は、 setRepeating()でアラームを登録する例です。

periodicAlarmToggle.setOnCheckedChangeListener(OnCheckedChangeListener { _, isChecked ->
    val toastMessage: String
    toastMessage = if (isChecked) {
        val repeatInterval: Long = 60*1000
        val triggerTime = (SystemClock.elapsedRealtime()
                + repeatInterval)
        alarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,
                triggerTime, repeatInterval,
                pendingIntent)
        "Exact periodic Alarm On"
    } else {
        alarmManager.cancel(pendingIntent)
        "Exact periodic Alarm Off"
    }
    Log.d(TAG, toastMessage)
    Toast.makeText(this, toastMessage, Toast.LENGTH_SHORT).show()
})

上記の言ったようにAPI 19以上では精度が保証されません。

もし正確な時間にアラームが発生するようにするには、 setExact()を使用して、希望の時間に1回だけアラームが鳴るように作って、イベントを受け取ったときに再次時間を設定して、1回限りのアラームを登録するように実装する必要があります。

Real timeにアラーム登録

以下は、RTCを使用して繰り返しアラームを登録する例です。

realtimePeriodicAlarmToggle.setOnCheckedChangeListener(OnCheckedChangeListener { _, isChecked ->
    val toastMessage: String
    toastMessage = if (isChecked) {
        val repeatInterval: Long = 15 * 60 * 1000   // 15 min
        val calendar: Calendar = Calendar.getInstance().apply { // 1
            timeInMillis = System.currentTimeMillis()
            set(Calendar.HOUR_OF_DAY, 20)
            set(Calendar.MINUTE, 25)
        }

        alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, // 2
                calendar.timeInMillis,
                repeatInterval,
                pendingIntent)
        "Realtime periodic Alarm On"
    } else {
        alarmManager.cancel(pendingIntent)  // 3
        "Realtime periodic Alarm Off"
    }
    Log.d(TAG, toastMessage)
    Toast.makeText(this, toastMessage, Toast.LENGTH_SHORT).show()
})
  1. Calendarオブジェクトを生成して、アラームが鳴る正確な時間を設定します。
  2. setRepeating()の引数として RTC_WAKEUPとcalendarの時間を提供します。

3.アラームを解除します。

上記のコードを実行すると、結果は次のとおりです。

06-20 20:02:02.458  3435  3435 D MainActivity: Realtime periodic Alarm On
06-20 20:25:02.467  3435  3435 D AlarmReceiver: Received intent : Intent { flg=0x14 cmp=com.codechacha.alarmmanager/.AlarmReceiver (has extras) }
06-20 20:40:13.302  3435  3435 D AlarmReceiver: Received intent : Intent { flg=0x14 cmp=com.codechacha.alarmmanager/.AlarmReceiver (has extras) }
....

アラームキャンセル

上記の例において、既にアラーム解除コードを紹介しました。次のように cancel()を呼び出すときに登録したPendingIntentを引数として渡します。

alarmManager.cancel(pendingIntent)

デバイスが実行されると、アラーム登録

デバイスが起動すると、アラームを登録するには、 ACTION_BOOT_COMPLETEDイベントを受けて、アラームを登録するように実装する必要があります。

まず、 ACTION_BOOT_COMPLETEDを受けるレシーバを実装する必要があります。 onReceive()でインテントを受信したときにアラームを登録します。

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
        }
    }
}

そしてAppのManifestに次のように受信機を登録する必要があります。 アプリが ACTION_BOOT_COMPLETEDイベントを受け取るandroid.permission.RECEIVE_BOOT_COMPLETEDという権限が必要です。 この権限ももれなくぜひManifestに記述します。

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

<application ... />
    ....
    <receiver android:name=".BootReceiver">
        <intent-filter>
            <action android:name="android.intent.action.BOOT_COMPLETED" />
        </intent-filter>
    </receiver>
</application>

アラームが発生したときIntentServiceにインテント伝達

上記の例は、BroadcastReceiverにインテントを提供したが、IntentServiceも渡すことができます。

以下のようにIntentServiceを実装します。

class AlarmIntentService : IntentService(AlarmIntentService::class.java.name) {
    override fun onHandleIntent(intent: Intent?) {
        val context: Context = applicationContext
        Log.d(TAG, "Received intent: $intent")
    }

    companion object {
        const val TAG = "AlarmIntentService"
    }
}

AndroidManifest.xmlにサービスを登録します。

<service android:name=".AlarmIntentService"
    android:exported="false">
</service>

最後に、次のようにPendingIntentを作成AlarmManagerにアラームを登録します。

intentServiceAlarmToggle.setOnCheckedChangeListener(OnCheckedChangeListener { _, isChecked ->
    val pIntentForIntentService = PendingIntent.getService(
            this,
            0,
            Intent(this, AlarmIntentService::class.java),
            PendingIntent.FLAG_UPDATE_CURRENT)
    val triggerTime = System.currentTimeMillis() + 10 * 60 * 1000
    alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, triggerTime, pendingIntent)
})

別の方法でTaskをスケジューリング

AlarmManagerを使用せずにWorkManager、JobSchedulerを利用して、Taskをスケジュールすることができます。

詳細については、次の二つを書き込みと良いです。

Sample

この記事で使用されてSampleは、GitHub - AlarmManagerで確認することができます。

参考

Related Posts

codechachaCopyright ©2019 codechacha