我在 Android 中使用 Jetpack Compose 编写的 Overlay 服务遇到问题。我试图在按下后退按钮时删除覆盖层,但我在实现此功能时遇到困难。具体来说,当我尝试使用 BackHandler 时,遇到错误消息“No OnBackPressedDispatcherOwner wasprovided via LocalOnBackPressedDispatcherOwner”。
服务是这样的:
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 {
// Handle the incoming intent
handleIntent(intent)
return super.onStartCommand(intent, flags, startId)
}
private fun handleIntent(intent: Intent?) {
// Check if the intent is not null
intent?.let {
// Extract data from the intent
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
)
PreviousCallCard.getTrackerInfo(this)
val composeView = ComposeView(this)
composeView.setContent {
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)
[email protected]()
}
AnimatedVisibility(
modifier = Modifier,
visibleState = animVisibleState,
enter = fadeIn(),
exit = fadeOut()
) {
Column(
modifier = Modifier
.fillMaxSize()
.background(Color.Black.copy(alpha = 0.5f))
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = null
) {
animVisibleState.targetState = false
},
verticalArrangement = Arrangement.Bottom
) {
PreviousCallCard(
modifier = Modifier,
number = number,
endDate = date,
onWhatsAppClick = {
val whatsappPackage = "com.whatsapp"
val packageManager = context.packageManager
val isWhatsAppInstalled = try {
packageManager.getPackageInfo(
whatsappPackage,
PackageManager.GET_ACTIVITIES
)
true
} catch (e: PackageManager.NameNotFoundException) {
false
}
if (!isWhatsAppInstalled) {
val whatsappUrl =
"https://play.google.com/store/apps/details?id=$whatsappPackage"
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(whatsappUrl))
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
context.startActivity((intent))
} else {
val whatsappVideoId = getIdWhatsAppVideo(contentResolver, it)
if (whatsappVideoId != null) {
launchWhatsAppIntent(
context,
launcher = null,
whatsappVideoId,
true
)
} else {
launchWhatsAppMessageIntent(
context = context,
isWhatsAppInstalled = true,
launcher = null,
phoneNumber = number
)
}
}
animVisibleState.targetState = false
},
onViewProfile = {
context.startActivity(MainActivity.getStartIntent(context, ExtraDataType.VIEW_PROFILE))
animVisibleState.targetState = false
},
share = { name ->
if (name == "Unknown") {
launchLogShareIntent(
context,
number,
true
)
} else {
val lookUpContact =
getLookUpKey(contentResolver = contentResolver, name = name)
if (lookUpContact != null) {
launchContactShareIntent(context, lookUpContact, true)
}
}
animVisibleState.targetState = false
},
onClose = {
animVisibleState.targetState = false
},
navigateToCall = {
}
)
Spacer(modifier = Modifier.size(5.dp))
if (rememberAds == AdsType.BANNER_ID) {
Box(
modifier = Modifier
.fillMaxWidth()
.background(Color.White, RoundedCornerShape(20.dp))
) {
AdsBannerMedium(modifier = Modifier.fillMaxWidth())
}
} else {
Box(
modifier = Modifier
.fillMaxWidth()
.background(Color.White, RoundedCornerShape(20.dp))
) {
AdsWidget(
context = this@OverlayService,
modifier = Modifier.fillMaxWidth()
)
}
}
}
}
}
// Trick The ComposeView into thinking we are tracking lifecycle
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
}
}
我尝试了多种解决方案,但没有任何效果,我尝试的最后一件事是像这样在视图本身上设置侦听器键
composeView.requestFocus()
composeView.setFocusableInTouchMode(true)
composeView.setOnKeyListener(View.OnKeyListener { v, keyCode, event ->
if (keyCode == KeyEvent.KEYCODE_BACK) {
stopSelf()
return@OnKeyListener true
}
false
})
但不幸的是,我根本没有收到通知,而且效果也不佳。 我在网站上调查了一些问题,例如:
在android中按后退或主页按钮时是否可以使用WindowManager删除覆盖层? - 堆栈溢出
我尝试了该问题的正确答案,但它总是崩溃要求令牌,所以我想这个答案只有在活动没有被破坏的情况下才有效,这不是我的情况,因为我的撰写是一个覆盖层,即使应用程序关闭也可以工作
带有叠加层的服务 - 按返回按钮 - stackoverflow
我尝试了我之前在这个问题帖子中提到的关键侦听器的解决方案,但仍然对我不起作用,并且根本没有收到通知。
我找到了这个问题的解决方案,适用于 28 到 34 的 API。
解决方案是删除 来自此处的“FLAG_NOT_FOCUSABLE”标志
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 ,
PixelFormat.TRANSLUCENT
)
然后我们用我们的视图设置一个监听器,如下所示:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
composeView.addOnUnhandledKeyEventListener{ view, event ->
if ( event.action == KeyEvent.KEYCODE_BACK || event.action == KeyEvent.KEYCODE_SOFT_LEFT || event.action == KeyEvent.KEYCODE_SOFT_RIGHT || event.action == KeyEvent.KEYCODE_HOME || event.action == KeyEvent.KEYCODE_MOVE_HOME) {
PreviousCallCard.finishedShowingCard(this)
windowManager.removeView(composeView)
[email protected]()
true
} else {
false
}
}
}
由于 28 以上的 API 有软左或右作为返回方式而不是后退按钮,我们处理所有这些键码来处理后退效果。
我仍然不知道这是否准确且实施良好,因此如果有人有任何改进,我会很高兴看到您的贡献。