Skip to content

Shower 虚拟屏无法"后台无窗"运行:子代理截图强依赖主屏 overlay(附 1 行修复建议) #1371

Open

Description

@7620qwertqwert-beep

问题

对虚拟屏 agent,PhoneAgent.captureScreenshotViaShower() 是从 VirtualDisplayOverlay 里的 SurfaceView 用 PixelCopy 抓帧的:

PhoneAgent.captureScreenshotViaShower()
 → VirtualDisplayOverlay.captureCurrentFramePng()
 → ShowerSurfaceView.captureCurrentFramePng()
 → ShowerVideoRenderer.captureCurrentFramePng()      // PixelCopy.request(Surface, bitmap)

所以,只要用户不想在主屏上看到那个悬浮预览窗(或 SYSTEM_ALERT_WINDOW 被拒/未授予):

  • ensureOverlay() 的 WindowManager.addView() 抛异常 → overlayView = null → surfaceView = null
  • captureCurrentFramePng() 返回 null
  • 流程回退到 MediaProjectionCaptureManager(只能截默认屏)
  • 结果:子代理看到的是主屏,既看不到也操作不了虚拟屏;而虚拟屏本身其实运行正常。

实测(Android 14 / Shower 模式):

appops set com.ai.assistance.operit SYSTEM_ALERT_WINDOW deny
# 重建虚拟屏:display 有效,目标 App(com.baidu.netdisk)在虚拟屏中正常启动
dumpsys display | grep ShowerVirtualDisplay      # -> ShowerVirtualDisplay-xxx 存在,state ON
# 但 UI 子代理读屏读到的却是主屏聊天界面(回退到了 MediaProjection)

已有能力(现成,但虚拟屏分支没用上)

IShowerService.requestScreenshot(int displayId) → DisplayCapture.captureDisplay(displayId):
用 ImageReader 镜像目标 display 直接出 PNG,全程不需要任何窗口,对 displayId=0 与虚拟屏一视同仁(scrcpy 同款做法)。目前只有主屏 agent 调用它。

建议修复

  1. (1 行) 虚拟屏 agent 的截图也走 helper:
- val pngBytes = if (isMainScreenAgent()) {
-     ShowerController.requestScreenshot(agentId)
- } else {
-     VirtualDisplayOverlay.getInstance(context, agentId).captureCurrentFramePng()
- }
+ // DisplayCapture mirrors the target display into an ImageReader, so this works for
+ // both displayId=0 and Shower virtual displays, with no overlay window required.
+ val pngBytes = ShowerController.requestScreenshot(agentId)
  1. (可选) 新增一个"后台虚拟屏"偏好:后台模式不创建 VirtualDisplayOverlay(其它 overlay,如悬浮球/进度 HUD 不受影响)。

这样虚拟屏就能完全无窗口地后台运行(主屏零窗口、连 SYSTEM_ALERT_WINDOW 都不需要),同时子代理仍能看到画面。

附带修正:helper 按真实 display 尺寸出图,可顺带避免 ShowerCodecAdapter 降分辨率后"截图尺寸与点击坐标不一致"的问题。

环境

Android 14 (SDK 34) / arm64-v8a / 无 root / Shizuku (shell, uid2000) / Operit 1.12.1

风险

方案 1 依赖"helper 能镜像一个 VIRTUAL_DISPLAY_FLAG_PUBLIC 的虚拟屏"这一前提(需设备实测,可能性高)。若不成立,行为与现状一致(回退主屏),不会更糟。

Activity

  1. yoruuuchan commented on Oct 22, 2026

    @yoruuuchan
    Contributor

    我在按这个 issue 做修复,先在这里认领一下,避免重复劳动。

    计划:

    1. 虚拟屏 agent 的截图统一走 ShowerController.requestScreenshot(agentId) → DisplayCapture.captureDisplay(displayId),去掉对 VirtualDisplayOverlay / PixelCopy 的依赖。
    2. 核对 agentId → displayId 的映射关系,确保虚拟屏 agent 始终抓的是它自己的那块 display,不会抓到主屏。
    3. 截图失败时给出明确诊断(display id / 真实尺寸 / 失败原因),不再静默落到主屏路径。
    4. 主屏 agent 和虚拟屏交互的现有行为保持不变。

    分支会基于 upstream/dev 新建,PR 关联 Fixes #1371。

    说明一点:我这边目前没有连接 Android 真机,所以有 overlay / 无 overlay 两种情况下的真机实测结果,我会在 PR 里准确标注哪些做了、哪些没做。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions