LoginSignup
34
45

More than 5 years have passed since last update.

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

Posted at

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は怖くない
・名前とか可読性とか大事

34
45
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
34
45