2011年3月15日火曜日

AlarmManager

AlarmManager

・システムの AlarmService を使うためのクラス
・アプリケーションを将来のあるポイントで起動するようスケジュールできる

・Alarm が開始すると、システムによって Intent が broadcast される
・この Intent に起動したいアプリケーションを登録しておく
・これにより、現状で起動していないアプリケーションが自動で起動する

・登録された Alarm はデバイスがスリープ状態の間保持される(オプションでデバイスがオフの場合に wake up させることができる)が、再起動したり電源を切ると登録はクリアされる


・Alarm Manager は alarm receiver の onReceive() メソッドが実行されているのと同じだけ CPU を hold する
・これはブロードキャストの処理が終了するまで電話がスリープ状態にならないことを保証する

・一度 onReceive() を返したら、Alarm Manager はこの wake lock を離す
・これは、onReceive() メソッドが完了してすぐにスリープ状態になる場合があることを意味する
・もし alarm receiver が Context.startService() を呼ぶ場合、service を起動するリクエストが完了するまえに電話がスリープ状態になる可能性がある
・これを防止するために、BroadcastReceiver と Service は、service が利用可能になるまで電話が動作し続けていることを確認するために、別々の wake lock policy を実装する必要がある


注意:
・Alarm Manager は現在アプリケーションが走っていなくても、特定の時刻にアプリケーションコードを実行したい場合を対象としている
・一般的なタイミング操作(タイミング計測、時間計測など) は Handler を使ったほうがより簡単で効果的


このクラスを直接インスタンス化してはいけない
Context.getSystemService(Context.ALARM_SERVICE) を通して取得する

-----------------------------

getSystemService(Context.ALARM_SERVICE)
で AlarmManager のインスタンスを取得

このインスタンスに対して

・set(int type, log triggerAtTime, PendingIntent operation)
・setInexactRepeating(int type, long triggerAtTime, long interval, PendingIntent operation)
・setRepeating(int type, long triggerAtTime, long interval, PendingIntent operation)

で alarm を登録する


まずは最小構成で。



・MainActivity : alarm をセット
・AlarmReceiver : alarm を受信
  AndroidManifest.xml に <receiver> で登録


public class MainActivity extends Activity {

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

Intent intent = new Intent(MainActivity.this, AlarmReceiver.class);
PendingIntent sender = PendingIntent.getBroadcast(MainActivity.this, 0, intent, 0);

Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(System.currentTimeMillis());
calendar.add(Calendar.SECOND, 20);

AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
// one shot
alarmManager.set(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), sender);

Toast.makeText(MainActivity.this, "Start Alarm!", Toast.LENGTH_SHORT).show();
}
}



public class AlarmReceiver extends BroadcastReceiver {

@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context, "Alarm Received!", Toast.LENGTH_SHORT).show();
Log.d("AlarmReceiver", "Alarm Received! : " + intent.getIntExtra(Intent.EXTRA_ALARM_COUNT, 0));
}
}


AndroidManifest.xml
(一部の属性省略)

<manifest>
<application>
<activity android:name=".MainActivity"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

<receiver android:name=".AlarmReceiver" android:process=":remote" />

</application>
</manifest>


アプリを起動して 20秒後に alarm が起動して Toast が表示される
AlarmManager.RTC_WAKEUP を指定しているので、デバイスがスリープ状態でも LogCat に "Alarm Received! : ..." が出力される

repeat の場合

// repeat
long firstTime = SystemClock.elapsedRealtime();
firstTime += 15*1000;
alarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, firstTime, 15*1000, sender);


おおまかな repeat の場合

// repeat
Calendar cal = Calendar.getInstance();
calendar.setTimeInMillis(System.currentTimeMillis());
cal.roll(Calendar.HOUR, true);
cal.set(Calendar.MINUTE, 0);
cal.set(Calendar.SECOND, 0);
cal.set(Calendar.MILLISECOND, 0);
Log.d("Calendar", cal.toString());

alarmManager.setRepeating(AlarmManager.RTC, cal.getTimeInMillis(), AlarmManager.INTERVAL_HOUR, sender);


など

repeat のキャンセルは

// cancel
alarmManager.cancel(sender);



スリープ状態をやめる



最初に書いたように、Alarm Manager を使うときの注意点として、
"onReceive() を返したあとは、すぐにスリープ状態になることがある" ということ

これを回避するために、onReceive() 内で、別の wake lock policy を使ってスリープ状態をめるようにしてみる


public class AlarmReceiver extends BroadcastReceiver {

@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context, "Alarm Received!", Toast.LENGTH_SHORT).show();
Log.d("AlarmReceiver", "Alarm Received! : " + intent.getIntExtra(Intent.EXTRA_ALARM_COUNT, 0));

// スクリーンオン
PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "My Tag");
wl.acquire(20000);
//wl.acquire();
}
}


acquire() でもいいが、wakelock は電池を食うので、timeout を設定する acquire(long timeout) を使うべき


public class AutoStartActivity extends Activity {

....

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

keyguard = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE);
keylock = keyguard.newKeyguardLock("keyLock");
}

private KeyguardManager keyguard;
private KeyguardLock keylock;

@Override
public void onResume() {
super.onResume();
// キーロックオフ
keylock.disableKeyguard();
Log.d("MainActivity", "keylock off");
}

@Override
public void onPause() {
super.onPause();
// キーロックオン
keylock.reenableKeyguard();
Log.d("MainActivity", "keylock on");
}
}


wakelock を使うには android.permission.WAKE_LOCK
keylock を使うには android.permission.DISABLE_KEYGUARD
を AndroidManifest.xml の <uses-permission> タグで追加する必要がある


AlarmManager の method と field



* set(int type, log triggerAtTime, PendingIntent operation)

・1回だけの alarm を登録する
・すでに同じ IntentSender に対してスケジュールされている場合、最初のがキャンセルされる
・過去の時間を指定した場合、ただちに alarm が起動する
・Alarm intent は、Intent.EXTRA_ALARM_COUNT というタイプの extra data を持っている
・これは、この intent broadcast に過去の alarm event が何個蓄積されているかを示す
・定期的な alarm がスリープ状態によって起動されてなかった場合、この値は 1以上になる可能性がある


* setRepeating(int type, long triggerAtTime, interval, PendingIntent)

・一定間隔で繰り返す定期的 alarm
・cancel(PendingIntent) で明示的に除かれるまで繰り返しは継続される
・設定時間が過去の場合、alarm は直ちに実装され、alarm count は繰り返し間隔に比べてどれだけトリガー時間が遠いかに依存する
・_WAKEUP ではない alarm type などでシステムのスリーブ状態で遅延された alarm は繰り返しはスキップされ、可能になった段階ですぐに実行される
・その後はもともとのスケージュールで alarm が実行される
・over time はドリフトされない
・over time をドリフトしたい場合は、one-time alarm を使って、その alarm が呼ばれたときに次の alarm をセットするようにする


* setInexactRepeating(int type, long triggerAtTime, long interval, PendingIntent operation)

・おおまかなトリガー時間が必要な定期的 alarm のスケジュール
・例えば、毎時間繰り返したいが、毎時0分ぴったりでなくていい場合など
・それ以外は setRepeating と同じ


type : ELAPSED_REALTIME, ELAPSED_REALTIME_WAKEUP, RTC, RTC_WAKEUP のどれか

triggerAtTime : type 設定した時計での alarm が起動する時間

interval : 繰り返し間隔、INTERVAL_FIFTEEN_MINUTES, INTERVAL_HALF_HOUR, INTERVAL_HOUR, INTERVAL_HALF_DAY, or INTERVAL_DAY のどれかが指定されている場合、余計な wakeup を減らすために、他の alarm と phase-aligned される

operation : alarm が起動したときに実行する処理、一般的には IntentSender.getBroadcast() で取得する


* interval

 INTERVAL_FIFTEEN_MINUTES
   Constant Value: 900000 (0x00000000000dbba0)
   15分

 INTERVAL_HALF_HOUR
   Constant Value: 1800000 (0x00000000001b7740)
   30分

 INTERVAL_HOUR
   Constant Value: 3600000 (0x000000000036ee80)
   1時間

 INTERVAL_HALF_DAY
   Constant Value: 43200000 (0x0000000002932e00)
   半日

 INTERVAL_DAY
   Constant Value: 86400000 (0x0000000005265c00)
   一日


* type

 ELAPSED_REALTIME
   Constant Value: 3 (0x00000003)
   SystemClock.elapsedRealtime() での時間 (= ブートしてからの時間、スリープ状態を含む)
   alarm はデバイスを起こさない
   デバイスがスリープ状態の場合に alarm が動作しても、次にデバイスが起きるまで機能しない

 ELAPSED_REALTIME_WAKEUP
   Constant Value: 2 (0x00000002)
   SystemClock.elapsedRealtime() での時間 (= ブートしてからの時間、スリープ状態を含む)
   デバイスがスリープ状態の場合に alarm が動作した場合、alarm はデバイスを起こす
   
 RTC
   Constant Value: 1 (0x00000001)
   System.currentTimeMillis() での時間 (= UTC での wall clock time)
   alarm はデバイスを起こさない
   デバイスがスリープ状態の場合に alarm が動作しても、次にデバイスが起きるまで機能しない

 RTC_WAKEUP
   Constant Value: 0 (0x00000000)
   System.currentTimeMillis() での時間 (= UTC での wall clock time)
   デバイスがスリープ状態の場合に alarm が動作した場合、alarm はデバイスを起こす


 

2 件のコメント:

  1. wakelockのtimeoutにはバグがあるので少なくともOS2.2まででは使い物になりません。timeoutを設けるけども、正常に終われば手動で解放する、ということができないためです。

    http://code.google.com/p/android/issues/detail?id=14184

    とっても残念です。

    返信削除
  2. Tomcat さん

    コメントありがとうございます。
    timeout にバグがあるんですね、知りませんでした。
    OSのバージョンをみて切り替える処理が必要ですね。むむむ。

     

    返信削除