ある時間にアプリから通知して欲しいという場合がよくあります。目覚まし時計とかスケジュール系のアプリなど、これらの通知には時間を管理する AlarmManager 及びその関連APIを使います。
2021.2.1
AlarmManager
いきなりですが、アプリのオプションでアラームを鳴らしたいだけなら、Intentを使ったアラーム クロックを使うと簡単です。
また、setExactAndAllowWhileIdleをサービスを使って繰り返す方法もあります。
短時間の単純なタイマーや時間計測にはAlarmManagerではなく、Handlerを使うことが推奨されています。
For normal timing operations (ticks, timeouts, etc) it is easier and much more efficient to use
Handler
.
Ref: AlarmManager
また、AlarmをBroadcastReceiver で受けるケースでは、ブロードキャストの制限事項 があります。
Android 8.0 を対象にしているアプリは、暗黙的なブロードキャストに対するブロードキャスト レシーバーをマニフェストで登録できなくなりました。
明示的な登録であれば大丈夫のようです。元々の理由が暗黙的BroadcastRecieverがたくさんあると関係ないアプリも毎回呼び出しを食らってリソース、消費電力の観点からよくないと言う理由からです。
また、AlarmManagerを使う上で、Dozeモードという低消費電力モードを考えないといけないのですが、API levelによって挙動が異なります。
つまり目覚まし時計を作ろうとした場合、このあたりをうまくやらないと時間通りにアラームが鳴りません。
この他にタスクスケジュールを実行できるののとしてJobSchedulerやJobDispatcherなどがあります。
AlarmManager
このクラスを直接インスタンス化してはいけないようで
Context.getSystemService(Context.ALARM_SERVICE) を使います
1 |
AlarmManager alarmManager = (AlarmManager)getSystemService(Context.ALARM_SERVICE); |
その後、例えば繰り返しのアラームメソッド set はこのように設定します
1 2 3 4 5 |
alarmManager.set( AlarmManager.RTC_WAKEUP, 5*1000, // 1sec = 1000 sender ); |
sender は PnedingIntent のインスタンスです。
これは単発アラームの例ですが、他にもメソッドがいくつもあります。
また、API levelによって様々追加されました。AlarmManager | Android Developers
- API level 19: アラーム時間の精度が低いものが使えるようになる
- API level 23: Dozeモードの導入でDoze中でも起動できるメソッドの追加
- API level 24: Direct callbackが使えるメソッド
: ワンショットのアラーム, API level 19 以降では低精度
1 2 3 4 |
//API level 1 set(int type, long triggerAtMillis, PendingIntent operation) |
set: 上記set に Direct callback が追加
1 2 3 4 5 |
//API level 24 set(int type, long triggerAtMillis, String tag, AlarmManager.OnAlarmListener listener, Handler targetHandler) |
setRepeating: 繰り返しアラーム
1 2 3 4 5 |
//API level 1 setRepeating(int type, long triggerAtMillis, long intervalMillis, PendingIntent operation) |
setInexactRepeating: 繰り返しアラーム、API level 19 以降では低精度
1 2 3 4 5 |
//API level 3 setInexactRepeating (int type, long triggerAtMillis, long intervalMillis, PendingIntent operation) |
setExact: setよりも正確なワンショットアラーム
1 2 3 4 |
//API level 19 setExact(int type, long triggerAtMillis, PendingIntent operation) |
setExact: 上記setExactに Direct Callback が追加
1 2 3 4 5 6 |
//API level 24 setExact (int type, long triggerAtMillis, String tag, AlarmManager.OnAlarmListener listener, Handler targetHandler) |
setAndAllowWhileIdle: setと同じだが low-power idleモードでも実行される。
1 2 3 4 |
//API level 23 setAndAllowWhileIdle (int type, long triggerAtMillis, PendingIntent operation) |
: setExactと同じだが low-power idleモードでも実行される。
1 2 3 4 |
//API level 23 setExactAndAllowWhileIdle (int type, long triggerAtMillis, PendingIntent operation) |
Doze中でも定期的にアラームを出にはこれらを使います。
setWindow: 設定したwindow内でアラームが実行
1 2 3 4 5 |
//API level 19 setWindow(int type, long windowStartMillis, long windowLengthMillis, PendingIntent operation) |
setWindow: 上記setWindowにDirect Callbackが追加
1 2 3 4 5 6 7 |
//API level 24 setWindow (int type, long windowStartMillis, long windowLengthMillis, String tag, AlarmManager.OnAlarmListener listener, Handler targetHandler) |
setAlarmClock: Dozeモードでもアラームを出してくれるようです。
1 2 3 |
// API level 21 setAlarmClock (AlarmManager.AlarmClockInfo info, PendingIntent operation) |
また、その他に
- cancel
- 設定したアラームの取り消し
- setTime
- ミリ秒での時間設定
- setTimeZone
- タイムゾーンの設定
などがあります
また、アラームに関する定数が幾つかあります。例えば、
- ELAPSED_REALTIME
- スリープ時間を含んだブートアップからの経過時間
- ELAPSED_REALTIME_WAKEUP
- ELAPSED_REALTIME に加えて、実機スリープ中では wake up してくれる
- RTC
- 時刻
- RTC_WAKEUP
- RTCに加えて実機スリープ中では wake up してくれる
PendingIntent
PendingIntent は作成した Intent をタイミングを見て他のアプリケーションに渡す場合に使います。
1 2 3 4 5 |
// Intent のインスタンス生成 Intent indent = new Intent(getApplicationContext(), AlarmBroadcastReceiver.class); // Broadcast にメッセージを送るための設定 PendingIntent pending = PendingIntent.getBroadcast(getApplicationContext(), 0, indent, PendingIntent.FLAG_IMMUTABLE); |
Android 12からは PendingIntentのmutability(可変性) を指定するために
FLAG_IMMUTABLE を設定する必要があります。
PeindingIntentでは受け取った側が指定されていないパラメータを追加して投げることができるためにFLAG_IMMUTABLEを明示的に指定して上書きできないようにします。
(FLAG_MUTABLE は書き換えできる)
getBroadcastの第2引数は requestCode です。PendingIntent が1つの場合は0で大丈夫ですが複数ある時は、この requestCode を使って Receiver 側で切り分けます
Ref: PendingIntent
http://developer.android.com/reference/android/app/PendingIntent.html
setExactなどの正確なアラームを設定したアプリでは、システム設定の [特別なアプリアクセス] 画面に表示される「アラームとリマインダー」機能へのアクセス権が必要になります。
マニフェストで SCHEDULE_EXACT_ALARM 権限をリクエストします。
1 |
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"/> |
BroadcastReceiver
PendingIntent からの Intent を受け取りるクラスを新しく作ります。そこでアラームを受けトーストします。
1 2 3 4 5 6 |
public class AlarmBroadcastReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { Toast.makeText(context, "Received ", Toast.LENGTH_LONG).show(); } } |
サンプルコード
これらの内容をふまえて実際にコードを組んでみましょう
WAKE_LOCK パーミッションとBroadcast の receiver を入れます。
AndroidManifest.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" .. <uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"/> <application ... <activity ... </activity> <receiver android:name=".AlarmBroadcastReceiver" android:process=":remote" /> </application> </manifest> |
ボタンをタップしてアラームをセットするようにします
MainAcrivity.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
//package your.package.name; import static android.app.PendingIntent.getBroadcast; import androidx.appcompat.app.AppCompatActivity; import android.os.Bundle; import android.app.AlarmManager; import android.app.PendingIntent; import android.content.Intent; import android.widget.Button; import android.widget.Toast; import java.util.Calendar; public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 開始ボタン Button button = this.findViewById(R.id.button1); String str = "Alarm Start"; button.setText(str); button.setOnClickListener(v -> { // 時間をセットする Calendar calendar = Calendar.getInstance(); // Calendarを使って現在の時間をミリ秒で取得 calendar.setTimeInMillis(System.currentTimeMillis()); // 5秒後に設定 calendar.add(Calendar.SECOND, 5); //明示的なBroadCast Intent intent = new Intent(getApplicationContext(), AlarmBroadcastReceiver.class); PendingIntent pending = getBroadcast( getApplicationContext(), 0, intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); // アラームをセットする AlarmManager am = (AlarmManager) getSystemService(ALARM_SERVICE); if(am != null){ am.setExact(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), pending); Toast.makeText(getApplicationContext(), "Set Alarm ", Toast.LENGTH_SHORT).show(); } }); } } |
アラームをBroadcastReceiver で受けます
AlarmBroadcastReceiver.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
//package your.package.name; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.widget.Toast; public class AlarmBroadcastReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { // toast で受け取りを確認 Toast.makeText(context, "Received ", Toast.LENGTH_LONG).show(); } } |
activity_main.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:gravity="center" tools:context=".MainActivity"> <Button android:id="@+id/button1" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="50dp" android:textSize="18sp"/> </LinearLayout> |
サンプル動画
アラームを設定して開始
BroadcastReceiver で受け取るという簡単な例でした
次はアラームを通知できるようにしてみます
関連:
- AlarmManagerをBroadcastRecieverと使う
- Alarm を NotificationManager で通知する
- Doze mode で AlarmManager の繰り返しアラームを実装するには
- アプリの restart
References:
AlarmManager | Android Developers
BroadcastReceiver | Android Developers
PendingIntent | Android Developers