一定間隔や周期的なアラームではなく、指定した時間または決まった秒・分後などにアラームを鳴らしたい場合など、1個に限らず2個以上のアラームを登録したいということがあると思います。
普通にAlarmManagerを実装するだけでは複数個登録はできないため、複数個登録する方法を記します。
AlarmManagerは自分もかなり躓いた実装のため、実装の仕方と一緒に複数登録方法までメモ程度に書きますが、どなたかの参考にもなれればと思います。
(間違っていたらご指摘ください。。。)
この記事では、時間を指定した場合の実装になります。
AlarmManagerをセット
class SettingAlarmManager : Application(){
fun settingAlarmManager(
context: Context,
timeDiff: Long,
id: Int,
) {
val alarmManager = context.getSystemService(ALARM_SERVICE) as AlarmManager
val intent = Intent(context, AlarmManagerReceiver::class.java)
intent.putExtra("id", id)
val pendingIntent = PendingIntent.getBroadcast(context, id, intent, FLAG_UPDATE_CURRENT)
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