Posted at

イベントの通知にインテントのブロードキャストを使う話

More than 3 years have passed since last update.

ActivityやFragmentをまたいだイベントの通知を行うのは、なかなかにやっかいです。

EventBusを使うという手もありますが、ここでは、Androidが標準で持っているインテントのブロードキャストを使ってみることにします。


LocalBroadcastManager

ブロードキャストを使うと言っても、Android OSが持っているブロードキャストと、サポートライブラリのLocalBroadcastManagerが提供しているそれの2種類があるので、ケースに応じて使い分けましょう。

LocalBroadcastManagerはLocalと付いていることからわかるように、自分のアプリの外には影響しませんし、外からの影響も受けません。

ただし、その実装は、(おそらく)EventBusなどと同様に、シングルトンなstatic変数でレシーバーやイベントを管理して配信しているライブラリで、そのインターフェースをOSの提供しているブロードキャストに似せているものに過ぎません。

アンチstatic変数派で、アプリの外の影響は気にしないというのであれば、本来のブロードキャストを使った方がよいかもしれません。


BroadcastReceiverはめんどくさい

BroadcastReceiverを用いたインテントのブロードキャストは、もともと、アプリ間も含めたイベント通知の仕組みなので、とても汎用的にできています。

必要な情報をインテントに詰め込む必要があったり、インテントフィルターを指定したりといった余計な手間がかかるため、しばしばEventBusと比較して面倒と言われてしまいます。

なのであれば、面倒なところは、BroadcastReceiverの派生クラス内に隠蔽してしまうのが、まっとうなオブジェクト指向というものでしょう。


では実装の例示といきましょう

いきなりですが、以下のようなクラスを作ります。

public class MyBroadcastReceiver extends BroadcastReceiver {

private static final String ACTION_INVOKED = "パッケージ名とかを混ぜ込んでユニークな文字列にする.ACTION_INVOKED";
private static final String ID = "ID";
private static final String COUNT = "COUNT";

public interface Callback {
void onEventInvoked(long id, int count);
}
// onReceive()メソッドだと何をしているのかわからないので、
// onReceive()からコールバックを呼ぶようにする(名前大事!)

private Callback callback;
private LocalBroadcastManager manager;

private MyBroadcastReceiver(@NotNull Context context, @NotNull Callback callback) {
super();
this.callback = callback;
manager = LocalBroadcastManager.getInstance(context.getApplicationContext());

IntentFilter filter = new IntentFilter();
filter.addAction(ACTION_INVOKED);

manager.registerReceiver(this, filter);
}

public static MyBroadcastReceiver register(@NotNull Context context, @NotNull Callback callback) {
return new MyBroadcastReceiver(context, callback);
}
// コンストラクタでregisterReceiver()しているのだけど、
// そんなこと想像しづらいので、コンストラクタはprivateにして、
// register()というファクトリメソッドを用意する(名前大事!)

@Override
public void onReceive(@NotNull Context context, @NotNull Intent intent) {
String action = intent.getAction();

if (ACTION_INVOKED.equals(action) {
long id = intent.getLongExtra(ID, 0);
int count = intent.getIntExtra(COUNT, 0);
callback.onEventInvoked(id, count);
}
}
// onReceive()内でインテントから必要な情報を引っ張り出して、コールバックを呼ぶ。

public static void sendBroadcast(@NotNull Context context, long itemId, int count) {
Intent intent = new Intent(ACTION_CHEERED);
intent.putExtra(ID, itemId);
intent.putExtra(COUNT, count);

LocalBroadcastManager manager = LocalBroadcastManager.getInstance(context.getApplicationContext());
manager.sendBroadcast(intent);
}
// 必要な情報をインテントに詰め込んで、sendBroadcast()するクラスメソッドを用意しておけば、イベントの配信も簡単。
// onReceive()メソッドの動作と合わせて、完全にインテントの存在を隠蔽してます。

public void unregister() {
manager.unregisterReceiver(this);
}
}

と、こんな感じで実装しておけば、イベントを受ける側としては、

    

MyBroadcastReceiver receiver;

receiver = MyBroadcastReceiver.register(context, new MyBroadcastReceiver.Callback {
@Override
void onEventInvoked(long id, int count) {
// なにかやる
}
});

// いらなくなったら
receiver.unregister();

と、これだけでよくなります。

ちなみに、この手のレシーバーは単一目的ごとに作って、Callbackインターフェースのメソッドは一つにしておくのがオススメです。

AndroidStudioやIntelliJのエディタは親切なので、ごちゃっとしたところをたたんで、

    

MyBroadcastReceiver receiver;

receiver = MyBroadcastReceiver.register(context, (long id, int count) {
// なにかやる
});

// いらなくなったら
receiver.unregister();

と、表示してくれます。可読性が高いですね。

イベントを送る側はもっと簡単で、

    MyBroadcastReceiver.sendBroadcast(1L, 100);

これだけ。EventBusをそのまま使うよりも楽ちんです。

LocalBroadcastManagerを使わない場合は、manager変数がいらないので、コンストラクタ内とunregister()メソッドを、

    private MyBroadcastReceiver(@NotNull Context context, @NotNull Callback callback) {

super();
this.callback = callback;

IntentFilter filter = new IntentFilter();
filter.addAction(ACTION_INVOKED);

context.registerReceiver(this, filter);
}

public void unregister(Context context) {
if (context != null) {
context.unregisterReceiver(this);
}
}

と、しておくのがいいかと思います。フラグメントから呼ぶ場合に、ContextとしてgetActivity()なんかを使っていると、Activityが破棄されてたときなんかにトラブルのを防げます(その代わり、メモリリークしますが)。


任意のオブジェクトを渡したい

インテントにまとまった情報の塊を渡したい場合は、Parcelableインターフェースを実装しておかなければならないというのも、嫌われる原因になっていると思うのですが、Parcelableはそんなに難しくないです。

ガンガンParcelableで渡しちゃいましょう。


まとめ

・Androidに初めから用意されているから安心

・LocalBroadcastManagerを使うかどうかは、考え方次第

・面倒なところは隠蔽できます

・Parcelableは怖くない

・名前とか可読性とか大事