🔄

メモリリークや不要なリソース消費を避ける、Broadcast Receiverのライフサイクル管理

2024/12/02に公開

はじめに

Android アプリでは、システムや他のアプリからの情報を受け取るために Broadcast Receiver が使われます。しかし、Broadcast Receiver のライフサイクルを適切に管理しないと、メモリリークや不要なリソース消費につながる可能性があります。

この記事では、Broadcast Receiver の基本とandroidx.lifecycleライブラリを用いたライフサイクル管理について、コードスニペットを交えながら解説します。

Broadcast Receiver とは

https://developer.android.com/develop/background-work/background-tasks/broadcasts

Broadcast Receiver は、Android システムやアプリ間で送受信されるブロードキャストメッセージを受け取るためのコンポーネントです。
主に次の 2 種類のブロードキャストがあります。

システムブロードキャスト

例: android.intent.action.BOOT_COMPLETED(デバイス起動完了時の通知)

カスタムブロードキャスト

例: アプリ内で定義した独自のメッセージ

Broadcast Receiver の登録方法

Broadcast Receiver は 2 つの方法で登録できます。次のセクションで、順に見ていきましょう。

  1. AndroidManifest.xml で静的登録
  2. コードで動的登録

1. 静的登録

アプリ起動中でなくてもブロードキャストを受け取ることができます。

<receiver android:name=".MyBroadcastReceiver">
    <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED"/>
    </intent-filter>
</receiver>

2. 動的登録

アクティビティやサービスが実行中の場合にのみブロードキャストを受け取ります。

val myReceiver = MyBroadcastReceiver()
val intentFilter = IntentFilter("com.example.CUSTOM_ACTION")
registerReceiver(myReceiver, intentFilter)

動的登録を使用する場合、ライフサイクルに応じた適切な登録と解除が必要です。これを怠ると、メモリリークやアプリのクラッシュにつながります。次のセクションで、少し深ぼって解説します。

ライフサイクル管理

Broadcast Receiver の登録解除を忘れると、メモリリークが発生します。動的登録の場合、特に意識すべき点があります。
良い例と望ましくない例を比較しながら見ていきましょう。

良い例: androidx.lifecycleを活用したライフサイクル管理

後述するミスは、登録解除を手動で行うことで引き起こされます。ReceiverLifecycleObserverを活用し、自動化しましょう。

https://developer.android.com/jetpack/androidx/releases/lifecycle

class MyBroadcastReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
        // ブロードキャストを受信したときの処理
        if (intent.action == "com.example.CUSTOM_ACTION") {
            Log.d("MyBroadcastReceiver", "Custom action received")
        }
    }
}

class ReceiverLifecycleObserver(
    private val receiver: BroadcastReceiver,
    private val filter: IntentFilter
) : DefaultLifecycleObserver {

    override fun onStart(owner: LifecycleOwner) {
        owner.context.registerReceiver(receiver, filter)
    }

    override fun onStop(owner: LifecycleOwner) {
        owner.context.unregisterReceiver(receiver)
    }
}

class MainActivity : AppCompatActivity() {
    private lateinit var myReceiver: MyBroadcastReceiver

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        myReceiver = MyBroadcastReceiver()
        val intentFilter = IntentFilter("com.example.CUSTOM_ACTION")

        lifecycle.addObserver(ReceiverLifecycleObserver(myReceiver, intentFilter))
    }
}

よくあるミス:メモリリークや不要なリソース消費につながるコード

1: onDestroy で `Receiver を解除し忘れる

class MainActivity : AppCompatActivity() {
    private lateinit var myReceiver: MyBroadcastReceiver

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // Broadcast Receiverを動的に登録
        myReceiver = MyBroadcastReceiver()
        val intentFilter = IntentFilter("com.example.CUSTOM_ACTION")
        registerReceiver(myReceiver, intentFilter)
    }

    // unregisterReceiverが呼ばれない
}

2: 同じ Receiver を複数回登録

class MainActivity : AppCompatActivity() {
    private lateinit var myReceiver: MyBroadcastReceiver

    override fun onStart() {
        super.onStart()

        // 毎回新しいReceiverを登録してしまう
        myReceiver = MyBroadcastReceiver()
        val intentFilter = IntentFilter("com.example.CUSTOM_ACTION")
        registerReceiver(myReceiver, intentFilter)
    }

    override fun onStop() {
        super.onStop()
        // 最後の1つしか解除しないため、以前のReceiverが残る
        unregisterReceiver(myReceiver)
    }
}

良い例

1: onDestroy で Receiver を自動的に解除

class MainActivity : AppCompatActivity() {

    private lateinit var myReceiver: MyBroadcastReceiver

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        myReceiver = MyBroadcastReceiver()
        val intentFilter = IntentFilter("com.example.CUSTOM_ACTION")

        // ReceiverLifecycleObserverを登録
        lifecycle.addObserver(ReceiverLifecycleObserver(this, myReceiver, intentFilter))
    }
}

2: 複数のアクションを 1 つの Receiver で管理

複数のアクションを個別の Receiver で管理するのではなく、1 つの Receiver でまとめて処理します。

class UnifiedBroadcastReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
        when (intent.action) {
            "com.example.ACTION_ONE" -> Log.d("UnifiedReceiver", "Action One received")
            "com.example.ACTION_TWO" -> Log.d("UnifiedReceiver", "Action Two received")
        }
    }
}

class MainActivity : AppCompatActivity() {
    private lateinit var unifiedReceiver: UnifiedBroadcastReceiver

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        unifiedReceiver = UnifiedBroadcastReceiver()
        val intentFilter = IntentFilter().apply {
            addAction("com.example.ACTION_ONE")
            addAction("com.example.ACTION_TWO")
        }

        lifecycle.addObserver(ReceiverLifecycleObserver(this, unifiedReceiver, intentFilter))
    }
}

おわりに

Broadcast Receiver を使用する際には、ライフサイクルを意識して適切に登録・解除を行うことが重要です。特に、例外処理や状態管理を行うことで、予期しない動作やメモリリークを防ぎます。androidx.lifecycle ライブラリを適切に活用し、この処理を自動化すると、そのような心配は無用になります。

公式の実装を参考に、安全かつ効率的な Broadcast Receiver の活用を目指しましょう。

浅学のため、この記事には粗があるかと思います。お気づきの点があれば、ぜひコメントや SNS で教えていただけると幸いです! 🙏✨

Discussion