Android: AlarmManagerのPendingIntentでハマった

先週からずっとBatteryCrystalのバグ修正と戦っているのですが、
未だに収束していません!(>_<) バッテリー状態を見張るBroadcastReceiverを登録しているServiceがシステムに勝手に落とされるのがバグの原因と思っているのでそれをなんとかしようと色々と修正し、twitterでアプリの検証をしてくださる方を募ってテストしていただいているのですが、まだ直らず。今さっき出来たバージョンでたぶんいけると思うのですが、深夜なので、メールは朝させてもらおうと思ってます。 とりあえず、今までやったことを一覧にしておきます。

  1. AlarmManagerを使って制御。
    ただ、AlarmManager.setしたイベントがウィジェットを削除してもServiceを起動させてしまったため、断念(私のミス)
  2. ServiceのonDestroyのタイミングで、独自のBroadcastを投げて、受け取ったレシーバがServiceを起動させる。
    これで勝ったと思ったのだけど、ServiceがonDestroyを通らずに落ちる場合があるらしく、動かなかったと報告ありorz
  3. @yokmamaさんと@hyoromoさんから助言いただき、AlarmManagerに再度チャレンジ。cancelすればServiceを止められるらしい。で、成功!あとはHT-03Aユーザさんに検証していただく←今ここ

で、紆余曲折あったので、メモを残しておこうと思います。

  1. PendingIntentをメンバ変数に持ってはいけない!

あれ?結果的にこれだけだなー…。
これはどういうことかというと、AlarmManager.setするときに、PendingIntentのインスタンスを渡すんですが、AlarmManager.cancelするときにもPendingIntentの同じ情報を渡すので、同じだったらメンバ変数に持っておいたら消す時に間違わなくていいだろうと思っていたのです。ですが、このメンバ変数に入れてあるPendingIntentを渡してcancelしたつもりでも、全然キャンセルされないのです。

この時点でPendingIntentをメンバ変数にしているということが原因だと気付いていなかったので、cancelするタイミングをonDeletedからonDisabledに変えてみたりだとか、onUpdateイベントをsetしていたので、異なるレシーバ経由でService呼び出してみたりだとか、余計なことばかりしていました。

数時間後に、ここをメンバ変数でなく、イベント毎にPendingIntentを生成してAlarmをcancelしたら止まりました。

おそらく、これでサービスの起動も定期的に確認できるので、HT-03Aなどでもまともに動くようになるかと思います。ただイベント発生数が前のに比べると結構多いので、バッテリーの消費量が気になるのですが。まぁ通信とかなくて自分のバッテリー残量を確認するだけのサービスの生存確認なので、大した事はないかと思いますけどね…。

ソースの全体ではありませんが、ハマった部分について自戒も込めて公開しておきます。
LogUtils.dは、自分のライブラリのメソッドです。
マニフェストでandroid:debuggable=trueならLog.dでログ出すというやつです。

public class BatteryCrystal extends AppWidgetProvider {
	private static final String TAG = "BatteryCrystal";
	private static final long INTERVAL = 300000;	// 5分
//	private PendingIntent sender; // 使えなかったのでコメントアウト

	@Override
	public void onDisabled(Context context) {
		LogUtils.d(TAG, "onDisabled called!!");
		AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
		// ウィジェットが削除されたらアラームを停止する
		// メンバ変数ではだめだったのでgetPendingAlarmIntentで取得
		PendingIntent sender = getPendingAlarmIntent(context);
		am.cancel(sender);
		super.onDisabled(context);
	}
	@Override
	public void onReceive(Context context, Intent intent) {
		LogUtils.d(TAG, "onReceive called!!");
		String action = intent.getAction();
		if (AppWidgetManager.ACTION_APPWIDGET_UPDATE.equals(action)) {
			setAlarm(context);
			Intent serviceIntent = new Intent(context, BatteryCheckService.class);
			// context.stopService(serviceIntent);
			context.startService(serviceIntent);
		}
		super.onReceive(context, intent);
	}
	private void setAlarm(Context context) {
		AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
		long now = System.currentTimeMillis() + 1;
		long nextTime = now + INTERVAL - now % 1000;
		// メンバ変数ではダメだった
		// sender = getPendingAlarmIntent(context);
		PendingIntent sender = getPendingAlarmIntent(context);
		am.set(AlarmManager.RTC_WAKEUP, nextTime, sender);
	}

	private PendingIntent getPendingAlarmIntent(Context context) {
		Intent intent = new Intent(context, BatteryCrystal.class);
		intent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
		PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent, 0);
		return pendingIntent;
	}
}

カテゴリー Android | タグ | パーマリンク

コメント・トラックバック一覧

  1. 杖松 says:

    私も全く同じところでハマっておりました。
    助かりました。ありがとうございました。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です