🔔

FlutterでAndroid 14以降のローカル通知をサポートするには

2023/07/21に公開

この記事は2023年7月21日に執筆しています。
参照しているflutter_local_notificationsのバージョンは15.1.0+1、もしくは16.0.0-dev.1です。

※執筆にあたり慎重にドキュメントの調査などを行いましたが、不正確な情報を記述している恐れがあります
※Android OSやライブラリの更新により、異なる対応が必要になる恐れがあります


Android端末に対応したFlutterアプリケーションにおいて、サーバーからのPush通知ではないPush、つまりローカルのPush通知を表示するライブラリにflutter_local_notificationsがあります。

https://pub.dev/packages/flutter_local_notifications

今回はflutter_local_notifications v14からサポートされた、Android 13や14向けの「正確なアラームのスケジュール」について理解を深めます。
これからAndroid 14以降をターゲットにするケースで、参考になればと。

Andorid 13までの機能アップデート

Android 13よりUSE_EXACT_ALARM(正確なアラームを使用するための新しい権限)が追加されました。
これはAndroid 12で追加されたSCHEDULE_EXACT_ALARM権限(正確なアラームの権限)から、対応が一歩進んだものになります。

https://developer.android.com/about/versions/12/behavior-changes-12?hl=ja#exact-alarm-permission
https://developer.android.com/about/versions/13/features?hl=ja#use-exact-alarm-permission

正確なアラームの権限

Android 12のガイドを読むと、次のような解説があります。

アプリによるシステム リソースの節約を促進するため、Android 12 以降をターゲットとし、正確なアラームを設定したアプリでは、システム設定の[特別なアプリアクセス]画面に表示される「アラームとリマインダー」機能へのアクセス権が必要になります。

この特別なアプリアクセスを取得するには、マニフェストで SCHEDULE_EXACT_ALARM 権限をリクエストします。

正確なアラームは、ユーザー向けの機能にのみ使用する必要があります。

書いてあることそのままになりますが、理解するべきは次の2点です。

  • Android 12からは、正確なアラームには正確なアラームの権限が必要になった
  • Android 12からは、正確なアラームの権限が必要な場合は、SCHEDULE_EXACT_ALARM権限のリクエストがAndroidManifest.xmlに必要になった

正確なアラームを使用するための新しい権限

Android 13のガイドを読むと、次のような解説があります。

Android 13 以降をターゲットとするアプリの場合、アプリに自動的に付与される USE_EXACT_ALARM 権限を使用できます。ただしアプリがこの権限を使用するには、次の条件の少なくとも 1 つを満たす必要があります。

  • 目覚まし時計アプリまたはタイマーアプリである。
  • イベントが近づくと通知を表示するカレンダー アプリである。

正確なアラームを設定する機能がアプリにあるものの、上記のどのケースも満たさない場合は、引き続き SCHEDULE_EXACT_ALARM 権限を宣言し、ユーザーがアプリのアクセスを拒否した場合も想定しておく必要があります。

書いてあることそのままになりますが、理解するべきは次の3点です。

  • Android 13からは、USE_EXACT_ALARM権限が自動的に付与されることになった
  • USE_EXACT_ALARMを利用できるアプリは「目覚まし時計アプリまたはタイマーアプリ」または「イベントが近づくと通知を表示するカレンダー アプリ」のみ
  • (Android 13リリース時点において、)将来的にGoogle Playポリシーの更新で、上記のいずれかのケースを満たさない限り、USE_EXACT_ALARM権限の使用は禁止

Android 14の機能アップデート

https://developer.android.com/about/versions/14/changes/schedule-exact-alarms?hl=ja

Andorid 14には、13までの対応から一歩踏み込んだ変更が入っています。

カレンダーやアラームアプリ

USE_EXACT_ALARMがインストール時に付与されるため、アラームの利用に制限はありません。
これらのアプリは、正確なアラームの設定が行えます。

カレンダーやアラームアプリではないアプリ

AndroidManifest.xmlにおけるSCHEDULE_EXACT_ALARMのリクエストは、デフォルトで拒否されます。
このため、いくつかの対応パターンを検討する必要が生じます。

  • 正確なアラームを利用するため、ユーザーに権限をリクエストする
  • 正確なアラームの利用ではなく、不正確なアラームの利用に切り替える

アラームの利用シーンに応じたパターンがドキュメントに記載されているため、アプリにおけるアラーム機能の必要性に応じて、個別に検討することをお勧めします。正確なアラームを必要としないユースケース

なお、この変更はTarget SDKが33(Andorid 13)以上であることに注意してください。2023年8月以降にリリースされるアプリは、Google Play Storeの制限でTarget SDKを33以上にする必要が生じます。

https://developer.android.com/google/play/requirements/target-sdk?hl=ja


正確なアラームを利用するためには、Androidの特別な権限をリクエストするフローを利用する必要があります。
このフローを経ることで、ユーザーが許可を与えた場合には、アプリがSCHEDULE_EXACT_ALARM権限を利用できるようになります。

細かなリクエストの処理を個別で書くこともできますが、Flutterの場合には、flutter_local_notificationsライブラリに処理をお任せすることになります。
ここからは、ライブラリの実装を確認しながら、どのように正確なアラーム不正確なアラームをセットするか確認していきます。

flutter_local_notificationsのAndroidScheduleMode(4+1)

flutter_local_notificationsを利用して、時間を指定し、通知を表示する方法を確認します。
ライブラリのexampleの中から、実装を引っ張ってきて確認します。まず_zonedScheduleAlarmClockNotification()を確認してみましょう。

Future<void> _zonedScheduleAlarmClockNotification() async {
  await flutterLocalNotificationsPlugin.zonedSchedule(
      123,
      'scheduled alarm clock title',
      'scheduled alarm clock body',
      tz.TZDateTime.now(tz.local).add(const Duration(seconds: 5)),
      const NotificationDetails(
          android: AndroidNotificationDetails(
              'alarm_clock_channel', 'Alarm Clock Channel',
              channelDescription: 'Alarm Clock Notification')),
      androidScheduleMode: AndroidScheduleMode.alarmClock,
      uiLocalNotificationDateInterpretation:
          UILocalNotificationDateInterpretation.absoluteTime);
}

この記事の関心はアラームの表示モードです。この処理ではandroidScheduleMode: AndroidScheduleMode.alarmClockを深掘りしていきます。
AndroidScheduleModeには、4つ+1つのenumがあります。
4つ+1つと表現しているのは、お察しの通り、SCHEDULE_EXACT_ALARMが許可されるか否の分岐があるためです。

alarmClock

Used to specify that the notification should be scheduled to be shown at the exact time specified AND will execute whilst device is in low-power idle mode. Requires SCHEDULE_EXACT_ALARM permission.

AndroidScheduleMode.alarmClockSCHEDULE_EXACT_ALARMを前提とした動きをする選択肢です。
Android 14以降ではAndroidManifest.xml経由で取得できなくなったため、このリクエストを送る前に権限を取得しておく必要があります。

権限が取得されていない場合には、SecurityExceptionがAndroidのExceptionとして発生することになります。
実装を確認する限り、try-catchで処理されていないため、Pluginの処理内でクラッシュするようです。ご注意ください。

なおflutter_local_notificationsでは、v16にて「SCHEDULE_EXACT_ALARMをリクエストする機能」の追加が予定されています。
詳細はv16のリリース時に追加されるドキュメントで確認する必要がありますが、現時点ではMethodChannelFlutterLocalNotificationsPlugin.requestExactAlarmsPermission()を呼び出すことで、権限をリクエストする処理を(単体で)呼び出せるようになります。

https://github.com/MaikuB/flutter_local_notifications/pull/2052


Androidのコードも簡単に確認しておきましょう。
DartでalarmClockを選択すると、JavaのScheduleMode.alarmClockにマッピングされます。

https://github.com/MaikuB/flutter_local_notifications/blob/flutter_local_notifications-v15.1.0%2B1/flutter_local_notifications/lib/src/platform_specifics/android/schedule_mode.dart#L9
https://github.com/MaikuB/flutter_local_notifications/blob/flutter_local_notifications-v15.1.0%2B1/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/models/ScheduleMode.java#L11

JavaではuseAlarmClock()のみがtrueとなり、alarmClock専用の処理がなされます。
FlutterLocalNotificationsPlugin.javaの処理を確認すると、1回/繰り返しの通知設定共にAlarmManagerCompat.setAlarmClockを呼び出します。

https://github.com/MaikuB/flutter_local_notifications/blob/flutter_local_notifications-v15.1.0%2B1/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/FlutterLocalNotificationsPlugin.java#L690
https://github.com/MaikuB/flutter_local_notifications/blob/flutter_local_notifications-v15.1.0%2B1/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/FlutterLocalNotificationsPlugin.java#L707

alarmClock以外

flutter_local_notificationsのv14で追加された、Android 13以降で増えたアラームの設定に対応するためのenumです。
それぞれ、名前から明らかではあるのですが、PluginのJava側で次のtrue/falseの表現を持ちます。

exact exactAllowWhileIdle inexact inexactAllowWhileIdle
useAllowWhileIdle() false true false true
useExactAlarm() true true false false

FlutterLocalNotificationsPlugin.javaを確認すると、useExactAlarm()useAllowWhileIdle()は次の処理がなされます。

useAllowWhileIdle()

trueの場合、AlarmManagerCompat.setAndAllowWhileIdleまたはAlarmManagerCompat.setExactAndAllowWhileIdleが呼び出されます。

ドキュメントにある通り、低電力アイドルモード、つまりDozeの最中にアラームが発生することを許可します。
前述のalarmClockとの違いとしては、Doze の制限事項を確認する限り「alarmClockの場合にはDozeを解除する、AllowWhileIdeの場合には解除しない」点になるようです。Dozeがバッテリー消費に寄与する機能である以上、この設定の差は端末のバッテリー消費に影響すると考えられます。

useExactAlarm()

trueの場合、ExactAlarmが利用できるかを確認し、利用できる場合にAlarmManagerCompat.setExact若しくはAlarmManagerCompat.setExactAndAllowWhileIdleが呼び出されます。
この時ExactAlarmが利用できるかAlarmManagerCompat.canScheduleExactAlarmsを呼び出しチェックするだけです。(利用できない場合には、通知がキャンセルされ、Log出力がなされます。)
つまり、権限の取得については処理を行いません。

flutter_local_notificationsのv16がリリースされれば、必要な権限についても、flutter_local_notificationsライブラリで完結できます。
しかしv15まででは、(アラームやカレンダーアプリでない場合には、)他のライブラリや自前の実装により、SCHEDULE_EXACT_ALARMの権限を取得する必要があります。

結論

  • カレンダーやアラームを主な機能としたアプリは、これまで通りでOK
  • カレンダーやアラームを主な機能としていないアプリは、権限周りを調査してからリリースする必要アリ
  • 正確なアラームを必要としない場合は、不正確なアラームを利用する方がユーザーと開発者双方にとって嬉しい
  • flutter_local_notificationsライブラリは神
GitHubで編集を提案

Discussion