在 Android 开发中,我们经常遇到需要"全局掌控"所有 Activity 的需求,比如:
- 判断 App 是否在前台(热启动监听)。
- 管理 Activity 栈(一键退出 App)。
- 全局埋点(自动记录每个页面的停留时间)。
- 全局水印/弹窗。
很多开发者可能会想到在 BaseActivity 里写逻辑,但这种方式侵入性强,且容易遗漏(比如引入的第三方 UI 库的 Activity 就不受控制)。
其实,Android 官方在 Application 中早就提供了一个神器------registerActivityLifecycleCallbacks。
什么是 ActivityLifecycleCallbacks?
它是 Application 类提供的一个接口,允许我们监听整个应用进程中所有 Activity 的生命周期变化。
无论 Activity 是你自己写的,还是第三方 SDK 里的,只要它运行在你的进程里,它的 onCreate、onStart、onResume 等回调都会被这个监听器捕获。
基本用法
Kotlin
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
registerActivityLifecycleCallbacks(object : ActivityLifecycleCallbacks {
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
Log.d("Lifecycle", "${activity.javaClass.simpleName} Created")
}
override fun onActivityStarted(activity: Activity) {}
override fun onActivityResumed(activity: Activity) {}
override fun onActivityPaused(activity: Activity) {}
override fun onActivityStopped(activity: Activity) {}
override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {}
override fun onActivityDestroyed(activity: Activity) {}
})
}
}
场景一:判断 App 前后台切换
这是最经典的使用场景。通过计算"处于 Started 状态的 Activity 数量",我们可以精准判断 App 是进入了前台还是切到了后台。
核心逻辑
onActivityStarted: 计数器 +1。onActivityStopped: 计数器 -1。- 如果计数从 0 变 1 ➡️ App 进入前台(冷启动/热启动)。
- 如果计数从 1 变 0 ➡️ App 进入后台。
代码实现
Kotlin
private var activityCount = 0
registerActivityLifecycleCallbacks(object : ActivityLifecycleCallbacks {
override fun onActivityStarted(activity: Activity) {
activityCount++
if (activityCount == 1) {
// App 回到前台
// 这里可以做:检查更新、同步配置、上报活跃日志
Log.d("AppStatus", "App Foreground")
}
}
override fun onActivityStopped(activity: Activity) {
activityCount--
if (activityCount == 0) {
// App 进入后台
Log.d("AppStatus", "App Background")
}
}
// 其他方法略...
})
注意 :这种方式比监听 ON_START 事件更底层,不依赖 Jetpack 库,且兼容性极好。
实战场景二:Activity 栈管理(一键退出)
有时候我们需要"退出登录",要求关闭所有页面并回到登录页。如果手动 finish() 很容易漏掉某个中间页。我们可以用一个 List 维护所有活着的 Activity。
Kotlin
object ActivityStackManager {
private val activityStack = Stack<Activity>()
fun init(app: Application) {
app.registerActivityLifecycleCallbacks(object : ActivityLifecycleCallbacks {
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
activityStack.push(activity)
}
override fun onActivityDestroyed(activity: Activity) {
activityStack.remove(activity)
}
// 其他方法略...
})
}
// 杀掉所有页面
fun finishAll() {
for (activity in activityStack) {
if (!activity.isFinishing) {
activity.finish()
}
}
activityStack.clear()
}
}
场景三:无埋点页面统计
产品想要统计每个页面的访问次数和停留时长,如果让每个 Activity 自己去上报,代码会非常冗余。
利用 LifecycleCallbacks,我们可以在一个地方搞定全应用统计。
Kotlin
registerActivityLifecycleCallbacks(object : ActivityLifecycleCallbacks {
override fun onActivityResumed(activity: Activity) {
// 开始计时
PageTracker.startRecord(activity.javaClass.simpleName)
}
override fun onActivityPaused(activity: Activity) {
// 结束计时并上报
PageTracker.stopRecordAndReport(activity.javaClass.simpleName)
}
})
常见问题:可以注册多个监听器吗?
答案是:可以,而且强烈推荐!
很多开发者担心多次调用 registerActivityLifecycleCallbacks 会覆盖之前的监听器,或者导致冲突。其实完全不用担心。
原理
在 Application 内部,它维护了一个 ArrayList<ActivityLifecycleCallbacks>。当你调用 register... 时,只是把你的监听器 add 到这个列表里。
当生命周期事件触发时,Application 会遍历列表,依次调用所有注册的监听器。
最佳实践:职责分离
不要把所有逻辑(栈管理、打点、业务检查)都塞进同一个 Callback 里,那样会制造出一个难以维护的"上帝类"。
推荐写法:
Kotlin
override fun onCreate() {
super.onCreate()
// 1. 负责 Activity 栈管理
registerActivityLifecycleCallbacks(ActivityStackManager())
// 2. 负责全局埋点统计
registerActivityLifecycleCallbacks(AnalyticsCallbacks())
// 3. 负责前后台业务检测
registerActivityLifecycleCallbacks(AppStatusCallbacks())
}
这样,如果哪天你想移除埋点功能,只需要删掉第 2 行代码,完全不会影响栈管理和业务检测。解耦才是架构的真谛。
避坑指南
- 配置变化(Configuration Change) :
当屏幕旋转时,Activity 会销毁重建。
onActivityDestroyed->onActivityCreated。
如果你的 StackManager 逻辑不够健壮,可能会导致引用了已经销毁的 Activity。 - 多进程问题 :
Application.onCreate会在每个进程创建时执行。
如果你的 App 有多个进程(比如推送进程、小程序进程),请务必判断if (isMainProcess),否则你的监听逻辑会在每个进程里都跑一遍,导致重复上报或逻辑错误。 - 初始化顺序 :
一定要在Application.onCreate的早期注册。如果注册晚了(比如在 SplashActivity 之后),可能会漏掉第一个页面的onCreate回调。
总结
registerActivityLifecycleCallbacks 是 Android 开发中低成本、高收益的架构级 API。
- 它解耦了
BaseActivity。 - 它提供了上帝视角的生命周期感知。
- 它是实现热启动监听、全局堆栈管理的最佳方案。
下次遇到"全局 xxx"的需求,先别急着改 Activity,想想它能不能帮你搞定!