2011年5月5日木曜日

Androidで端末がスリープ中でも定期的にバックグラウンド処理を実行する

結論から言うと、AlarmManagerとPowerManager.WakeLockを組み合わせて使えばよい。
具体的には、まず、AlarmManagerを使用して、定期的にBroadcastのIntentを投げる。AlarmManager.RTC_WAKEUPを指定して、端末がスリープ中でもBroadcastが行われるようにしておく。
public class Foo {
    private static final long INTERVAL = AlarmManager.INTERVAL_FIFTEEN_MINUTES;

    public void setBroadcast() {
        Intent intent = new Intent(this, BarReceiver.class);
        PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent, 0);
        AlarmManager am = (AlarmManager) getSystemService(ALARM_SERVICE);
        am.setInexactRepeating(AlarmManager.RTC_WAKEUP, 
                System.currentTimeMillis(), INTERVAL, pendingIntent);
    }
}
次に、BroadcastReceiverを継承したクラスで、BroadcastのIntentを受け取るようにする。端末がスリープ状態だった場合、onReceiveを抜けると、端末が再びスリープ状態に戻るのが仕様なので、 PowerManager.WakeLock#acquier() で、端末がスリープ状態に移行しないようにしておく。(BarReceiverのインスタンスよりも長い期間存在する必要があるので、static変数にしてある) そして、バックグラウンド処理を実行するサービスを起動する。
public class BarReceiver extends BroadcastReceiver {
    private static PowerManager.WakeLock wl;
    
    @Override
    public void onReceive(Context context, Intent arg1) {
        PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
        wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "BarReceiver");
        wl.acquire();
        
        Intent intent = new Intent(context, BazService.class);
        context.startService(intent);
    }
    
    public static void releaseWakeLock() {
        if (wl != null) {
            wl.release();
        }
    }
}
サービスでは、バックグラウンド処理を実行し、最後にPowerManager.WakeLock#release()が実行されるようにして、端末が再びスリープ状態に戻れるようにしておく。
public class BazService extends Service {
    @Override
    public void onCreate() {
        new Thread(new BazProcess())
        .start();
    }
    
    private class BazProcess implements Runnable {
        
        @Override
        public void run() {
            // ここでバックグラウンド処理を実行。
            BarReceiver.releaseWakeLock()  // 最後に端末がスリープ状態に戻れるようにする。
        }
}