Skip to content

系统级浮窗

petterp edited this page Aug 30, 2026 · 1 revision

系统级浮窗(SystemHost)

floatingx-system 提供的 SystemHost:真正的 WindowManager 窗口,能显示在应用之外。 本页讲权限三策略与降级、LayoutParams 定制、键盘与返回键、在 Service 里安装。

是什么

SystemHost 把容器挂到 WindowManager 上,是一个独立的系统窗口,可以盖在别的应用之上。 它只需要一个 application context,不依赖 Activity,所以 Service 里也能装(#192)。

模块清单里已经声明了 SYSTEM_ALERT_WINDOW 权限与透明的权限申请页 —— 接入方不需要自己配置任何东西

完整示例

FloatingX.install("sys") {
    layout(R.layout.fx_input)
    anchor(FxGravity.TOP_START, dx = 24f, dy = 200f)
    adsorb(FxAdsorb.Edges(setOf(FxEdge.START, FxEdge.END)))
    systemHost(app) {
        permission(FxPermissionStrategy.auto())          // 默认:自动弹页申请
        // 权限被拒时降级为 App 级浮窗(相当于 2.x 的 SYSTEM_AUTO)
        fallback(AppHost.builder(app).build())
        layoutParams { it.alpha = 0.9f }                 // 在默认 LayoutParams 之后执行,可覆盖任何字段
        keyboard(R.id.etInput)                           // 触到这些 EditText 时窗口临时可聚焦、弹键盘
        onBackPressed { true }                           // 键盘弹起期间才收得到返回键
    }
}.show()

权限三策略

策略 行为
FxPermissionStrategy.auto() 默认。无权限时自动弹透明页申请,被拒后走 fallback(没配就停在 INSTALLED
FxPermissionStrategy.manual { request -> … } 交给业务方:request.proceed() 去申请 / useFallback() 直接降级 / deny() 放弃
FxPermissionStrategy.skip() 不检查权限直接挂窗口(已自行申请,或 type 不需要权限)

manual 拿到的 FxPermissionRequest 三个方法只应调用一个

systemHost(app) {
    permission(
        FxPermissionStrategy.manual { request ->
            when {
                userAgreed -> request.proceed()      // 弹系统设置页申请
                canDowngrade -> request.useFallback() // 直接降级到 fallback 指定的 host
                else -> request.deny()                // 放弃:不降级,停在 INSTALLED
            }
        },
    )
    fallback(AppHost.builder(app).build())
}

被拒后拿到权限,调用 retryPermission() 即可恢复:

(control.host as? SystemHost)?.retryPermission()

权限被拒后的降级

配了 fallback(...) 时,权限被拒会自动 requestSwap 到指定的 host(通常是 AppHost): 容器换掉,但配置、监听器、feature 与当前位置都保留 —— 这就是 2.x 的 SYSTEM_AUTO。 没配 fallback 则停在 INSTALLED,之后拿到权限再 retryPermission()

LayoutParams 定制

systemHost(app) {
    layoutParams { lp ->
        lp.alpha = 0.9f
        lp.flags = lp.flags or WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
    }
}

customizer 在默认 LayoutParams 之后执行,可以覆盖任何字段(type / flags / alpha …)。 SystemLayoutParamsCustomizerfun interface,Java 侧可以直接写 lambda。

键盘与返回键

系统窗口默认带 FLAG_NOT_FOCUSABLE(不抢焦点,不然会挡住下层应用的输入)。 用 keyboard(...) 登记内容里的 EditText id,触到这些 EditText 时窗口才临时可聚焦并弹出软键盘:

systemHost(app) {
    keyboard(R.id.etInput)          // 可变参数,能登记多个 EditText id
    onBackPressed {                 // 只有窗口可聚焦(即键盘弹起)期间才收得到返回键
        true                        // 返回 true 表示已消费
    }
}

收键盘的那一次返回键会被系统吞掉,不会走到 onBackPressed

主题

和 App 级浮窗一样,内容用 application context 创建,Material 组件需要 Material 主题:

systemHost(app) { theme(R.style.Theme_App) }

在 Service 里安装

SystemHost 只要 application context,前台 Service 里安装是完全合法的用法(#192):

class DemoService : Service() {
    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        startAsForeground()   // API 34+ 前台 Service 必须声明 foregroundServiceType,且与清单一致
        FloatingX.install("sys") { /**/ systemHost(application) { } }.show()
        return START_NOT_STICKY
    }
}

两条系统限制要注意:

  • Android 10(Q)起后台起不了 Activity:应用在后台时,auto() 策略的权限申请页可能什么都不弹。 真正的后台场景请用 manual {} / skip() 把申请推迟,等回到前台再 retryPermission()
  • Android 13(T)起通知需用户授权:没授权时 startForeground 照样成功,只是通知不显示。

浮窗的生命周期归 FloatingX 注册表(按 tag),不跟着 Service 走 —— Service 销毁时浮窗仍在, 需要收掉就自己 control.cancel()FloatingX.uninstall(tag)

Java Builder

SystemHost host = SystemHost.builder(app)
        .layoutParams(lp -> lp.alpha = 0.9f)
        .permission(FxPermissionStrategy.auto())
        .fallback(AppHost.builder(app).build())
        .build();
FxControl control = FloatingX.install("java-system", config, host);
control.show();

已知限制:Android 11 以下没有 safe area

屏幕级 insets 的公开入口 WindowManager.getCurrentWindowMetrics() 是 API 30(R)才有的, R 以下 SystemHost 拿不到 safe area,safeArea 对它不起作用(可用区就是整块屏幕)。 需要在低版本避让状态栏 / 刘海时,自己用 margin(top = …) 留出来。更多问题见 常见问题


返回 Home · 相关:App 级全局浮窗 · 配置项 · 常见问题

Clone this wiki locally