スリープ時にもBroadcastを処理する方法

スリープとBroadcast

端末がスリープ状態の場合でもBroadcastReceiver.onReceive()の呼び出しは通常どおり行われる。 ただし、onReceive()からServiceを呼び出して処理させる場合、その処理が中断されてしまう可能性がある。 確実にバックグラウンド処理を行うためには、Wakelockによってスリープ状態を制御する必要がある。

この処理は通常、ServiceWakelockを扱う場合、PowerManagerを使って以下の様に記述する。

<uses-permission android:name="android.permission.WAKE_LOCK" />
PowerManager powerManager =  (PowerManager)getSystemService(POWER_SERVICE); 
Wakelock wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK , "MyWakelockTag"); 
// wakeLock開始
wakeLock.acquire();

// 処理

// wakeLock解除
wakeLock.release();

これでも問題なく動くのだが、Androidではスリープ時にBroadcastを扱うための便利な仕組みが用意されている。

WakefulBroadcastReceiver

WakefulBroadcastReceiverはSupport Libraryに含まれるBroadcastReceiverのサブクラスである。 スリープ状態でServiceを扱うためのメソッドが二つ追加されている。 https://developer.android.com/reference/android/support/v4/content/WakefulBroadcastReceiver.html

Manifest記述は通常のBroadcastReceiverと変わらない。 当然、Manifestへのuses-permission記述も必要となる。

<uses-permission android:name="android.permission.WAKE_LOCK" />
<receiver android:name=".MyBroadcastReceiver">
...
</receiver>
<service android:name=".MyService" />

サービスの起動にはstartServiceではなくstartWakefulServiceを使用する。 このメソッドは内部でstartServiceを呼んだあとWakelock.acquire()でスリープ制御を行っている。

public class MyBroadcastReceiver extends WakefulBroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        Intent serviceIntent = new Intent(context,MyService.class);
        // サービス起動
        startWakefulService(context,serviceIntent);
    }
}

サービス側では処理の終了時に必ず呼び出し元BroadcastReceiverのcompleteWakefulIntent()を呼ぶ。 また、startWakefulService()ではIntent.putExtra()でwakelock制御IDをIntentに追記しているため、Service起動時のIntentを引数に入れること。

public class MyServiceextends IntentService {

    public MyService() {
        super("MyService");
    }

    @Override
    protected void onHandleIntent(final Intent intent) {
        try {
             // サービス内部の処理
        }finally {
            // Wakelockの解除処理が必ず呼ばれるようにしておく
            MyBroadcastReceiver.completeWakefulIntent(intent);                    
        }
    }
}

まとめ

コード量を大幅に削減できてID管理とかも裏でやってくれるのは楽だ。 スリープ時にBroadcastを処理することはよくあるので、ぜひ覚えておきたい。 しかし、BroadcastReceiverServiceの結合が強くなってしまうのは良いんだろうか…。 Servcieを使い回したい場合はどうするんだろう。継承?