Android Compose悬浮窗按返回按钮无法关闭问题求助
问题背景
在使用Jetpack Compose开发Android的Overlay Service悬浮窗时,按下返回按钮关闭悬浮窗的需求遇到两个问题:
- 使用
BackHandler组件时,报错:No OnBackPressedDispatcherOwner was provided via LocalOnBackPressedDispatcherOwner - 尝试为
ComposeView设置OnKeyListener监听返回键,未收到任何回调
用户原Overlay Service核心代码:
class OverlayService : Service() { val windowManager get() = getSystemService(WINDOW_SERVICE) as WindowManager override fun onCreate() { super.onCreate() this.updateAppLanguage() } override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { handleIntent(intent) return super.onStartCommand(intent, flags, startId) } private fun handleIntent(intent: Intent?) { intent?.let { val number = it.getStringExtra("number") val dateEnd = it.getLongExtra("date", 0) if (number != null) { showOverlay(number, dateEnd) } } } private fun showOverlay(number: String, date: Long) { val layoutFlag: Int = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY val params = WindowManager.LayoutParams( WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.MATCH_PARENT, layoutFlag, WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSLUCENT ) val composeView = ComposeView(this) composeView.setContent { // 原有Compose内容 val context = LocalContext.current val rememberAds by remember { mutableStateOf(PreviousCallCard.showAd(context)) } val animVisibleState = remember { MutableTransitionState(false) .apply { targetState = true } } if (!animVisibleState.targetState && !animVisibleState.currentState ) { PreviousCallCard.finishedShowingCard(this) windowManager.removeView(composeView) this@OverlayService.stopSelf() } AnimatedVisibility( modifier = Modifier, visibleState = animVisibleState, enter = fadeIn(), exit = fadeOut() ) { // 原有布局内容 } } // 原有生命周期相关设置 val viewModelStore = ViewModelStore() val viewModelStoreOwner = object : ViewModelStoreOwner { override val viewModelStore: ViewModelStore get() = viewModelStore } val lifecycleOwner = MyLifecycleOwner() lifecycleOwner.performRestore(null) lifecycleOwner.handleLifecycleEvent(Lifecycle.Event.ON_CREATE) lifecycleOwner.handleLifecycleEvent(Lifecycle.Event.ON_START) lifecycleOwner.handleLifecycleEvent(Lifecycle.Event.ON_RESUME) composeView.setViewTreeLifecycleOwner(lifecycleOwner) composeView.setViewTreeSavedStateRegistryOwner(lifecycleOwner) composeView.setViewTreeViewModelStoreOwner(viewModelStoreOwner) windowManager.addView(composeView, params) } override fun onBind(intent: Intent): IBinder? { return null }
用户尝试的OnKeyListener代码(未生效):
composeView.requestFocus() composeView.setFocusableInTouchMode(true) composeView.setOnKeyListener(View.OnKeyListener { v, keyCode, event -> if (keyCode == KeyEvent.KEYCODE_BACK) { stopSelf() return@OnKeyListener true } false })
核心问题分析
OnKeyListener未生效的根本原因是WindowManager.LayoutParams设置了FLAG_NOT_FOCUSABLE,该标记会让悬浮窗无法获取焦点,自然收不到任何按键事件。同时BackHandler报错是因为Compose的LocalOnBackPressedDispatcherOwner未被正确提供。
有效解决方案
方案一:修改LayoutParams后使用OnKeyListener
修改WindowManager.LayoutParams的flags
将原有flags中的FLAG_NOT_FOCUSABLE替换为FLAG_NOT_TOUCH_MODAL,确保悬浮窗可以获取焦点:val params = WindowManager.LayoutParams( WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.MATCH_PARENT, layoutFlag, WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN or WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL, PixelFormat.TRANSLUCENT )FLAG_NOT_TOUCH_MODAL的作用是允许其他View接收触摸事件,同时当前悬浮窗仍能获取焦点和按键事件。正确设置ComposeView的焦点和OnKeyListener
在添加到WindowManager之前,设置ComposeView的焦点属性并绑定监听器:composeView.apply { isFocusable = true isFocusableInTouchMode = true setOnKeyListener { _, keyCode, event -> // 只处理按下事件,避免重复触发 if (event.action == KeyEvent.ACTION_DOWN && keyCode == KeyEvent.KEYCODE_BACK) { // 执行关闭逻辑:先移除View再停止Service windowManager.removeView(this) this@OverlayService.stopSelf() true } else { false } } }
方案二:自定义OnBackPressedDispatcherOwner,使用Compose BackHandler
如果你更倾向于使用Compose的BackHandler组件,可以按以下步骤实现:
同样先修改LayoutParams的flags(同方案一第一步,去掉
FLAG_NOT_FOCUSABLE)实现自定义OnBackPressedDispatcherOwner
在showOverlay方法中,添加自定义的OnBackPressedDispatcherOwner实现,并绑定到ComposeView的ViewTree:// 自定义OnBackPressedDispatcherOwner val backPressedDispatcherOwner = object : OnBackPressedDispatcherOwner { private val dispatcher = OnBackPressedDispatcher() override val onBackPressedDispatcher: OnBackPressedDispatcher get() = dispatcher override val lifecycle: Lifecycle get() = lifecycleOwner.lifecycle } // 绑定到ComposeView的ViewTree composeView.setViewTreeOnBackPressedDispatcherOwner(backPressedDispatcherOwner)在Compose内容中使用BackHandler
在composeView.setContent内部添加BackHandler:composeView.setContent { // ... 原有代码 BackHandler { // 触发关闭动画 animVisibleState.targetState = false // 动画结束后执行销毁逻辑 LaunchedEffect(animVisibleState.currentState) { if (!animVisibleState.currentState) { windowManager.removeView(composeView) this@OverlayService.stopSelf() } } } // ... 原有AnimatedVisibility等内容 }
内容的提问来源于stack exchange,提问作者hema ezzat

