Androidアプリ開発 LocalBroadcastManager LocalBroadcastManagerでPub/Subパターンを実装する

2017年07月09日(編集2017年07月09日)
このエントリーをはてなブックマークに追加

Androidアプリを作成するとき、ActivityやFragment間のデータ受け渡しでPub/Subパターンを利用したい場合があります。
Androidでは、Otto, EventBus, RxJavaといったライブラリを利用しなくても、LocalBroadcastManagerでPub/Subパターンを実装できます。

この記事は、AndroidアプリでLocalBroadcastManagerを使ってPub/Subパターンを実装する方法を記載した記事です。

環境はAndroid 8.0 (API level 26) です。

環境

  • macOS Sierra
  • android sdk 26
  • Oracle jdk version 1.8.0_72
  • Android Studio 2.3.3
  • kotlin 1.1.1

難易度

中級者向け

サンプルコード

Android-Basic-Technique-Demo(Java)
Android-Basic-Technique-Demo(Kotlin)

動画

記事の内容を実装したアプリケーションの動画です。

Pub/Subパターン

パブリッシャ(Pub)と呼ばれるメッセージの送信者が、特定の受信者、つまりサブスクライバ(Sub)を想定せずにメッセージを送るようにプログラムすることです。

Pub/Subパターン使用例

Activity画面に複数のFragmentやViewを用いて表示している時に、離れたコンポーネントのFragmentやviewにデータに送りたい場合に使います。

LocalBroadcastManager

ローカルオブジェクトにインテントのブロードキャストを登録して送信するヘルパークラスです。自分のアプリ内にブロードキャストするので、他のアプリにデータが漏れることはありません。

その他のライブラリとの違い

LocalBroadcastManagerはsupport libraryのクラスなので、ライブラリのインストールをしないで利用できます。Otto、EventBus、RxJavaを使う場合はライブラリのインストールが必要なので、アプリのサイズが大きくなります。
とはいえ、LocalBroadcastManagerの機能は貧弱なので、何回ものPub/Subパターンが必要なアプリの場合は、素直にEventBusを利用した方が良いでしょう。

アプリ設計

この記事のサンプルでは、ActivityにbodyとfooterのFragmentをコミットし、bodyのFragmentから呼び出したダイアログから、footerのFragmentにデータをブロードキャストします。

fig1. LocalBroadcastManagerを使ってデータ送信

kotlin

この記事のサンプルは、Javaとkotlinの両方で実装します。

kotlinをAndroid Studio3未満で利用するには、{project_folder}/build.gradleに次のように記述します。

{project_folder}/build.gradle
buildscript {
    ext.kotlin_version = '1.1.1'
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:2.3.3'
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 
    }
}
          

kotlinのversionを最新でない1.1.1に指定しているのは、jackOptions(Java8)に対応するためです。

続いてapp/build.gradleにも追記します。

{project_folder}/app/build.gradle
apply plugin: 'kotlin-android'

dependencies {
    // kotlin
    compile "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
}
repositories { 
    mavenCentral()
}
          

以上で準備は完了です。

実装

画面を管理するLocalBroadcastManagerActivityにBodyとFooterのFragmentをコミットします。FooterのFragmentは、BodyのFragmentから呼び出すDialogからBroadcastを受け取ります。

{project_folder}/app/src/main/LocalBroadcastManagerFooterFragment.java
public class LocalBroadcastManagerFooterFragment extends Fragment implements LifecycleRegistryOwner {

    private LifecycleRegistry lifecycleRegistry = new LifecycleRegistry(this);

    @Override
    public LifecycleRegistry getLifecycle() {
        return lifecycleRegistry;
    }

    private View view;
    private TextView msg;

    /**
     * Use this factory method to create a new instance of
     * this fragment using the provided parameters.
     *
     * @return A new instance of fragment LocalBroadcastManagerChildFragment.
     */
    public static LocalBroadcastManagerFooterFragment newInstance() {
        return new LocalBroadcastManagerFooterFragment();
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        view = inflater.inflate(R.layout.fragment_local_broadcast_manager_footer, container, false);
        msg = view.findViewById(R.id.msg);
        return view;
    }

    @Override
    public void onResume() {
        super.onResume();
        LocalBroadcastManager.getInstance(getActivity().getApplicationContext()).registerReceiver(
                messageReceiver, new IntentFilter(Constants.ACTION_NAME));
        super.onResume();
    }

    @Override
    public void onPause() {
        LocalBroadcastManager.getInstance(getActivity().getApplicationContext()).unregisterReceiver(messageReceiver);
        super.onPause();
    }

    private BroadcastReceiver messageReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            if (getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.RESUMED)) {
                if (intent.getAction().equals(Constants.ACTION_NAME)) {
                    String message = intent.getStringExtra(Constants.EXTRA_NAME);
                    int bgcolor = intent.getIntExtra(Constants.EXTRA_BG_COLOR, 0);
                    msg.setText(message);
                    view.setBackgroundColor(ContextCompat.getColor(getActivity().getApplicationContext(), bgcolor));
                }
            }
        }
    };
}
          

■ 実装の解説

1. BroadcastReceiver実装

BroadcastReceiverはブロードキャストされた暗黙的Intentに応答するための仕組みです。BroadcastReceiverクラスを実体化し、intentを受け取るonReceiveメソッドをオーバーライドします。

private BroadcastReceiver messageReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        if (getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.RESUMED)) {
            if (intent.getAction().equals(Constants.ACTION_NAME)) {
                String message = intent.getStringExtra(Constants.EXTRA_NAME);
                int bgcolor = intent.getIntExtra(Constants.EXTRA_BG_COLOR, 0);
                msg.setText(message);
                view.setBackgroundColor(ContextCompat.getColor(getActivity().getApplicationContext(), bgcolor));
            }
        }
    }
};
        

getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.RESUMED)はライフサイクルの状態を確認しています。

if (getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.RESUMED)) {
    // something
}
        

onResumeメソッドでBroadcastReceiverの登録を行うので、onResumeが実行済みであることを確認しています。

if (intent.getAction().equals(Constants.ACTION_NAME)) {
    String message = intent.getStringExtra(Constants.EXTRA_NAME);
    int bgcolor = intent.getIntExtra(Constants.EXTRA_BG_COLOR, 0);
    msg.setText(message);
    view.setBackgroundColor(ContextCompat.getColor(getActivity().getApplicationContext(), bgcolor));
}
        

Intent#getAction()メソッドでアクション名を取得します。アクション名は送信側でも利用するので、定数化した方がよいでしょう。
送信したBroadcastとアクション名が一致した場合の処理を記述します。

2. LocalBroadcastManager登録

LocalBroadcastManagerの登録は、onResumeメソッドで行うのが一般的です。

public void onResume() {
    super.onResume();
    LocalBroadcastManager.getInstance(getActivity().getApplicationContext()).registerReceiver(
            messageReceiver, new IntentFilter(Constants.ACTION_NAME));
    super.onResume();
}
        

LocalBroadcastManager#getInstanceメソッドはシングルトンです。registerReceiverメソッドはHashMapでBroadcastReceiverを登録するので、必ず対になるonPauseメソッドで解除処理を実装する必要があります。

3. LocalBroadcastManager解除

LocalBroadcastManagerの解除は、onPauseメソッドで行うのが一般的です。

@Override
public void onPause() {
    LocalBroadcastManager.getInstance(getActivity().getApplicationContext()).unregisterReceiver(messageReceiver);
    super.onPause();
}
        

registerReceiverメソッドで登録したBroadcastReceiverを削除します。

続いて暗黙的Intentをブロードキャストする処理を実装します。BottomSheetDialogFragmentを継承したLocalBroadcastManagerBottomSheetDialogFragmentを実装します。

{project_folder}/app/src/main/LocalBroadcastManagerBottomSheetDialogFragment.java
public class LocalBroadcastManagerBottomSheetDialogFragment extends BottomSheetDialogFragment implements LifecycleRegistryOwner {

    private LifecycleRegistry lifecycleRegistry = new LifecycleRegistry(this);

    @Override
    public LifecycleRegistry getLifecycle() {
        return lifecycleRegistry;
    }

    /**
     * Use this factory method to create a new instance of
     * this fragment using the provided parameters.
     *
     * @return A new instance of fragment LocalBroadcastManagerBottomSheetDialogFragment.
     */
    public static LocalBroadcastManagerBottomSheetDialogFragment newInstance() {
        return new LocalBroadcastManagerBottomSheetDialogFragment();
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        final View view = inflater.inflate(R.layout.fragment_local_broadcast_manager_bottom_sheet_dialog, container, false);
        Button btnSend = view.findViewById(R.id.btn_send);
        btnSend.setOnClickListener(v -> onClickBtnSend());
        return view;
    }

    private void onClickBtnSend() {
        if (getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.CREATED)) {
            Intent intent = new Intent(Constants.ACTION_NAME);
            intent.putExtra(Constants.EXTRA_NAME, "Hello LocalBroadcastManager");
            intent.putExtra(Constants.EXTRA_BG_COLOR, R.color.colorBg);
            LocalBroadcastManager.getInstance(getActivity().getApplicationContext()).sendBroadcast(intent);
            dismiss();
        }
    }
}
          

■ 実装の解説

1. Broadcast送信

Broadcastの送信を実装します。BroadcastReceiverのonReceiveメソッドに暗黙的Intentを送信します。

if (getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.CREATED)) {
    Intent intent = new Intent(Constants.ACTION_NAME);
    intent.putExtra(Constants.EXTRA_NAME, "Hello LocalBroadcastManager");
    intent.putExtra(Constants.EXTRA_BG_COLOR, R.color.colorBg);
    LocalBroadcastManager.getInstance(getActivity().getApplicationContext()).sendBroadcast(intent);
    dismiss();
}
        

getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.CREATED)はライフサイクルの状態を確認しています。

if (getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.CREATED)) {
    // something
}
        

Acitivtyが生成されていることを確認するために、onCreateが実行済みであることを確認しています。

Intent intent = new Intent(Constants.ACTION_NAME);
intent.putExtra(Constants.EXTRA_NAME, "Hello LocalBroadcastManager");
intent.putExtra(Constants.EXTRA_BG_COLOR, R.color.colorBg);
LocalBroadcastManager.getInstance(getActivity().getApplicationContext()).sendBroadcast(intent);
        

Intentを生成し、アクション名とデータを設定します。アクション名は受信側と同じになります。
sendBroadcastメソッドでintentを送信します。

以上で実装は完了です。

ビルド

上記のコードの動作を確認してみましょう。

fig2. LocalBroadcastManager(kotlin)

intentがブロードキャストされて、fotterFragmentの背景色と文字列が変更されました。

結論

LocalBroadcastManagerは便利な機能です。しかし、Pub/Subパターンを何回も使う必要があるアプリの場合は、EventBusを導入した方が実装しやすいと思います。
アプリの仕様によって決めることをお勧めします。

関連記事

タグ検索で調べてみよう

Android8.0 kotlin1.1.1