techium

このブログは何かに追われないと頑張れない人たちが週一更新をノルマに技術情報を発信するブログです。もし何か調査して欲しい内容がありましたら、@kobashinG or @muchiki0226 までいただけますと気が向いたら調査するかもしれません。

AlarmManagerを攻略する

Motivation

KITKAT以降でAlarm発火のタイミングがInExactになり、MARSHMALLOWでDozeが採用され更にややこしくなったAlarmManagerについてまとめておく。

Exact指定について

はじめに、KITKAT以降から登場するExact指定についてまとめる。
Exactのついたメソッドはその名の通り、遅延せずに発火する。
厳密にはFrameworkが極力遅延の無いように発火する。

set()などの遅延時間がどう決められるかというと、指定したtriggerTimeに対し現時刻からの差の75%が最大の遅延時間として設定されるようになっている。(Repeat系のメソッドの場合はIntervalの75%がそれにあたる)
この値はFramework内ではwindowLengthとして扱っている。

バッテリーへの影響を考えて、極力Suspend状態に入れるための措置なので何でもかんでもExactにするのは良くない。

(4/17 追記)
Framework内ではExact指定を行うと、そのイベントに合わせたbatch(処理の塊単位というニュアンス)が生成される。
set()の場合は近しいbatchに対し登録する形であるが、専用に作られる点が大きく違う。
例を挙げるなら、1分以内にExactが2つある場合、それぞれのbatchが生成されてしまう点に大きくコストがかかってしまう。

IDLE状態で無いタイミングで行いたい処理であれば、set()を利用せずにJobSchedulerの導入の検討もするといいと思う。

windowLengthを自由に設定したい場合

基本的に上記のwindowLengthはFramework内(AlarmManager.java)で設定される。
唯一この値を自由に設定できるメソッドがsetWindow(int type, long windowStartMillis, long windowLengthMillis, PendingIntent operation)である。
windowLengthMillisを0に設定すれば、setExact()と同等の設定が可能である。
負の値を指定した場合は、set()等と同じく75%の件が出てくるので注意すること。

75%を指定されると困るが、多少遅れても良い場合にはこちらを利用すると良いのでは。

Dozeへの対応について

Dozeについて

各所で説明されているが、Dozeは端末が居眠りするモードでDoze中には下記のことが起こる。

  • ネットワークアクセスのサスペンド
  • wakelock取得が拒否される
  • AlarmManager.set() / AlarmManager.setExact()は次のMaintenance Windowまで遅延される
  • wifiのスキャンが実行されない
  • Sync Adapaterが実行されない
  • JobSchedulerが実行されない

DozeについてはADBを利用して擬似的にモードへ入れることが出来る。

$ adb shell dumpsys battery unplug         
$ adb shell dumpsys deviceidle enable    
$ adb shell dumpsys deviceidle step
($ adb shell dumpsys deviceidle force-idle      //強制的に入れる場合はこちら)

$  adb shell dumpsys deviceidle| grep mState    //確認
  mState=IDLE


$ adb shell dumpsys deviceidle disable     //元に戻す
$ adb shell dumpsys battery reset

Doze下でのset()およびsetExact()の挙動

上記ADBにより作成した環境を用いて確認したところ、どちらもDoze下ではドキュメントの通り発火しない。
disableしたタイミングでIDLE→ACTIVEと状態が変化するため、このタイミングで発火する。
RTC_WAKEUP等の設定はDozeではなく、単にSuspendに対して有効であるので気をつけること。

Doze下での setAndAllowWhileIdle()およびsetExactAndAllowWhileIdle()

どちらも制限つきでDoze中に処理を実行することができる。
その制限は、IDLE中に発火した場合の実行時間が10sであるということと、IDLEからの復帰は15minに一度だけ許されるということである。
状態はIDLE→ACTIVEと変動する訳では無く、そのアプリケーションのみが動作する。
Exactのある無しについては前述の通り。Dozeに対処可能だといってwindowLengthの件は別に設定されるので注意すること。
この点が後述するsetAlarmClock()と大きく異なる。

Doze下でのsetAlarmClock()

このメソッドには前述の制限が全て無いメソッドである。
発火タイプとしては、RTC_WAKEUPが設定され、Exactを持ち、IDLE中にも発火する。
ただし、発火直前にIDLE→ACTIVEと状態が遷移するため、デバイス毎起こしてしまう事になるため、バッテリーへの影響が非常に大きい。
本当に必要な場合のみに利用すべきである。

その他の気づき

AlarmManagerのドキュメントを読んでいると、ちょっとしたタイミング制御にはHandlerを使えと書いてある。
AlarmManagerServiceのsourceを見ると5秒未満のAlarmに対しては5秒に丸める処理が入っているので、あまり短い間隔で指定するものでは無いと理解しておくとよいと思う(6.0.1調べ)
同じく、Repeat系のメソッドに対しinterval < 1minを設定しても1minに丸められるので注意すること。

また、21以上からgetNextAlarmClock()が追加され、次に発火するAlarmClockの情報が取得出来るようになっている。 setAlarmClock()を行う場合は、同一ユーザー(マルチユーザーの話)のアプリケーションから、PendingIntent.send()を用いてIntentが発火される可能性がある点にも気をつけておくこと。