PendingIntentとAlermManager

ActivityやServiceを時刻指定で起動させたいときは、PendingIntentを作成し、Alermマネージャーに登録する
この2つのクラスをなんとなくセットで使っていると動作がよくわからず、複数のタイマーをセットした時におかしなことになります。

この2つのクラスはどう働いているか、別々に考えるとわかりやすいです。

まず、よくあるサンプルコード


public foobarTimer(Context context, Calendar when, String message){
  //起動させたいActivity, ServiceなどのIntentを指定
  Intent intent = new Intent(context, MainActivity.class);
  intent.putExtra("tag_name", "hello");//起動時に渡したい値をセット

  //intentを指定してPendingIntentを作成する。
  PendingIntent pIntent = PendingIntent.getService(context, 0, intent, 0);

  //AlermManagerをコンテキストより取得
  AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);

  //AlermManagerに起動する時間と起動したいIntentをセットしたpIntentを指定
  am.set(AlarmManager.RTC_WAKEUP, when.getTimeInMillis(), pIntent);
}

よくあるサンプルだし、通常のタイマーとしてはこれで問題なく動くんだけど、

PendingIntent pIntent = PendingIntent.getService(context, 0, intent, 0);

よくわかんないから0でいいや!ってなってしまいがち


Android ReferenceのPendingIntentをみると
getActivity(Context context, int requestCode, Intent intent, int flags)
第2と第4引数はそれぞれrequestCodeとflagsとやらを指定しろということになってる。

まず第2引数のrequestCodeは、そのアプリケーション内でPendingIntentを区別するためにユニークな値を入れる必要がある
たとえば幾つかのタイマーをセットできるアプリを考える。
先ほどのfoobarTimer関数を利用してこのように3つのタイマーを同時にセットした場合、これらはすべてrequestCode=0と指定されている

Calendar after10 = Calendar.getInstance().add(Calendar.SECOND, 10);
Calendar after20 = Calendar.getInstance().add(Calendar.SECOND, 20);
Calendar after30 = Calendar.getInstance().add(Calendar.SECOND, 30);
foobarTimer(this, after10, "10秒後です");
foobarTimer(this, after20, "20秒後です");
foobarTimer(this, after30, "30秒後です");

この場合、同じrequestCodeであることが原因で、3つが正しくセットされない。
結果としては30秒後に「10秒後です」と表示される。
というとんでもない事になります。


なぜこのようなことが起きるか掘り下げてみると、まずこの関数
PendingIntent.getActivity(Context context, int requestCode, Intent intent, int flags)
必ず新しくPendingIntentを作り出すわけではありません。
登録されているPendingIntentを再び返したり、なければ新しく作成したり、などをしているようです。
それを判断するのが requestCodeflagsなわけです。

PendingIntentのFlagsの説明をみると

FLAG_CANCEL_CURRENT Flag indicating that if the described PendingIntent already exists, the current one should be canceled before generating a new one.
FLAG_NO_CREATE Flag indicating that if the described PendingIntent does not already exist, then simply return null instead of creating it.
FLAG_ONE_SHOT Flag indicating that this PendingIntent can be used only once.
FLAG_UPDATE_CURRENT Flag indicating that if the described PendingIntent already exists, then keep it but replace its extra data with what is in this new Intent.

4つのFLAGが表記されてます。

詳しく説明すると

FLAG_CANCEL_CURRENT: 同じrequestCodeのPendingIntentが存在したら、それをキャンセルして新しく作ります。
FLAG_NO_CREATE: 同じrequestCodeのPendingIntentが存在すればそれを返します。そうでなければNullを返します。
strong>FLAG_ONE_SHOT: 一度特定のrequestCodeを持つPendingIntentが作られたら、それ以降で同じrequestCodeを指定された場合は最初に作られたPendingIntentを返し続けます。
FLAG_UPDATE_CURRENT: 同じrequestCodeのPendingIntentが存在するばあい、それをつかうが、putExtraの値は新しく更新します。

という指定になっています。
flagsに0を指定した場合にどうなるか、見つけられなかったのですが、FLAG_ONE_SHOTと同じ動きをしているとおもわれます。


そしてAlermManager.set()ですがこれは単純に、指定された時間にPendingIntentを実行する。
ただし、すでに同じrequestCodeのPendingIntentがセットされていたら、それを更新する。と考えればよいです。

30秒後に「10秒後です」と表示されたのは、2回めのPendingIntent.getActivity()で同じrequestCodeが指定されたため、
1回めに作成された「10秒後です」がセットされたPendingIntentが返されて、それが30秒後の指定でAlermManager.set()されてしまったために起きた現象です。


PendingIntentとAlermManagerを使う場合は
requestCodeを意識して、書き換えなのか、新規登録なのかを考えながら書く必要があります。

以下のサイト参考にさせていただきました。
http://d.hatena.ne.jp/yujimny/20110303/1299115905


トラックバックURL  -  http://mashi.exciton.jp/archives/45/trackback