ブロードキャストとは
公式ドキュメントには以下のように書かれています。
たとえば、システムの起動、デバイスの充電の開始など、さまざまなシステム イベントが発生したときに、Android システムがブロードキャストを送信します。
このようにブロードキャストを用いることで、Androidシステムの情報を取得することができるようになります。
システム情報を取得する際の注意点
プライバシーの問題からシステム情報を取得する方法は刻一刻と変化しています。
特にAndroidのアップデートの際は、取得する方法が変わっていないか注意しましょう。
OSのバージョンによる制限
OSのバージョンによって取得できる情報が制限されるようになっています。
Android9以降
Android9以降のデバイスでは、プライバシーの観点から以下の情報が取得できなくなっています。
- 位置情報や個人を特定できるデータを受信しなくなっています。
- Wi-Fi からのシステム ブロードキャストに SSID、BSSID、接続情報、スキャン結果は含まれません。
Android8以降
Android8以降のデバイスでは、ブロードキャストのレシーバーを宣言する方法が制限されています。マニフェストを使用するブロードキャストの記述は避けましょう。(ただし例外もあります)
システムはマニフェストで宣言されたレシーバーに対して追加の制限を課します。
実装
それでは実装方法を説明します。
上でも説明しましたが、マニフェストを使用するブロードキャストの記述ができなくなっているため、コンテキストを用いた方法を紹介します。
受信方法
レシーバーの作成
まず、BroadcastReceiver
を継承したファイルを作成し、onReceive
をoverride
します。
その後、onReceive
内で受信したいイベントを定義し、イベントが起こったときにする動作を書きます。
class TimeZoneBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
// 受信したいイベントを定義
if (intent.action == Intent.ACTION_TIMEZONE_CHANGED) {
// イベントが起こったときに発生する動作を定義する
// 動作が完了しない場合があるので注意(下で説明します)
}
}
}
コールバックを用いて以下のように書くこともできます。
private val br = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
// 受信したいイベントを定義
if (intent.action == Intent.ACTION_TIMEZONE_CHANGED) {
// イベントが起こったときに発生する動作を定義する
// 動作が完了しない場合があるので注意(下で説明します)
}
}
}
レシーバーを登録
次に、定義したレシーバーを登録します。以下の3つを行います。
- 登録、解除を定義する
- どのイベントを受け取るか
- いつからいつまで観測するのか
まず、登録、解除を定義します。
また、登録の際にレシーバーがどのイベントを受信するのかを定義します。
fun register(context: Context) {
if (!registered) {
val filter = IntentFilter()
// どのイベントを受け取るのかを定義
filter.addAction(Intent.ACTION_TIMEZONE_CHANGED)
context.registerReceiver(this, filter)
registered = true
}
}
fun unregister(context: Context) {
if (registered) {
context.unregisterReceiver(this)
registered = false
}
}
次に、いつから観測するのかを教えてあげます。
アプリが起動してから終了するまで観測したい場合は、Activityのようなアプリが終了するまで生存しているファイルのonCreateで登録し、onDestroyで登録解除を行なってあげましょう。
override fun onCreate(savedInstanceState: Bundle?) {
val receiver = TimeZoneBroadcastReceiver()
receiver.register(context)
}
override fun onDestroy() {
receiver.unregister(context)
}
イベントが起こったときにする動作について
イベントが起こって動作を行う際、OSが優先度の低いタスクだと判断し、動作が強制終了される可能性があります。
公式ドキュメントにも以下のように記載されています。
onReceive() からコードが返されると、システムは対象のプロセスを優先度の低いプロセスとみなし、重要度の高い他のプロセスでリソースを利用できるようにするため、このプロセスを強制終了する可能性があります。
それゆえ、OSに処理時間が必要であることを伝える必要があります。
OSに処理時間を伝える方法
さまざまな方法がありますが、ここでは2つ紹介します。
goAsync()
- flowの
awaitClose
を用いる
goAsync()
公式ドキュメントで紹介されている方法です。
以下のように処理時間を伝えてあげる必要があります。
- onReceive内で
goAsync
のインスタンスを作成する - 行いたい処理をAsyncTaskを継承したクラス内で定義する
-
execute()
で開始する - onPostExecute内の
finish()
で終了する
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 {
// 行いたい処理を書く
}
override fun onPostExecute(result: String?) {
super.onPostExecute(result)
// 終了を伝える
pendingResult.finish()
}
}
flowのawaitClose
を用いる
flowを用いている場合はこちらを使うとスッキリと書くことができます。
nowinandroidでも使われている方法となっています。
override val isOnline: Flow<Boolean> = callbackFlow {
val callback = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
// 受信したいイベントを定義
if (intent.action == Intent.ACTION_TIMEZONE_CHANGED) {
// イベントが起こったときに発生する動作を定義する
channel.trySend(true)
}
}
channel.trySend(connectivityManager.isCurrentlyConnected())
// onReceive内の処理がキャンセルされないように、受信するまではスレッドをブロックする
awaitClose()
}
awaitCloseはスタゼロさんのブログ記事によると次のような機能を持っているそうです。callbackFlow
の中で呼び出すことができ、callbackFlowが終了するまで他の処理をブロックする役割を持っています。
Flow Channelがクローズまたはキャンセルされるまで処理をブロックします
flowの導入を検討している際はこちらの方法を用いると便利に使うことができます。
まとめ
- ブロードキャストによってAndroidのシステム情報を取得できる
- AndroidのOSによって取得できる情報は変わる
- レシーバーを定義、登録することで用いることができる
- OSに処理時間を伝える必要がある
最後に
最後まで読んでいただきありがとうございました。よければtwitterのフォローよろしくお願いします。
⚡️ "たかっしーの開発日記"https://t.co/aGE0WGERzU
— たかっしー(開発垢)@東北放浪中 (@takashiho_2) November 15, 2022