Tips

Androidアプリの4大要素 BroadcastReceiver【Android Tips】

1
Androidアプリの4大要素とインテント
BroadcastReceiver
この節では、Androidアプリケーションの4大要素のうちの1つ”BroadcastReceiver”を紹介します。 BroadcastReceiverはブロードキャストされたIntentに応答するための仕組みです。また合わせて、“IntentFilter”というIntentを識別するための仕組みについても紹介します。
IntentFilter概要
暗黙的Intentには、受け取り手となるオブジェクトを識別するための情報を”基本情報”や”補足情報”という形で設定していました。

この暗黙的Intentの受け取り手となりうるクラスは、Activity・Service・BroadcastReceiverがあります。
これらのクラスを暗黙的Intentが発せられたときに、起動するようにしなくてはいけません。
しかし、それぞれのオブジェクトがすべてのIntentに反応していてはリソースがいくらあってもたりません。どのようなIntentの場合に起動したいのか、そのIntentを識別するための仕組みが必要です。

それが、IntentFilterです。

暗黙的なIntentは発せられると受け取り手となるうるクラスをAndroidシステムが検索してくれるわけですが、IntentFilterはこの受け取り手を探すための手掛かりになる仕組みといってもよいでしょう。

インテントフィルターの定義方法には、IntentFilterクラスを使ってプログラムで定義する方法とマニフェストファイルにタグを定義する方法の2種類があります。

まずは、IntentFilterクラスをつかって定義する方法です。

IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(Intent.ACTION_BATTERY_CHANGED);

IntentFilterクラスをインスタンス化し、addAction()メソッドをつかいアクションの設定を行っています。

つまり、“ACTION_BATTERY_CHANGE“が設定されたIntentのみに反応することができるということになります。
あとは、作成したIntentFilterオブジェクトをActivityやService、BroadcastReceiverに紐付けてあげれば完了です。

この例では、アクションのみを設定していますが、もちろんデータやタイプなどを設定することも可能です。

つぎに、マニフェストファイルを使って定義する方法です。

<activity android:name=".MainActivity"
          android:label="@string/app_name">
   <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>

activityタグの中にintent-filterタグを定義し、さらにその子要素としてactionタグやcategoryタグを定義しています。
つまり、MainActivityはアクションに“ACTION_MAIN“、カテゴリーに”CATEGORY_LAUNCHER”が設定されたIntentのみに反応することができるということになります。

CATEGORY_LAUNCHER(文字列としては”android.intent.category.LAUNCHER”が定義されています)はランチャー(ホーム画面にあるアプリケーションの起動アイコン)に表示されるアプリケーションであることを表します。

注意点としては、マニフェストファイルにIntentFilterを設定する場合は、定数名(ACTION_MAINやCATEGORY_LAUNCHERなど)ではなく、実際の文字列(”android.intent.action.MAIN”や”android.intent.category.LAUNCHER”など)を設定するということです。

BroadcastReceiver概要
BroadcastReceiverはブロードキャストされた暗黙的Intentに応答するための仕組みです。
システムの起動完了、バッテリ残量の低下、日付時刻の設定など、Androidシステムで非同期に発生するブロードキャストインテントを受け取ることが可能なコンポーネントとなります。
もちろん、Androidシステムが発するIntent以外にも、独自にアクション等を定義したIntentを受け取ることも可能です。

BroadcastReceiverは極めてシンプルな構造で、ライフサイクルはインテントを受け取った時にただ1つのメソッドを実行するのみです。ここにBroadcastReceiverが行うべき処理を記述することになります。

独自のBroadcastReceiverクラスを作成するには、BroadcastReceiverクラスを継承し、onReceive()メソッドを実装します。インテントを受け取った時はこのonReceive()が実行され、引数のintentから受け取ったIntent情報を取得することができます。

public class FooReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        // インテントを受け取ったときの処理を記述
    }
}

onReceive()内の処理は素早く終えるようにするか、あるいは、別スレッド上で処理を行うべきです。
これは、BroadcastReceiverは画面描画を行っているUIスレッドと同一のスレッドで実行され、
時間のかかる処理をonReceive()内で行ってしまうと、UIがフリーズしているかのような状態に陥ってしまうためです。

 

たとえば、以下のようにThread.sleep()メソッドで時間のかかる処理をシュミレーションしてみます。

public class FooReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        try{
            Thread.sleep(30 * 1000); // 30秒処理を止める
        }catch(InterruptedException e){
            e.printStackTrace();
        }
    }
}

しばらくすると、以下のような警告が発生しAndroidシステムはアプリケーションを強制終了しようとします。



作成したBroadcastReceiverクラスを有効にするには、反応するIntentの種類を特定するためのIntentFilterをBroadcastReceiverオブジェクトと紐づけなくてはなりません。

この紐づけは、Contextクラスに定義されているregisterReceiver()メソッドを実行するか、マニフェストファイルのreceiverタグ内部にintent-filterタグを定義することで行います。

 

以下にregisterReceiver()メソッドのシグネチャを示します。

public Intent registerReceiver (BroadcastReceiver receiver, IntentFilter filter)

第1引数がBroadcastReceiverクラスのオブジェクト(先ほどの例ではFooReceiverクラスのオブジェクト)を、第2引数にはIntentFilterオブジェクトを指定します。


一般的に、ActivityやService内でブロードキャストされたIntentを受け取りたい場合は、BroadcastReceiverを継承したクラスをインナークラスとして定義し、registerReceiver()メソッドを実行することが多いようです。
これは、ActivityやServiceのライフサイクルに応じてBroadcastReceiverのON/OFFを切り替えられるという利点があるからでしょう。OFFにする場合は、同じくContextクラスに定義されているunregisterReceiver()メソッドを使用します。

暗黙的なインテントを利用したBMI計算アプリ
明示的なインテントで作成したBMI計算アプリに手を加え、暗黙的なインテントにも対応してみましょう。


“暗黙的インテント”ボタンをクリックするとIntentがブロードキャストされます。
ブロードキャストされたIntentは、新たに作成するBroadcastReceiverが受け取り、入力された身長と体重から算出したBMI値をToastで出力します。
ボタンをクリックしたときに発せられるIntentには独自定義したアクション“ACTION_CALC_BMI”が設定されており、同じく新たに作成するBroadcastReceiverではこのアクションのみに反応する作りとします。


まずは、MainActivityのレイアウトXMLファイルからです。

“res/layout/activity_main.xml“

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >
 
    <TextView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="身長(cm)" />
 
    <EditText
        android:id="@+id/editText1"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content" />
 
    <TextView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="体重(kg)" />
 
    <EditText
        android:id="@+id/editText2"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content" />
 
    <Button
        android:id="@+id/button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="明示的インテント" />
 
    <Button
        android:id="@+id/button2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="暗黙的インテント" />
</LinearLayout>

追加となっているのは、33から37行目だけです。
新しくボタンを追加し、android:idにbutton2を、表示する文字に“暗黙的インテント”を設定しています。


次にMainActivityのソースコードです。
“src/com.example.intent/MainActivity.java”

package com.example.intent;
// 中略 
public class MainActivity extends Activity {
    public static final String ACTION_CALC_BMI = "com.example.intent.action.CALC_BMI";
 
    private EditText textHeight;
    private EditText textWeight;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
 
        textHeight = (EditText)findViewById(R.id.editText1);
        textWeight = (EditText)findViewById(R.id.editText2);
 
        Button button1 = (Button)findViewById(R.id.button1);
        button1.setOnClickListener(new OnClickListener() {
 
            public void onClick(View v) {
                Intent intent = new Intent(MainActivity.this, ResultActivity.class);
 
                intent.putExtra("HEIGHT", Integer.parseInt(textHeight.getText().toString()));
                intent.putExtra("WEIGHT", Integer.parseInt(textWeight.getText().toString()));
 
                startActivity(intent);
            }
        });

        Button button2 = (Button)findViewById(R.id.button2);
        button2.setOnClickListener(new OnClickListener() {
 
            public void onClick(View v) {
                Intent intent = new Intent(ACTION_CALC_BMI);
 
                intent.putExtra("HEIGHT", Integer.parseInt(textHeight.getText().toString()));
                intent.putExtra("WEIGHT", Integer.parseInt(textWeight.getText().toString()));
 
                sendBroadcast(intent);
            }
        });
    }
}

4行目にACTION_CALC_BMIという定数を追加しました。これはアクションとして使われます。
また、30から41行目を追加しました。新しく追加する“暗黙的インテント”ボタンに関する処理を記述しています。
よく見ると、button1とbutton2のonClick()メソッド内の処理がほとんど同じであることがわかります。

生成したIntentは、sendBroadcast()メソッドを使いブロードキャストしています。


次に、BroadcastReceiverを継承したCalcBmiReceiverクラスを新規作成します。

“src/com.example.intent/CalcBmiReceiver.java“

package com.example.intent;
// 中略
public class CalcBmiReceiver extends BroadcastReceiver {
 
    @Override
    public void onReceive(Context context, Intent intent) {
        int height = intent.getIntExtra("HEIGHT", 0);
        int weight = intent.getIntExtra("WEIGHT", 0);
 
        int bmi = 10000 * weight / height / height;
 
        String text = String.format("あなたのBMIは%dです", bmi);
        Toast.makeText(context, text, Toast.LENGTH_SHORT).show();
    }
}

引数のintentはこのBroadcastReceiverが受け取ったIntentが入ってきました。
そのIntentからHEIGHTおよびWEIGHTを取り出しBMI計算を行った後、Toastで出力しています。

Intentから付加情報を取り出す方法については、明示的Intentと同じ手法ですね。


最後に、マニフェストファイルです。
“Intent/AndroidManifest.xml“

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.intent"
    android:versionCode="1"
    android:versionName="1.0" >
 
    <uses-sdk
        android:minSdkVersion="7"
        android:targetSdkVersion="15" />
 
    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name=".MainActivity"
            android:label="@string/title_activity_main" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
 
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name=".ResultActivity"></activity>
        <receiver android:name=".CalcBmiReceiver">
            <intent-filter>
                <action android:name="com.example.intent.action.CALC_BMI" />
            </intent-filter>
        </receiver>
    </application>
 
</manifest>

追加している箇所は24から28行目のみです。
新しくCalcBmiReceiverクラスを追加したため、Activityを追加した時と同様にマニフェストファイルに追記します。
receiverタグの子要素にintent-filterタグ、およびその子要素としてactionタグが定義されているところにも注目してください。

Androidアプリ開発の必須知識!JAVAプログラミングを学べる連載リンク

はじめてのJAVA 連載

Recent News

Recent Tips

Tag Search