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

背景

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

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

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

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

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

现状:

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

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

安卓多任务形态直观区别

详细功能特性对比

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

实践

画中画实现效果

画中画实现步骤

声明PiP权限

AndroidManifest.xml中声明PiP支持:

ini 复制代码
<activity 
    android:name=".PipActivity"
    android:supportsPictureInPicture="true"
    android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation" />
 

关键点:

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

进入PiP模式

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

scss 复制代码
// 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():添加交互按钮(如"出车/收车")。

处理PiP模式下的UI

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

kotlin 复制代码
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窗口中的订单信息

按钮操作控制

通过RemoteAction添加PiP窗口按钮

kotlin 复制代码
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
        ))
    )
}

处理按钮点击事件

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

xml 复制代码
*   **检查pip支持**

<!---->

    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()
        }
    }

注意:

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

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

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

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

常见问题解决

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

原因:

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

修复:

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

问题 2:PiP 窗口显示空白

原因:

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

修复:

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

问题 3: setSourceRectHint 无法满足需求

替代方案:自定义pip布局

  • 在 Activity 中预先设置一个仅包含目标内容的隐藏布局。
  • 进入 PiP 时显示该布局,退出时恢复原布局。
xml 复制代码
<!-- activity_pip.xml -->
<FrameLayout>
    <LinearLayout android:id="@+id/main_content"> ... </LinearLayout>
    <LinearLayout 
        android:id="@+id/pip_content"
        android:visibility="gone"> ... </LinearLayout>
</FrameLayout>
kotlin 复制代码
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. 通话兼容:接听电话时保持导航可见。

相关推荐
用户69371750013842 小时前
Android 手机终于能当电脑用了
android·前端
用户5172231574803 小时前
android资源类型与布局资源详细介绍
android
优选资源分享3 小时前
GKD v1.11.6 | 安卓开屏广告跳过工具 可用版
android
robotx4 小时前
安卓zygote启动相关
android
Mac的实验室5 小时前
2026年最新真实社交怎么注册?手把手教你如何成功注册Truth Social账号
android
毕设源码-郭学长5 小时前
【开题答辩全过程】以 基于Android的点餐APP的设计为例,包含答辩的问题和答案
android
polaris06305 小时前
学生成绩管理系统(MySQL)
android·数据库·mysql
__Yvan5 小时前
Kotlin 的 ?.let{} ?: run{} 真的等价于 if-else 吗?
android·开发语言·前端·kotlin
tangweiguo030519876 小时前
Android WorkManager 完整实战教程(含完整文件)
android·kotlin