一、需求背景与核心挑战
功能需求
需实现一个全局悬浮窗(Floating View),具体行为规范如下:
- 应用位于前台时,隐藏悬浮窗,避免遮挡用户正常操作。
- 应用退至后台时,显示悬浮窗,使其保持悬浮于系统或其他应用界面之上。
核心挑战
悬浮窗通常由独立的 Service 组件维护,以确保其生命周期独立于 Activity。因此,该需求的技术难点在于:在后台运行的 Service 如何实时、准确地感知当前应用的前后台状态切换?
二、悬浮窗实现的核心原理剖析
在阐述具体代码方案前,需明确通过"前台 Service + WindowManager"实现悬浮窗效果的底层原理。此机制主要涉及 Android 的显示架构与进程保活策略:
2.1 Android 的窗口层级体系 (Z-order)
Android 的显示系统并非局限于各个应用单独绘制自身区域。屏幕上的所有渲染内容均由 WindowManagerService (WMS) 统一管理。WMS 维护着一个全局的窗口 Z-order 栈,不同类型的窗口对应不同的显示层级:
- 系统窗口层(System Window):最高层级,包含状态栏、导航栏、Toast、悬浮窗等。
- 子窗口层(Sub Window):中间层级,包含 Dialog、PopupWindow 等(需依附于宿主窗口)。
- 应用窗口层(Application Window):最底层级,对应普通 Activity 的窗口。
由于系统窗口层的 Z-order 高于应用窗口层,将窗口类型配置为系统级,即可实现覆盖于所有应用之上的"悬浮"效果。
2.2 WindowManager.addView() 的底层机制
调用 WindowManager.addView(view, params) 时,系统底层的实际调用链路如下:
应用层代码 → WindowManager (客户端代理) → Binder IPC → WindowManagerService (系统服务端) → SurfaceFlinger (合成渲染)
传入的 LayoutParams.type 参数决定了目标 View 所属的显示层级。当参数配置为:
params.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
WMS 会将该窗口分配至系统窗口层。该窗口独立于任何 Activity 的生命周期,直接由系统 WMS 管理,其 Z-order 默认高于所有普通 Activity,从而呈现悬浮状态。
2.3 Service 与 Activity 的生命周期差异
悬浮窗通常不依附于 Activity 实现,其核心原因如下:
- Activity 的局限性:Activity 具有严格的系统生命周期。用户触发 Home 键或切换至其他应用时,Activity 将执行
onStop()/onDestroy(),其持有的 Context 和资源存在被系统回收的风险。悬浮窗需要在应用退至后台后持续存在,Activity 无法满足此场景需求。 - Service 的适用性:Service 作为无界面的后台组件,生命周期独立于 Activity。维持 Service 的存活状态,即可确保其持有的 WindowManager 引用及附属 View 持续显示在屏幕上。
2.4 前台 Service 与进程保活
Android 系统的进程管理策略对后台组件有严格限制:
- 普通 Service 运行于后台时,系统的内存管理机制(LMK)在资源紧张时会随时清理该进程。进程一旦消亡,悬浮窗将随之销毁。
- 前台 Service(通过
startForeground()启动)会在通知栏展示常驻通知,向系统声明该进程正在执行用户可感知的重要任务。系统因此会提升该进程的oom_adj优先级,极大降低其被后台清理机制终止的概率。因此,前台 Service 是保障悬浮窗持续稳定存在的前提。
三、方案设计与对比
针对"前台隐藏,后台显示"的功能需求,目前存在三种主流实现方案。其核心逻辑均依赖于应用生命周期监听机制,以判断应用的前后台状态。
方案一:ActivityLifecycleCallbacks + Binder 通信
利用 Application 的 ActivityLifecycleCallbacks 监听应用内所有 Activity 的生命周期,通过计数器计算前后台状态,并借助 Binder 跨组件控制 Service。
1. 悬浮窗 Service 实现
class FloatingViewService : Service() {
private lateinit var windowManager: WindowManager
private lateinit var floatingView: View
private lateinit var params: WindowManager.LayoutParams
private var isViewAdded = false
override fun onCreate() {
super.onCreate()
// 获取系统级 WindowManager
windowManager = getSystemService(WINDOW_SERVICE) as WindowManager
// 创建悬浮窗 View
floatingView = LayoutInflater.from(this).inflate(R.layout.layout_floating, null)
// 配置参数
params = WindowManager.LayoutParams(
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
PixelFormat.TRANSLUCENT
).apply {
gravity = Gravity.TOP or Gravity.START
x = 0
y = 100
}
// 注册 View,初始状态设为隐藏
windowManager.addView(floatingView, params)
isViewAdded = true
floatingView.visibility = View.GONE
}
// ===== 状态控制接口 =====
fun showFloatingView() {
if (isViewAdded) floatingView.visibility = View.VISIBLE
}
fun hideFloatingView() {
if (isViewAdded) floatingView.visibility = View.GONE
}
// ===== Binder 通信实现 =====
private val binder = FloatingBinder()
inner class FloatingBinder : Binder() {
fun getService(): FloatingViewService = this@FloatingViewService
}
override fun onBind(intent: Intent): IBinder = binder
override fun onDestroy() {
if (isViewAdded) windowManager.removeView(floatingView)
super.onDestroy()
}
}
2. Application 生命周期监听
class MyApplication : Application() {
var floatingService: FloatingViewService? = null
private var activityCount = 0
private val serviceConnection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
val binder = service as FloatingViewService.FloatingBinder
floatingService = binder.getService()
}
override fun onServiceDisconnected(name: ComponentName?) {
floatingService = null
}
}
override fun onCreate() {
super.onCreate()
// 启动并绑定悬浮窗 Service
val intent = Intent(this, FloatingViewService::class.java)
startForegroundService(intent) // 需配置前台服务通知
bindService(intent, serviceConnection, BIND_AUTO_CREATE)
// 注册全局 Activity 生命周期回调
registerActivityLifecycleCallbacks(object : ActivityLifecycleCallbacks {
override fun onActivityStarted(activity: Activity) {
activityCount++
if (activityCount == 1) {
// 计数器由 0 变为 1:应用切换至前台
floatingService?.hideFloatingView()
}
}
override fun onActivityStopped(activity: Activity) {
activityCount--
if (activityCount == 0) {
// 计数器归零:应用完全退至后台
floatingService?.showFloatingView()
}
}
// 其他回调缺省处理
override fun onActivityCreated(a: Activity, s: Bundle?) {}
override fun onActivityResumed(a: Activity) {}
override fun onActivityPaused(a: Activity) {}
override fun onActivitySaveInstanceState(a: Activity, s: Bundle) {}
override fun onActivityDestroyed(a: Activity) {}
})
}
}
逻辑依据:采用 onStart/onStop 进行状态计数的合理性
相较于 onResume/onPause,使用 onStart/onStop 进行计数可有效避免误判:
- 页面跳转场景 (Activity A -> B):目标页面 B 先触发
onStart(activityCount=2),源页面 A 后触发onStop(activityCount=1)。此过程计数器不会归零,可防止误触发后台逻辑。 - 退至后台场景 (如触发 Home 键):当前页面直接触发
onStop(activityCount=0),系统正确识别为后台状态并显示悬浮窗。
方案二:ActivityLifecycleCallbacks + 全局状态总线 (LiveData/EventBus)
为降低 Application 与 Service 之间的耦合度,可采用响应式通信方式替代 Binder 机制。
1. 定义全局状态
object AppStateManager {
val isAppForeground = MutableLiveData<Boolean>()
}
2. Application 状态分发
override fun onActivityStarted(activity: Activity) {
activityCount++
if (activityCount == 1) {
AppStateManager.isAppForeground.postValue(true)
}
}
override fun onActivityStopped(activity: Activity) {
activityCount--
if (activityCount == 0) {
AppStateManager.isAppForeground.postValue(false)
}
}
3. Service 状态观察
// 置于 Service 的 onCreate() 中
AppStateManager.isAppForeground.observeForever { isForeground ->
if (isForeground) {
hideFloatingView()
} else {
showFloatingView()
}
}
方案三:ProcessLifecycleOwner(官方推荐方案)
利用 Google 官方提供的 Lifecycle 架构组件,直接监听应用进程级的前后台状态。
技术优势:无需手动维护计数逻辑或跨组件通信机制,代码精简,且对配置变更(Configuration Changes)、多窗口模式等边界场景具有最佳兼容性。
1. 引入依赖
implementation "androidx.lifecycle:lifecycle-process:2.6.2" // 建议使用最新稳定版
2. Service 状态监听实现
class FloatingViewService : Service() {
// 隐藏 WindowManager 与 View 初始化逻辑 ...
override fun onCreate() {
super.onCreate()
// 隐藏视图注册逻辑 ...
// 注册进程级生命周期观察者
ProcessLifecycleOwner.get().lifecycle.addObserver(object : DefaultLifecycleObserver {
override fun onStart(owner: LifecycleOwner) {
// 应用进入前台 -> 隐藏悬浮窗
hideFloatingView()
}
override fun onStop(owner: LifecycleOwner) {
// 应用退至后台 -> 显示悬浮窗
showFloatingView()
}
})
}
}
四、总结与建议
| 方案 | 优势 | 局限性 | 适用场景 |
|---|---|---|---|
| 方案一 (Binder) | 无需依赖第三方组件,控制逻辑完全自主 | 代码较为冗长,存在 Application 与 Service 强耦合 | 限制引入外部依赖的遗留系统维护 |
| 方案二 (LiveData) | 组件间解耦良好,业务逻辑更清晰 | 仍需人工维护生命周期计数,需关注内存泄漏风险 | 项目架构已深度整合响应式编程范式 |
| 方案三 (Lifecycle) | 官方标准方案,代码极简,系统级边界处理最稳健 | 需引入 androidx.lifecycle 外部依赖 | 所有现代化 Android 工程首选 |
结论:在多数业务场景中,推荐优先采用 方案三(ProcessLifecycleOwner) 并配合前台 Service,以构建稳定且易于维护的悬浮窗交互架构。