优雅解决Android app后台悬浮窗权限问题
背景
2025年10月起,小米将对悬浮窗权限(SYSTEM_ALERT_WINDOW)的使用场景进行调整,调整方案为:
当应用从前台切换到后台时,将仅支持以下业务功能需求下继续使用悬浮窗
1.视频聊天、直播、播放视频等小窗接续播放功能
2.音乐播放软件悬浮显示歌词功能
3.灾害预警、反诈预警等预警类功能
现状:
app在后台时,悬浮窗展示司机状态、订单、抢单等相关信息,使用场景调整之后悬浮窗将无法使用

基于此,考虑使用安卓其他组件来替代,实现悬浮窗效果
安卓多任务形态直观区别

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

实践
画中画实现步骤
-
声明PiP权限
在AndroidManifest.xml中声明PiP支持:
<activity android:name=".PipActivity" android:supportsPictureInPicture="true" android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation" />
关键点:
-
supportsPictureInPicture="true"启用PiP。 -
configChanges防止Activity在PiP模式切换时重建。
-
进入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():添加交互按钮(如”出车/收车”)。
-
处理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 }}
-
pip更新数据
数据同步:通过ViewModel或LocalBroadcast更新PiP窗口中的订单信息
-
按钮操作控制
通过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() } }}
-
兼容性处理
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支持
注意:
-
pip仅能显示当前 Activity 的 UI 内容,并且系统会自动裁剪和缩放当前 Activity 的可见区域。
-
RemoteAction只能设置简单的按钮交互,不能显示复杂UI
-
如果 PiP 窗口和主应用交互复杂,需注意
taskAffinity和launchMode的影响: -
taskAffinity:避免 PiP Activity 被错误地放入其他 Task。 -
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和权限声明
可应用场景拓展
-
导航:在接单后,司机退出App仍能悬浮显示路线,用户退出后,可悬浮路线查看司机到达位置。
-
订单提醒:后台时新订单以小窗形式弹出,避免错过订单。
-
通话兼容:接听电话时保持导航可见。
夜雨聆风

