Broadcast Receivers

Overview

Static vs. Dynamic

BroadcastReceiver は AndroidManifest.xml 内の <receiver> 要素を用いて静的に登録する方法と、Context#registerReceiver で動的に登録する方法がある。

Static:

<application>
    ...
    <receiver
        android:name=".ExampleReceiver"
        android:exported="false" >
        <intent-filter android:priority="0" >
            <action android:name="net.example.android.broadcast.EXAMPLE" >
            </action>
        </intent-filter>
    </receiver>
</application>

Dynamic:

private BroadcastReceiver mReceiver;

protected void onCreate(Bundle savedInstanceState) {
    ...
    mReceiver = new ExampleReceiver();
    IntentFilter ifilter = new IntentFilter("net.example.android.broadcast.EXAMPLE");
    ifilter.setPriority(0);
    registerReceiver(mReceiver, ifilter);
    ...
}

protected void onDestroy() {
    if (null != mReceiver)
        unregisterReceiver(mReceiver);
    super.onDestroy();
}

動的に登録/解除する場合、ライフサイクルに注意しなければならない。

  • Activity#(onCreate|onDestroy) の場合は、アクティビティが休止していても、タスクツリー内にあれば解除されない。
  • Activity#(onResume|onPause) の場合は、アクティビティが休止する際に解除される。onResume で登録する場合は、onPause で解除しなければならない。

アクティビティで登録を行なった場合は、明示的に解除されていないと、警告とともにアクティビティのライフサイクルで破棄される。

android.app.IntentReceiverLeaked: .... Are you missing a call unregisterReceiver()?

言い換えると、Context#getApplicationContext() で得られる Context から登録を行なった場合は、明示的に解除しなければ、メモリリークを引き起こすことになる。

Normal vs. Ordered

sendBroadcast で送信した場合は、順不同で Receiver に配信される。sendOrderedBroadcast で送信した場合は、IntentFilter に指定した priority 値の大きいものから、順番に配信される。

Orderd Broadcast の場合は、BroadcastReceiver#(get|set)ResultData により、各レシーバから結果データを受け渡すことができる。

public class ExampleReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        setResultData("Received");
    }
}
final String action = "net.example.android.broadcast.EXAMPLE";
registerReceiver(new ExampleReceiver(), new IntentFilter(action));
sendOrderedBroadcast(new Intent(action), null,
        new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                String data = getResultData();
                ...
            }
        }, null, 0, null, null);

結果データを得るには、最終結果を受け取るレシーバを登録できる Ordered Broadcast である必要がある。Normal Broadcast で、(get|set)ResultData を使うと警告が表示される。

Context vs. LocalBroadcastManager

Broadcast はグローバルに行なわれる。すなわち、外部アプリケーションのレシーバであっても IntentFilter にマッチすれば、配信を受け取ることができる。

  • アクション名は、他アプリケーションと衝突がないように、慣例的にパッケージ名を付与したものにする。
  • 外部アプリケーションによる受信を許可する場合には、適切なパーミッションを定義する。

レシーバ側からの視点でも、外部アプリケーションからの配信を受けてしまう事になる。静的に登録する場合は <receiver ... android:exported="false" ...> で、外部からの配信を受けないようにすることができるが、動的に登録する場合には、このオプションは使えないようである。

Broadcast を自アプリケーション内に限るなら、LocalBroadcastManager を使うことができる。LocalBroadcastManager による送受信は、同一アプリケーション内で閉じており、外部アプリケーションからは守られる。

LocalBroadcastManager mBcast = LocalBroadcastManager.getInstance(getApplicationContext());
mBcast.regsisterReceiver(mReceiver);
...
mBcast.sendBroadcast(new Intent(...));
...
mBrast.unregisterReceiver(mReceiver);

LocalBroadcastManager には、以下の制限がある。

  • 同一アプリケーション内であっても、LocalBroadcastManager で登録したレシーバは、LocalBroadcastManager からの送信のみ受け付ける。Context での送受信とは区別される。
  • LocalBroadcastManager では Ordered Broadcast はできない。

Sticky Broadcast

Sticky Broadcast は、配信後に永続的に残り、配信後に登録したレシーバであっても、最終配信を受信できる。

配信済みであれば Context#registerReceiver の戻り値 Intent で最終配信を得られるので、レシーバは null でよい。バッテリー残量などシステム情報を得るときに使う。

Intent batteyStatus = registerReceiver(
        null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
int capacity = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);

アプリケーションからは Context#(send|remove)StickyBroadcast で配信できる。

  • パーミッション android.permission.BROADCAST_STICKY を設定する。
  • 配信後も永続的に残るので、removeStickyBroadcast で破棄する。

Sticky Broadcast は、外部アプリケーションでも制限なく最終配信を得られるため、セキュリティ面での問題が多い。このため API level 21 より、関連する API は非推奨となっている。システムから、グローバルな端末情報等を配信するものとして用い、アプリケーションから配信する必要はない。利用しているアプリケーションがあれば、すみやかに別の方法でアップデートすべきである。