一定間隔や周期的なアラームではなく、指定した時間または決まった秒・分後などにアラームを鳴らしたい場合など、1個に限らず2個以上のアラームを登録したいということがあると思います。
例)9:00、10:00、11:00に鳴るように3つのアラームを設定したい等
普通にAlarmManagerを実装するだけでは複数個の登録はできないため、複数個登録する方法を記します。
AlarmManagerは自分もかなり躓いた実装のため、実装の仕方と一緒に複数登録方法までメモ程度に書きますが、どなたかの参考にもなれればと思います。
この記事では、時間を指定した場合の実装になります。
AlarmManagerをセット
class SettingAlarmManager : Application(){
fun settingAlarmManager(
context: Context,
timeDiff: Long,
id: Int,
) {
// AlarmManagerのインスタンス化
val alarmManager = context.getSystemService(ALARM_SERVICE) as AlarmManager
// AlarmManager受け取りクラス(受取りクラスにidを渡す)
val intent = Intent(context, AlarmManagerReceiver::class.java)
intent.putExtra("id", id)
// intentの予約
val pendingIntent = PendingIntent.getBroadcast(context, id, intent, FLAG_UPDATE_CURRENT)
// AlarmManagerセット
alarmManager.setExact(AlarmManager.RTC_WAKEUP, timeDiff, pendingIntent)
}
}
上記のコードを1行ずつ見ていきます。
val alarmManager = context.getSystemService(ALARM_SERVICE) as AlarmManager
AlarmManagerのインスタンス化です。
どうやら、普通にインスタンス化することはできないみたいです。(詳しくはちょっと忘れた)
引数で渡されたContextを使ってcontext.getSystemService(ALARM_SERVICE)と記載し、AlarmMangerをキャストしています。
val intent = Intent(context, AlarmManagerReceiver::class.java)
intentは画面遷移時にもよく使われますね。
このコードでアラームを鳴らすときどこで応答させるかを指定しています。
要は、アラームをどこで受け取るか指定しているというところでしょうか。
intent.putExtra("id", id)
複数登録したことを明示的にするのには、こちらのコードが重要となります。
引数にもある、このidでアラームを管理しているイメージです。言わば管理番号です。
このidを応答するクラスで受け取ります。
管理方法は自由ですが、例えばListやRecyclerViewなどでインデックス番号を取得してidとして渡す感じでもいいと思います。
作成アプリによりますが、試しであればどれが実行されるかなど区別されたアラームを可視化したいときintent.putExtra()を使って値を渡し、Toastで実行を確かめたりすると分かりやすいですね。
val pendingIntent = PendingIntent.getBroadcast(context, id, intent, FLAG_UPDATE_CURRENT)
これもまたAlarmManagerを使う上で重要になり、複数登録する上でも必要になります。
PendingIntentはintentを予約して指定したタイミングで発行させます。
つまり、ボタンを押されたタイミングなどのイベントを起こす時に利用されます。
Broadcastの意味を調べればわかるかと思いますが、Broadcastは「同じメッセージを複数の受信者に同時に転送する」こと。
つまり、このPendingIntent.getBroadcast()がアラームに複数登録させることに直接的な関わりがあるとわかります。
第二引数はrequestCodeです。
ここが自分の躓きポイントでした。
どの参考サイトを見てもここは0になっていることが多く、そのままコードを書いていました。
このrequestCodeはアラームが一つの場合は0でOKですが、複数の場合だと受け取る側のクラスでrequestCodeを見て切り分けるため別の値を入れなければいけません。
なので、自分の場合は管理番号のidを第二引数にセットしたという感じです。
第四引数はflagです。
FLAGもAlarmManagerの使い方により、セットするFLAGが異なるかと思います。
この第四引数のflagは、同一のPendingIntentがあった場合の挙動を指定するものになります。
FLAG_UPDATE_CURRENTは、公式developerでは
Flag indicating that if the described PendingIntent already exists, then keep it but replace its extra data with what is in this new Intent.
と記載されています。
既に存在しているPendingIntentの情報を保持しつつ、新しいPendingIntentを更新します。
今までflagも0にしていましたが、0だと更新されず常に新しいものに置き換えられていたということですね。
ちなみにですが、Android 12の場合、PendingIntentの引数を変更しなければなりません。
第四引数のflagをFLAG_IMMUTABLEかFLAG_MUTABLEのどちらかを明示的に指定する必要があるようです。
対応させる方法としては上記二つどちらかに書き換えてしまうか、
val pendingIntent = PendingIntent.getBroadcast(context, id, intent, FLAG_UPDATE_CURRENT or FLAG_IMMUTABLE)
とKotlinならばorで書き分けることができるみたいです。
詳しくは公式などをご確認ください。
alarmManager.setExact(AlarmManager.RTC_WAKEUP, timeDiff, pendingIntent)
こちらは、AlarmManagerをセットしています。
セットの仕方ですが、set()でも実行できるみたいですが、より正確な時間にAlarmManagerを実行させるならsetExact()がいいです。Dozeでも発火するのはsetExactAndAllowWhileIdle()です。
第一引数はアラームのタイプです。
RTC_WAKEUPは起動中は勿論、実機がスリープ中はWAKE_UPしてくれます。
他いろんなタイプあるので自分のアプリに合わせてセットしてみてください。(詳しくは参照から)
第二引数はアラームを起動する時間です。
timeDiffは指定した時間です。設定しときの時間(端末時刻)と指定した時間の差分をミリ秒で取得したものになります。(UnixTime)
また、ボタンを押してから30秒後に実行したい場合などもカレンダークラスを使いミリ秒で取得して設定すれば実装ができます。
第三引数は上記でセットしたPendingIntentです。
これでAlarmManagerのセットは出来ました。
Broadcastを使わないやり方もあるみたいなので、自分に合うやり方でいいかと思います。
AlarmManagerの応答
次はAlarmManagerの応答を受け取るクラスです。
class AlarmManagerReceiver: BroadcastReceiver() {
@RequiresApi(Build.VERSION_CODES.O)
override fun onReceive(context: Context?, intent: Intent?) {
// Alarmの区別と送信するデータの番号
val id = intent?.getIntExtra("id", 0)
Toast.makeText(context, "アラーム番号 :「{$id}」が実行されました", Toast.LENGTH_LONG).show()
}
BroadcastReceiver()を継承したクラスでonReceive(context: Context?, intent: Intent?)をオーバーライドしています。
// Alarmの区別と送信するデータの番号
val id = intent?.getIntExtra("id", 0)
先ほどのアラームをセットしたクラスでintentを使って値を受け渡す用に実装しましたね。
こちらのonReceive()内でintentの値を受け取ります。
Toast.makeText(context, "アラーム番号 :「{$id}」が実行されました", Toast.LENGTH_LONG).show()
AlarmManagerが発動したら処理する内容はトーストを表示するようにしています。
このトーストの文字列内に{$id}とありますが、こちらでどのアラームが発動したか確認ができるようにセットしています。
試すぐらいであればトーストを表示するぐらいの実装で問題ないかと思います。
BroadcastReceiverを継承したクラスでonReceiveを実装する際
BroadcastReceiverは、日付指定やシステムの起動等のイベントを受信するものです。
受信する方法はいくつかあるかと思いますが、自分の場合は以下のコードをAndroidManifest.xmlに追加しています。
// ①
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"/>
<application
...
<activity
...
</activity>
// ②
<receiver android:name=".AlarmManagerReceiver"
android:process=":remote" />
</application>
①は、作業をスケジューリングする際に必要となるコードです。
正確なアラームを使用できるようにするため、パーミッションを追加します。
②は、BroadcastReceiverの実行プロセスの名前です。
追加するとメインスレッドとは別のスレッドでレシーバを稼働させます。
②を入れないと、onReceive()内の処理は実行されませんでした。
まとめ
AlarmManagerの単体登録と複数登録に関して簡単にまとめます。
アラームを1つのみ登録実行
・PendingIntent.getBroadcastの第二引数と第四引数は簡単に「0」と記載してしまってOK
・仮に第二引数と第四引数を「0」で設定してアラームを複数発動させようとした場合、新しいアラームが上書きされるため最後に登録したアラームしか発動しない
・区別するためのintentの受け渡しはなくても分かる
アラームを2つ以上登録
・考え方は「アラームを複数個登録する」ではなく、「アラームを複数個生成する」の方が近くわかりやすいかも
・PendingIntent.getBroadcastの第二引数と第四引数は、アラームを区別するために必要となるため、値を変更・更新するようにする
・どのアラームが発動したか区別するならintentを応答クラスで受け取るようにする
メインとしては複数登録して発動させる方法を記載しましたが、単体・複数登録どちらでも誰かの参考に少しでもなればいいなと思います。
参考
Android公式
・https://developer.android.com/training/scheduling/alarms?hl=ja
・https://developer.android.com/reference/android/app/PendingIntent
・https://developer.android.com/guide/topics/manifest/receiver-element#proc
そのほか
・https://qiita.com/kumas/items/f21a55117d2a6cafa7c1
・https://akira-watson.com/android/alarmmanager-timer.html
・https://codechacha.com/ja/android-alarmmanager/
・http://bodony-android.blogspot.com/2013/11/broadcastreceiver.html
・https://techbooster.org/android/application/2166/
・https://android.keicode.com/basics/services-communicate-broadcast-receiver.php
・https://hungry-bros.com/archives/1044
・https://qiita.com/kurodai0715/items/f9bc46e349a15b65e36b