乐于分享
好东西不私藏

优雅解决Android app后台悬浮窗权限问题

优雅解决Android app后台悬浮窗权限问题

背景

2025年10月起,小米将对悬浮窗权限(SYSTEM_ALERT_WINDOW)的使用场景进行调整,调整方案为:

当应用从前台切换到后台时,将仅支持以下业务功能需求下继续使用悬浮窗

1.视频聊天、直播、播放视频等小窗接续播放功能

2.音乐播放软件悬浮显示歌词功能

3.灾害预警、反诈预警等预警类功能

现状:

app在后台时,悬浮窗展示司机状态、订单、抢单等相关信息,使用场景调整之后悬浮窗将无法使用

基于此,考虑使用安卓其他组件来替代,实现悬浮窗效果

安卓多任务形态直观区别

详细功能特性对比

对比三种多任务处理方式,使用画中画可以解决我们app在后台无法使用悬浮窗权限的问题

实践

画中画实现步骤

  1. 声明PiP权限

AndroidManifest.xml中声明PiP支持:

<activity     android:name=".PipActivity"    android:supportsPictureInPicture="true"    android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation" />

关键点:

  • supportsPictureInPicture="true" 启用PiP。
  • configChanges 防止Activity在PiP模式切换时重建。
  1. 进入PiP模式

在需要触发PiP的代码逻辑中,或按home键进入画中画

// Kotlin 示例fun enterPipMode() {    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {        val params = PictureInPictureParams.Builder()            .setAspectRatio(Rational(16, 9)) // 画面宽高比(如16:9)            .setActions(getPipActions()) // 可选:添加操作按钮(如出车/收车)            .build()        activity.enterPictureInPictureMode(params)    }}

参数说明:

  • setAspectRatio():设置悬浮窗比例(需避免遮挡关键UI)。
  • setActions():添加交互按钮(如”出车/收车”)。
  1. 处理PiP模式下的UI

在Activity中重写onPictureInPictureModeChanged,调整UI布局:

override fun onPictureInPictureModeChanged(    isInPiP: Boolean,    newConfig: Configuration) {    if (isInPiP) {        // 隐藏无关控件,仅保留核心信息(比如:订单信息)        binding.btn.visibility = View.GONE        binding.floatPip.scaleToFitPip()    } else {        // 退出PiP时恢复完整UI        binding.btn.visibility = View.VISIBLE    }}
  1. pip更新数据

数据同步:通过ViewModelLocalBroadcast更新PiP窗口中的订单信息

  1. 按钮操作控制

通过RemoteAction添加PiP窗口按钮

private fun getPipActions(): List<RemoteAction> {    val intent = Intent(this, PipActionReceiver::class.java).apply {        action = "ACTION_DUTY_UPDATE"    }    val icon = Icon.createWithResource(this, R.drawable.ic_duty)    return listOf(        RemoteAction(icon, "出车", "出车按钮", PendingIntent.getBroadcast(            this, 0, intent, PendingIntent.FLAG_IMMUTABLE        ))    )}

处理按钮点击事件

class PipActionReceiver : BroadcastReceiver() {    override fun onReceive(context: Context, intent: Intent) {        when (intent.action) {            ACTION_DUTY_UPDATE -> dutyUpdate()        }    }}
  1. 兼容性处理

    fun isPipSupported(): Boolean {    return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {        packageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE)    } else {        false    }}
    fun checkPipAvailability() {    if (!isPipSupported()) {        // 降级方案:使用普通后台服务或通知        showNotificationInstead()    }}
    • 降级
    • 检查pip支持

注意:

  1. pip仅能显示当前 Activity 的 UI 内容,并且系统会自动裁剪和缩放当前 Activity 的可见区域。

  2. RemoteAction只能设置简单的按钮交互,不能显示复杂UI

  3. 如果 PiP 窗口和主应用交互复杂,需注意 taskAffinity 和 launchMode 的影响:

    1. taskAffinity:避免 PiP Activity 被错误地放入其他 Task。
    2. singleInstance:可能导致返回栈异常,慎用。

常见问题解决

问题 1:PiP 窗口未显示指定区域

原因:

  • 目标 View 的坐标未正确计算(如未考虑状态栏高度)。
  • 目标 View 在 PiP 触发时未完成布局(onCreate 中调用过早)。

修复:

// 确保在 View 布局完成后调用(如 onWindowFocusChanged)override fun onWindowFocusChanged(hasFocus: Boolean) {    super.onWindowFocusChanged(hasFocus)    if (hasFocus && isInPictureInPictureMode) {        updatePipRegion()    }}

问题 2:PiP 窗口显示空白

原因:

  • 指定的 Rect 超出 Activity 边界。
  • 目标 View 被其他控件覆盖(如 Fragment)。

修复:

// 检查 Rect 是否有效if (visibleRect.width() > 0 && visibleRect.height() > 0) {    setSourceRectHint(visibleRect)}

问题 3: setSourceRectHint 无法满足需求

替代方案:自定义pip布局

  • 在 Activity 中预先设置一个仅包含目标内容的隐藏布局。
  • 进入 PiP 时显示该布局,退出时恢复原布局。
<!-- activity_pip.xml --><FrameLayout>    <LinearLayout android:id="@+id/main_content"> ... </LinearLayout>    <LinearLayout         android:id="@+id/pip_content"        android:visibility="gone"> ... </LinearLayout></FrameLayout>
override fun onPictureInPictureModeChanged(    isInPiP: Boolean,    newConfig: Configuration) {    if (isInPiP) {        binding.pipContent.visibility = View.VISIBLE        binding.mainContent.visibility = View.GONE    } else {        binding.mainContent.visibility = View.VISIBLE        binding.pipContent.visibility = View.GONE    }}

问题 4: 交互按钮不显示

原因:RemoteAction创建或权限问题

修复:检查PendingIntent的flags和权限声明

可应用场景拓展

  1. 导航:在接单后,司机退出App仍能悬浮显示路线,用户退出后,可悬浮路线查看司机到达位置。

  2. 订单提醒:后台时新订单以小窗形式弹出,避免错过订单。

  3. 通话兼容:接听电话时保持导航可见。