背景
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
}
}
-
pip更新数据
数据同步:通过ViewModel或LocalBroadcast更新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()
}
}
}
-
兼容性处理
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()
}
}
注意:
-
pip仅能显示当前 Activity 的 UI 内容,并且系统会自动裁剪和缩放当前 Activity 的可见区域。
-
RemoteAction只能设置简单的按钮交互,不能显示复杂UI
-
如果 PiP 窗口和主应用交互复杂,需注意
taskAffinity和launchMode的影响:taskAffinity:避免 PiP Activity 被错误地放入其他 Task。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和权限声明
可应用场景拓展
-
导航:在接单后,司机退出App仍能悬浮显示路线,用户退出后,可悬浮路线查看司机到达位置。
-
订单提醒:后台时新订单以小窗形式弹出,避免错过订单。
-
通话兼容:接听电话时保持导航可见。