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 注解处理的整体流程:
进阶:支持粘性广播和有序广播
实际项目中,我们可能还需要处理粘性广播和有序广播。可以在注解里加参数:
@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
)
处理器生成代码时,根据 sticky 和 ordered 选择不同的注册方式:
- 粘性广播:用
registerReceiver(receiver, filter)即可,系统会自动发送最后一次的粘性广播。 - 有序广播:设置
filter.priority来控制接收顺序,高优先级的先收到。
小技巧:如果你需要接收系统粘性广播(比如电池电量变化),记得在 AndroidManifest 里声明对应权限。处理器不会帮你检查权限,这是运行时的事。
性能考量
用注解处理器生成的代码,和手写的代码在性能上完全一致。因为生成的就是普通的 Kotlin 代码,编译后就是字节码,没有任何反射或动态代理的开销。
我对比过两种方式:
| 方式 | 编译时间 | 运行时性能 | 代码量 | 维护成本 |
|---|---|---|---|---|
| 手写注册/注销 | 无影响 | 最优 | 多 | 高(容易漏注销) |
| 注解处理器生成 | 增加约 0.5-1 秒 | 与手写一致 | 少 | 低(自动配对) |
| 运行时反射 | 无影响 | 有反射开销 | 中等 | 中等 |
说白了,注解处理器是「用编译时间换运行效率和代码质量」。我个人觉得这笔交易很划算,尤其是团队协作时,能减少很多低级错误。
嗯,关于 BroadcastReceiver 的注解处理,今天就聊到这儿。核心就是:定义注解 → 处理器扫描 → 生成配对代码。你可以在自己的项目里试试,把那些烦人的注册注销逻辑交给处理器,自己只关心业务。