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_WAKEUP
:ELAPSED_REALTIME
と同じですが、省電力モードのときにアラームを発生させますRTC
:Real Time Clockを使用して、アラームを発生させます。省電力モードのときは、アラームを発生させませんRTC_WAKEUP
:RTC
と同じですが、省電力モードのときにアラームを発生させます
上記のコードを実行してみると、結果は次のとおりです。
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) }
より正確な時間にアラーム発生させる
上記の例では、 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()
})
- Elapsed timeにアラームが発生する時間を設定しました。 Intervalは
INTERVAL_FIFTEEN_MINUTES
を使用しました。詳細は、以下で再び説明します。 setInexactRepeating()
でアラームを登録すれば、正確ではないが、概ね同じような時間にアラームを発生させます。
3.アラームを解除します。
1番からIntervalは、事前に定義された INTERVAL_FIFTEEN_MINUTES
定数を使用しました。
その理由は、 setInexactRepeating()
を使用すると、特定の時間にアラームを設定することができず、決められた時間だけ使用することができるからです。
使用できる時間は、次のように事前に定義されています。
INTERVAL_FIFTEEN_MINUTES
:15INTERVAL_HALF_HOUR
:30INTERVAL_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()
})
- Calendarオブジェクトを生成して、アラームが鳴る正確な時間を設定します。
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
- Android - 振動、Vibrator、VibrationEffectの例
- Android - TabLayoutの実装方法(+ ViewPager2)
- Android - PackageManagerにPackage情報を取得する
- Android - ACTION_BOOT_COMPLETEDイベント受信
- Android - FusedLocationProviderClientに位置情報を取得する
- Android - GPS、Network位置情報を取得する(LocationManager)
- Android - Foreground Service実行
- Android - 時間、日付、変更イベント受信
- Android - currentTimeMillis()、elapsedRealtime()、uptimeMillis()
- Android-PowerManager WakeLock
- Android - ファイル入出力の例(Read、Write、内部、外部ストレージ)
- Android - Screen On / Offイベントの受信、状態確認
- Android - 他のアプリのServiceにバインド
- Android - Handler vs Executor
- Android - Darkmode有効にする方法
- Android - hasSystemFeature()、サポートされているFeature確認
- Android - アプリの権限を確認(Permission check)
- Android - インストールされてアプリリストをインポートする
- Android App Shortcuts実装
- Android - ContentProviderを実装、および例
- Android - AIDLを利用して、Remote Serviceの実装
- Android - Uri、Scheme、SSP(Scheme Specific Part)説明
- Android - アプリのインストール、削除、イベントダウンロード(BroadcastReceiverインテントを受け取る)
- Android - SharedPreferencesに簡単なデータを保存する方法
- Android - AlarmManagerにアラームを登録する方法、および例
- Android - Quick SettingsにCustom Tile追加する方法(kotlin)
- Android - Broadcast Receiver登録およびイベントの受信方法
- Android - Runtime permissionリクエスト方法と例(kotlin)
- Android - ネットワーク(WIFI)の接続状態を確認し、変更の検出
- Mockito - static、final methodをmockingする方法
- Andriod - カスタムパーミッションを定義する方法
- RobolectricにUnit Testを作成する(kotlin)
- Android Mockitoのテストコードを作成する(kotlin)
- Android - Handlerの使用方法、および例
- Android - IntentService使用方法
- Android - JobIntentService使用方法