Android - Broadcast Receiver登録およびイベントの受信方法

AndroidはBroadcastを介してシステムとアプリやアプリのアプリのイベントを送受信することができます。

publish-subscribeデザインパターンのように一方では、イベントを提供するだけで一方では、イベントを受けるだけです。

たとえば、Broacastを介してアプリは、システムからLocalが変更されたり、Appがインストールされているなど、さまざまなイベントを受け取ることができます。また、エプドゥル同士あらかじめ事前に定義されたActionを送受信することができます。

アプリBroadcastを受信する前にReceiverを登録する必要があります。 BroadcastReceiverオブジェクトを生成し、これをManifestに登録したり、 registerReceiver()に登録する必要があります。 そして sendBroadcast()を利用して、アプリでは、ブロードキャストを送信することができます。

Broadcastイベントを送受信する方法について詳しく説明します。

静的BroadcastReceiver登録

静的BroadcastReceiverはアプリをインストールする時点でReceiverを登録することをいいます。 DeveloperはManifest-declared receivers用語で表現します。

  • 利点としては、アプリが実行されなくても、レシーバが常に登録されているので、すべてのイベントを受け取ることができます
  • 代わりにレシーバを解除することができません。 (レシーバComponentの状態をdisabledに変更すると、動作しなく作成することはできます。

静的レシーバを登録する方法は次のとおりです。

  1. BroadcastReceiverクラスを継承したReceiverクラスを実装します。
  2. 実装したクラスをAndroidManifest.xmlに宣言します。

レシーバオブジェクトの実装

次のようにBroadcastReceiverを継承したクラスを実装します。

class MyReceiver : BroadcastReceiver() {

    override fun onReceive(context: Context?, intent: Intent?) {
        Log.d("MyReceiver", "Intent: $intent")
    }
}

Manifestにレシーバ宣言

AndroidManifest.xmlに <receiver />属性に上記の実装したレシーバを宣言します。 重要なことは、受信されるインテントの情報をintelt-filter内部に宣言する必要があります。

<receiver android:name=".MyReceiver">
    <intent-filter>
        <action android:name="android.intent.action.LOCALE_CHANGED"/>
    </intent-filter>
</receiver>

上記のコードでは LOCALE_CHANGEDインテントをブロードキャストで受けられるように宣言しました。このインテントは、システムの言語が変更されるときに送出されるインテントです。

アプリをインストールし、システムの言語を変更してみると次のようにブロードキャストを受け取ることをログで確認することができます。

06-06 21:43:26.097  6607  6607 D MyReceiver: Intent: Intent { act=android.intent.action.LOCALE_CHANGED flg=0x11200010 cmp=com.codechacha.broadcastreceiver/.MyReceiver }

アプリで定義されてブロードキャスト受信

android.intent.action~で始まるActionは、ほとんどのシステムでのみ送信するイベントです。 新しいActionを定義して、私のアプリ同士送受信することもできます。

たとえば、次のように com.codechacha.action.testというActionにレシーバを定義すると、私のアプリでは、Actionにブロードキャストを送信したとき受け取ることができます。

<receiver android:name=".MyReceiver">
    <intent-filter>
        <action android:name="com.codechacha.action.test"/>
    </intent-filter>
</receiver>

二つ以上のブロードキャストの受信

1つのレシーバで二つ以上のActionのイベントを受信する次のようにintent-filterにActionを定義します。

<receiver android:name=".MyReceiver">
    <intent-filter>
        <action android:name="android.intent.action.LOCALE_CHANGED"/>
        <action android:name="com.codechacha.action.test"/>
    </intent-filter>
</receiver>

または、次のようにintent-filterを二つ定義してもされます。

<receiver android:name=".MyReceiver">
    <intent-filter>
        <action android:name="android.intent.action.LOCALE_CHANGED"/>
    </intent-filter>
    <intent-filter>
        <action android:name="com.codechacha.action.test"/>
    </intent-filter>
</receiver>

動的BroadcastReceiver登録

動的BroadcastReceiverはアプリの実行中にコードに登録されている受信機を指します。 DeveloperはContext-registered receiversと表現します。

  • 利点は、好きな時間に、お好きな状況では、レシーバを登録して終了することができるということです
  • 欠点は、アプリが実行されない場合は受信機を登録することができないということです

動的レシーバを登録、失効する方法は次のとおりです。

  1. まず、レシーバオブジェクトを生成します。
  2. registerReceiver()メソッドでレシーバを登録します。
  3. unregisterReceiver()メソッドでレシーバを終了します。

レシーバオブジェクトの作成

次のように匿名クラスでBroadcastReceiverを継承するクラスのオブジェクトを作成することができます。

private val br: BroadcastReceiver = object:BroadcastReceiver() {
    override fun onReceive(context: Context?, intent: Intent?) {
        Log.d("Receiver", "Intent: $intent")
    }
}

または、次のようにクラスを定義し、そのクラスをオブジェクトとして生成する方法もあります。

private val br = MyBroadcastReceiver()

class MyBroadcastReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context?, intent: Intent?) {
        Log.d("MyBroadcastReceiver", "Intent: $intent")
    }
}

レシーバオブジェクトを作成する方法は重要ではありません。履き方を使用します。

レシーバ登録と解除

次のように registerReceiver()でレシーバを登録することができます。引数としてレシーバオブジェクトと、IntentFilterオブジェクトを渡してくれるします。 IntentFilterはブロードキャストで受けたいインテント情報が定義されています。

private val br = MyBroadcastReceiver()

override fun onCreate(savedInstanceState: Bundle?) {
    // ...
    val filter = IntentFilter(Intent.ACTION_LOCALE_CHANGED)
    registerReceiver(br, filter)
}

逆に unregisterReceiver()で登録されたレシーバを終了することができます。引数として終了するレシーバオブジェクトを渡す必要があります。

override fun onDestroy() {
    // ...
    unregisterReceiver(br)
}

Context(Activity)に基づいてレシーバを登録するので、Context(Activity)が消滅すると、レシーバも終了します。

したがってregisterとunregisterは、次の時点でくれることでしょう。

  • onCreate()でレシーバを登録すると onDestroy()で終了することをお勧め
  • onResume()でレシーバを登録すると onPause()で終了することをお勧め

動的にレシーバを登録して言語を変更すると、次のようにブロードキャストを受信することをログで確認することができます。

06-06 22:36:43.676  3732  3732 D Receiver: Intent: Intent { act=android.intent.action.LOCALE_CHANGED flg=0x11200010 }

Receiverにブロードキャストの転送

ブロードキャストを転送するとき二つの方法があります。

  • sendBroadcast(Intent):レシーバに順序でブロードキャストを転送します。レシーバにほぼ同時に配信されるので、伝達速度が速い
  • sendOrderedBroadcast(Intent, String):一度に一つのレシーバーにブロードキャストを転送します。 IntentFilterの android:priorityの値を参考にして順序を定めて、この順に転送します。以前に送信したレシーバがすべての処理を終了する次のレシーバにイベントを送出するため、伝達速度が遅くなります。以前に処理したレシーバの結果を読み取ることができ、次のレシーバに転送がないように防ぐことができます

単純にイベントを転送することが目的であれば、 sendBroadcast()を使用してください。このメソッドは、 Activity(Context)に実装されています。

暗示的な方法でブロードキャストの転送

暗示的な方法という意味ではFrameworkがインテントに関連するすべてのレシーバを探して、その受信機にすべて伝えてくれることを意味します。

以下のように渡すインテントを作成し、 sendBroadcast()の引数として渡すと、このインテントを受信するすべてのレシーバに渡されます。

val intent = Intent("com.codechacha.action.test")
sendBroadcast(intent)

明示的な方法でブロードキャストの転送

明示的な方法とは意味はブロードキャストを転送するアプリで指定されたアプリのレシーバのみイベントを配信することを意味します。

次のようにIntentに送りたいアプリのpackage nameを設定すると、そのアプリのレシーバのみ渡されます。

val intent = Intent("com.codechacha.action.test")
intent.setPackage("com.codechacha.broadcastreceiver")
sendBroadcast(intent)

暗黙的(Implicit)ブロードキャスト制限ポリシー

暗示的な方法でブロードキャストを送信するアプリに登録されたすべてのレシーバに渡されます。そのレシーバのアプリが実行されていない場合のアプリのプロセスを実行して転送します。 このような理由から、システムが遅くなったり、スラッシングことができます。

このような問題を解決するためにAndroidはTarget SDK APIが26以上であるアプリを対象に、Manifestに登録されたレシーバは、暗黙的にブロードキャストをできなかっ受けるた。

つまり、 registerReceiver()で登録したレシーバは、暗黙的なブロードキャストを受信することができますがManifestに登録されたレシーバは、イベントを受け取ることができません。 Target SDK APIが26以上であるアプリを対象にするので、Targetを25以下に下げると、このポリシーを回避して、暗黙的なブロードキャストを受信することができます。

幸いなことは、 ACTION_BOOT_COMPLETEDのようないくつかのブロードキャストは、例外を置いてManifesteに登録されたレシーバも受けられるようにしました。 Implicit Broadcast Exceptionsを参照してくださいどのようなActionが例外なのか知ることができます。

Receiverで時間がかかる作業の処理

レシーバでのイベントを受信すると、 onReceive()が呼び出されます。短くなることはただ処理すればよいがかかる作業は、他のスレッドで処理するか、Schedulerで作業を登録して処理する必要があります。

override fun onReceive(context: Context?, intent: Intent?) {
    ...
}

registerReceiver()でレシーバを登録する際にHandlerを引数として渡して、そのHandlerで onReceive()処理されるようにすることができます。 しかし、Manifestに登録されたレシーバは、Handlerを選択することができず、アプリのMain handlerで処理されます。作業が長くなる場合Main threadで処理される他の作業に影響を与えることができます。

このような理由は、次のようにAsyncオブジェクトを作成し、作業を委任して onReceive()が高速に処理されるようにすることができます。

class MyBroadcastReceiver : BroadcastReceiver() {

    override fun onReceive(context: Context, intent: Intent) {
        val pendingResult: PendingResult = goAsync()
        val asyncTask = Task(pendingResult, intent)
        asyncTask.execute()
    }

    private class Task(
            private val pendingResult: PendingResult,
            private val intent: Intent
    ) : AsyncTask<String, Int, String>() {

        override fun doInBackground(vararg params: String?): String {
            val sb = StringBuilder()
            sb.append("Action: ${intent.action}\n")
            sb.append("URI: ${intent.toUri(Intent.URI_INTENT_SCHEME)}\n")
            return toString().also { log ->
                Log.d(TAG, log)
            }
        }

        override fun onPostExecute(result: String?) {
            super.onPostExecute(result)
            // Must call finish() so the BroadcastReceiver can be recycled.
            pendingResult.finish()
        }
    }

上記の例では、 goAsync()pendingResult.finish()などのコードを使用する理由は、他のスレッドで処理中のジョブが進行中であることをシステムに知らせて、実行中のプロセスを終了しないように作るためです。 システムは、資源が不足している場合は、未使用中のプロセスを終了してメモリを回収することができますので、このような方法でプロセスが終了されないようにしてくれるとします。

権限でBroadcast発送/受信制限

自分のアプリ同士を与える受けようとactionを定義したが、いくつかの悪意のあるアプリがこれを知って同じactionをブロードキャストすることができます。 権限を送受信するときのパーミッションを設定して、このようなことを防ぐことができます。

権限を持っているアプリからブロードキャスト受信

次のようにレシーバを宣言するときパーミッションを設定すると、そのパーミッションを持っているエプマンブロードキャストを送信することができます。 もし権限がない場合は、ブロードキャストを送信することができなくなります。

<receiver android:name=".MyReceiver"
        android:permission="android.permission.SEND_SMS">
    <intent-filter>
        <action android:name="com.codechacha.action.test"/>
    </intent-filter>
</receiver>

上記の例では、 com.codechacha.action.testインテントを送信し、アプリがSEND_SMS権限を持っていなければ、このレシーバは、ブロードキャストを受けません。

権限を持っているレシーバのみブロードキャストの転送

次のように sendBroadcast()の引数にパーミッションを渡すと、このパーミッションを持っているアプリのレシーバのみブロードキャストを受信することができます。

Intent intent = new Intent("com.codechacha.action.test");
intent.setPackage("com.codechacha.broadcastreceiver");
sendBroadcast(intent, Manifest.permission.SEND_SMS);

上記の例では、 com.codechacha.broadcastreceiverアプリがSEND_SMS権限を持っていないブロードキャストを受信することができません。

参考

Related Posts

codechachaCopyright ©2019 codechacha