AlarmManagerの複数登録


一定間隔や周期的なアラームではなく、指定した時間または決まった秒・分後などにアラームを鳴らしたい場合など、1個に限らず2個以上のアラームを登録したいということがあると思います。
例）9:00、10:00、11:00に鳴るように3つのアラームを設定したい等

普通にAlarmManagerを実装するだけでは複数個の登録はできないため、複数個登録する方法を記します。

AlarmManagerは自分もかなり躓いた実装のため、実装の仕方と一緒に複数登録方法までメモ程度に書きますが、どなたかの参考にもなれればと思います。


この記事では、時間を指定した場合の実装になります。


# AlarmManagerをセット

```kotlin

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行ずつ見ていきます。

```kotlin
val alarmManager = context.getSystemService(ALARM_SERVICE) as AlarmManager
```
`AlarmManager`のインスタンス化です。
どうやら、普通にインスタンス化することはできないみたいです。（詳しくはちょっと忘れた）
引数で渡された`Context`を使って`context.getSystemService(ALARM_SERVICE)`と記載し、`AlarmManger`をキャストしています。

```kotlin
val intent = Intent(context, AlarmManagerReceiver::class.java)
```
`intent`は画面遷移時にもよく使われますね。
このコードでアラームを鳴らすときどこで応答させるかを指定しています。
要は、アラームをどこで受け取るか指定しているというところでしょうか。

```kotlin
intent.putExtra("id", id)
```
複数登録したことを明示的にするのには、こちらのコードが重要となります。
引数にもある、この`id`でアラームを管理しているイメージです。言わば管理番号です。
この`id`を応答するクラスで受け取ります。
管理方法は自由ですが、例えば`List`や`RecyclerView`などでインデックス番号を取得してidとして渡す感じでもいいと思います。
作成アプリによりますが、試しであればどれが実行されるかなど区別されたアラームを可視化したいとき`intent.putExtra()`を使って値を渡し、`Toast`で実行を確かめたりすると分かりやすいですね。

```kotlin
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`のどちらかを明示的に指定する必要があるようです。
対応させる方法としては上記二つどちらかに書き換えてしまうか、

```kotlin
val pendingIntent = PendingIntent.getBroadcast(context, id, intent, FLAG_UPDATE_CURRENT or FLAG_IMMUTABLE)
```
とKotlinならば`or`で書き分けることができるみたいです。
詳しくは公式などをご確認ください。


```kotlin
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の応答を受け取るクラスです。


```kotlin
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?)`をオーバーライドしています。


```kotlin
// Alarmの区別と送信するデータの番号
val id = intent?.getIntExtra("id", 0)
```

先ほどのアラームをセットしたクラスで`intent`を使って値を受け渡す用に実装しましたね。
こちらの`onReceive()`内で`intent`の値を受け取ります。

```kotlin
Toast.makeText(context, "アラーム番号　：「{$id}」が実行されました", Toast.LENGTH_LONG).show()
```
AlarmManagerが発動したら処理する内容はトーストを表示するようにしています。
このトーストの文字列内に`{$id}`とありますが、こちらでどのアラームが発動したか確認ができるようにセットしています。
試すぐらいであればトーストを表示するぐらいの実装で問題ないかと思います。

# BroadcastReceiverを継承したクラスでonReceiveを実装する際

`BroadcastReceiver`は、日付指定やシステムの起動等のイベントを受信するものです。
受信する方法はいくつかあるかと思いますが、自分の場合は以下のコードをAndroidManifest.xmlに追加しています。

```kotlin
// ①
<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