24、BroadcastReceiver注解:动态注册与注销的注解处理

动态广播注册,说白了就是我们在代码里手动调 registerReceiver()unregisterReceiver()。这活儿本身不复杂,但写多了就烦——每个 Activity 或 Fragment 里都要写一堆模板代码,还得小心配对,漏了注销就会内存泄漏。

我个人习惯用注解处理器来自动生成这些注册和注销的逻辑。今天我们就聊聊怎么用 Kotlin 的注解处理器,把 BroadcastReceiver 的动态管理变得优雅。

为什么需要注解处理?

你想想看,一个稍微复杂点的 App,可能有十几个广播接收器。每个都要写:

val receiver = object : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
        // 处理逻辑
    }
}
val filter = IntentFilter("com.example.MY_ACTION")
registerReceiver(receiver, filter)
// 别忘了在 onDestroy 里注销
unregisterReceiver(receiver)

嗯,这里要注意:注册和注销必须成对出现。我在项目中遇到过好几次,同事在 onResume 里注册了,但 onPause 里忘了注销,结果 Activity 销毁后 receiver 还在存活,导致内存泄漏。这种 bug 很难查,因为崩溃日志往往指向别的地方。

用注解处理器,我们可以把 receiver 的定义和注册逻辑分离,让框架自动生成配对代码。

核心思路:注解 + 处理器

我们先定义一个注解:

@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.SOURCE)
annotation class OnBroadcast(
    val actions: Array<String>,
    val priority: Int = 0
)

这个注解加在方法上,标明这个方法要处理哪些广播 action。注意我用的是 SOURCE 级别,因为运行时不需要反射,处理器在编译期就把代码生成了。

然后定义一个接口,让 Activity 或 Fragment 实现:

interface BroadcastAware {
    fun registerBroadcasts()
    fun unregisterBroadcasts()
}

处理器会扫描所有实现了 BroadcastAware 的类,找到带有 @OnBroadcast 注解的方法,然后生成注册和注销的实现。

注解处理器实现

处理器核心逻辑是这样的:

class BroadcastProcessor : AbstractProcessor() {
    override fun process(annotations: MutableSet<out TypeElement>?, roundEnv: RoundEnvironment): Boolean {
        val elements = roundEnv.getElementsAnnotatedWith(OnBroadcast::class.java)
        if (elements.isEmpty()) return false

        // 按类分组
        val classMap = elements.groupBy { it.enclosingElement as TypeElement }

        for ((classElement, methods) in classMap) {
            val packageName = processingEnv.elementUtils.getPackageOf(classElement).qualifiedName.toString()
            val className = classElement.simpleName.toString()
            val generatedClassName = "${className}_BroadcastHelper"

            val builder = TypeSpec.classBuilder(generatedClassName)
                .addSuperinterface(BroadcastAware::class.java)

            // 生成 registerBroadcasts 方法
            val registerMethod = FunSpec.builder("registerBroadcasts")
                .addModifiers(KModifier.OVERRIDE)
                .addParameter("context", Context::class.java)
                .apply {
                    for (method in methods) {
                        val annotation = method.getAnnotation(OnBroadcast::class.java)
                        val actions = annotation.actions
                        val priority = annotation.priority
                        val methodName = method.simpleName.toString()

                        addStatement(
                            "val filter = IntentFilter().apply { %L }",
                            actions.joinToString("\n") { """addAction("$it")""" }
                        )
                        if (priority != 0) {
                            addStatement("filter.priority = %L", priority)
                        }
                        addStatement(
                            "context.registerReceiver(object : BroadcastReceiver() { " +
                            "override fun onReceive(c: Context, i: Intent) { " +
                            "this@${className}.${methodName}(c, i) } }, filter)"
                        )
                    }
                }
                .build()

            // 生成 unregisterBroadcasts 方法
            val unregisterMethod = FunSpec.builder("unregisterBroadcasts")
                .addModifiers(KModifier.OVERRIDE)
                .addParameter("context", Context::class.java)
                .apply {
                    // 这里需要保存 receiver 引用,实际项目中建议用 List 管理
                    addStatement("// 实际项目中需要维护 receiver 列表")
                    addStatement("// 这里仅做示意")
                }
                .build()

            builder.addFunction(registerMethod)
            builder.addFunction(unregisterMethod)

            val file = FileSpec.builder(packageName, generatedClassName)
                .addType(builder.build())
                .build()

            file.writeTo(processingEnv.filer)
        }
        return true
    }
}

这段代码看起来有点长,但核心逻辑很简单:遍历注解 → 按类分组 → 生成辅助类。生成的类实现了 BroadcastAware 接口,里面包含了注册和注销的具体代码。

关键点:生成的代码里,receiver 是匿名内部类,每次注册都会创建新实例。如果你需要注销,就得保存引用。我个人习惯在生成的类里维护一个 MutableList<BroadcastReceiver>,注册时 add,注销时遍历 unregister。

使用方式

在 Activity 里用起来就清爽多了:

class MainActivity : AppCompatActivity(), BroadcastAware {

    @OnBroadcast(actions = ["android.intent.action.TIME_TICK"])
    fun onTimeTick(context: Context, intent: Intent) {
        // 每分钟收到一次时间更新
        updateClock()
    }

    @OnBroadcast(
        actions = ["com.example.MY_CUSTOM_ACTION"],
        priority = 100
    )
    fun onCustomAction(context: Context, intent: Intent) {
        val data = intent.getStringExtra("data")
        handleData(data)
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // 注册广播
        (application as MyApp).broadcastHelper.registerBroadcasts(this)
    }

    override fun onDestroy() {
        super.onDestroy()
        // 注销广播
        (application as MyApp).broadcastHelper.unregisterBroadcasts(this)
    }
}

你看,Activity 里只需要关注业务逻辑,注册和注销的模板代码全交给处理器了。而且因为是在编译期生成的,运行时没有任何反射开销。

避坑指南

我曾经在项目里踩过一个坑:多个 Activity 注册了相同的 action。比如两个 Activity 都监听 TIME_TICK,结果前台 Activity 销毁后,后台 Activity 的 receiver 也被注销了。这是因为我们用了同一个 BroadcastHelper 实例,没有区分注册者。

解决方案:在生成的辅助类里,用 WeakHashMap<Context, List<BroadcastReceiver>> 来管理,每个 Context 对应自己的 receiver 列表。这样注销时只影响对应的 Activity。

注意:不要用 applicationContext 来注册广播,除非你真的需要全局监听。用 Activity 的 context 注册,生命周期更可控。

知识体系图

下面这张图展示了 BroadcastReceiver 注解处理的整体流程:

BroadcastReceiver 注解处理流程 @OnBroadcast 注解 BroadcastProcessor (APT 处理器) Xxx_BroadcastHelper 辅助类 BroadcastAware 接口 (registerBroadcasts / unregisterBroadcasts) Activity / Fragment 运行时调用 处理逻辑 1. 扫描 @OnBroadcast 2. 解析 action 和 priority 3. 生成 register 方法 4. 生成 unregister 方法 5. 编译期完成,零反射 6. 运行时直接调用

进阶:支持粘性广播和有序广播

实际项目中,我们可能还需要处理粘性广播和有序广播。可以在注解里加参数:

@Target(AnnotationTarget.FUNCTION)
@Retention(RetentionPolicy.SOURCE)
annotation class OnBroadcast(
    val actions: Array<String>,
    val priority: Int = 0,
    val sticky: Boolean = false,
    val ordered: Boolean = false
)

处理器生成代码时,根据 stickyordered 选择不同的注册方式:

  • 粘性广播:用 registerReceiver(receiver, filter) 即可,系统会自动发送最后一次的粘性广播。
  • 有序广播:设置 filter.priority 来控制接收顺序,高优先级的先收到。

小技巧:如果你需要接收系统粘性广播(比如电池电量变化),记得在 AndroidManifest 里声明对应权限。处理器不会帮你检查权限,这是运行时的事。

性能考量

用注解处理器生成的代码,和手写的代码在性能上完全一致。因为生成的就是普通的 Kotlin 代码,编译后就是字节码,没有任何反射或动态代理的开销。

我对比过两种方式:

方式 编译时间 运行时性能 代码量 维护成本
手写注册/注销 无影响 最优 高(容易漏注销)
注解处理器生成 增加约 0.5-1 秒 与手写一致 低(自动配对)
运行时反射 无影响 有反射开销 中等 中等

说白了,注解处理器是「用编译时间换运行效率和代码质量」。我个人觉得这笔交易很划算,尤其是团队协作时,能减少很多低级错误。

嗯,关于 BroadcastReceiver 的注解处理,今天就聊到这儿。核心就是:定义注解 → 处理器扫描 → 生成配对代码。你可以在自己的项目里试试,把那些烦人的注册注销逻辑交给处理器,自己只关心业务。

公众号:蓝海资料掘金营,微信deep3321