Repository navigation
fix(Device): 适配 MuMu 6.0 (Android 15) 多虚拟显示 - #1952
Enough1122 wants to merge 2 commits into
Conversation
MuMu 6.0 的 A15 引擎给每个 app 分配独立虚拟显示(mumuscreenNNN), 而 screencap/input 默认作用于 display 0(桌面),导致截图与点击全部落在桌面。 - 新增 _mumu_display_ids():定位游戏所在的非 0 显示并解析 SurfaceFlinger 长 id - 截图走 screencap -d <long id>,点击/滑动/长按走 input -d <small id> - app_start/app_stop 清缓存(每次启动显示 id 会变);负结果缓存 20s - 门控不满足时命令形状与改动前完全一致(老 MuMu 12 不受影响)
Findings
Warning Firewall blocked 1 domainThe following domain was blocked by the firewall during workflow execution:
To allow these domains, add them to the network:
allowed:
- defaults
- "ab.chatgpt.com"See Network Configuration for more information.
|
There was a problem hiding this comment.
嘿——我发现了 5 个问题
AI 代理提示词
请处理这次代码审查中的评论:
## 各条评论
### 评论 1
<location path="module/device/method/adb.py" line_range="198" />
<code_context>
+ if m:
+ current = int(m.group(1))
+ continue
+ if current and 'mFocusedApp=ActivityRecord' in line and 'launcher' not in line.lower():
+ display_id = current
+ break
</code_context>
<issue_to_address>
**截图指向了另一个应用**
当另一个非启动器应用在游戏显示屏之前列出的非零 MuMu 显示屏上处于前台时,`_mumu_display_ids()` 不会将前台 Activity 与 `self.package` 匹配,就接受了该显示屏,并缓存其 ID,导致截图和输入被发送到另一个应用,而不是游戏。
接受该显示屏之前,请将前台 Activity 与 `self.package` 进行匹配。
</issue_to_address>
### 评论 2
<location path="module/device/method/adb.py" line_range="361" />
<code_context>
if not package_name:
package_name = self.package
+ # MuMu 6.0 assigns the app a new virtual display on every launch
+ self._reset_mumu_display_ids()
result = self.adb_shell([
'monkey', '-p', package_name, '-c',
</code_context>
<issue_to_address>
**ADB 仍在使用旧显示屏**
当应用通过绕过 ADB 重置方法的生命周期路径停止或重新启动时,`AppControl.app_start()` 和 `app_stop()` 可能会使用 uiautomator2,却没有清除 ADB 显示屏缓存。因此,后续 ADB 截图和输入会重复使用旧显示屏 ID,并指向错误的显示屏。
无论选择哪种启动或停止方法,都应在应用共用的生命周期路径中清除 ADB 显示屏缓存。
另请参阅 `module/device/method/adb.py:179-180`、`module/device/method/adb.py:224`、`module/device/method/adb.py:412`。
</issue_to_address>
### 评论 3
<location path="module/device/method/adb.py" line_range="187" />
<code_context>
+ now = time.monotonic()
+ if now < self._mumu_display_ids_retry_at:
+ return None
+ self._mumu_display_ids_retry_at = now + self._MUMU_DISPLAY_RETRY_SEC
+ try:
+ # 1) Which display is the game running on? (skip display 0, the desktop)
</code_context>
<issue_to_address>
**游戏检测仍停留在桌面**
如果在 MuMu 暴露游戏所在的前台虚拟显示屏之前执行显示屏查询,或者 `dumpsys` 调用暂时失败,`_mumu_display_ids()` 会在检查显示屏之前设置 20 秒的重试截止时间,并在查询失败或显示屏尚未就绪时返回 `None`。这样一来,即使游戏显示屏随后已经可用,截图和输入仍会使用显示屏 0,导致启动检测和交互失败。
查询失败或显示屏尚未就绪时,不要将结果缓存整个重试窗口;应尽快重试,包括在启动后重试。
另请参阅 `module/device/method/adb.py:227-228`、`module/device/method/adb.py:361`。
</issue_to_address>
### 评论 4
<location path="module/device/method/adb.py" line_range="224" />
<code_context>
+ if long_id is None:
+ return None
+ logger.info(f'MuMu multi-display: game on display {display_id} ({name}), screencap id {long_id}')
+ self._mumu_display_ids_cache = (display_id, long_id)
+ return self._mumu_display_ids_cache
+ except Exception as e:
</code_context>
<issue_to_address>
**过时的显示屏 ID 覆盖了重置操作**
如果显示屏检测与应用启动或停止同时发生,`_mumu_display_ids()` 可能会在 `_reset_mumu_display_ids()` 执行后才完成,并将旧显示屏 ID 写入缓存。之后的截图和输入便会指向已销毁的显示屏。
请阻止在缓存重置之前启动的检测在重置之后发布其结果。
另请参阅 `module/device/method/adb.py:167-170`。
</issue_to_address>
### 评论 5
<location path="module/device/method/adb.py" line_range="190" />
<code_context>
+ self._mumu_display_ids_retry_at = now + self._MUMU_DISPLAY_RETRY_SEC
+ try:
+ # 1) Which display is the game running on? (skip display 0, the desktop)
+ out = self.adb_shell(['dumpsys', 'window', 'displays'])
+ display_id = None
+ current = None
</code_context>
<issue_to_address>
**检测耗时过长会延误命令**
当设备对显示屏检测的 `dumpsys` 调用响应缓慢时,每次 `dumpsys` 调用都会使用 `adb_shell` 默认的 10 秒超时,而检测回退逻辑会吞掉异常。因此,设备响应慢或无响应时,截图或输入处理可能会被卡住,直到代码回退到显示屏 0;这会延误任务,还可能导致操作落在桌面上。请为显示屏检测使用有上限且更短的超时时间,并让调用方能够获知回退情况。
为检测调用显式设置更短的超时时间,并在检测失败时将失败情况反馈出来,而不是静默地继续使用显示屏 0。
另请参阅 `module/device/method/adb.py:227`。
</issue_to_address>Sourcery 评估
等待批准。 请先处理 5 项问题。
阻塞性问题:module/device/method/adb.py:198、module/device/method/adb.py:361、module/device/method/adb.py:187、module/device/method/adb.py:224、module/device/method/adb.py:190
Original comment in English
Hey - I've found 5 issues
Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments
### Comment 1
<location path="module/device/method/adb.py" line_range="198" />
<code_context>
+ if m:
+ current = int(m.group(1))
+ continue
+ if current and 'mFocusedApp=ActivityRecord' in line and 'launcher' not in line.lower():
+ display_id = current
+ break
</code_context>
<issue_to_address>
**Screenshots target another app**
When another non-launcher app is focused on a nonzero MuMu display listed before the game’s display, `_mumu_display_ids()` accepts that display without matching the focused activity to `self.package`, caches its IDs, and sends screenshots and input to the other app instead of the game.
Match the focused activity against `self.package` before accepting its display.
</issue_to_address>
### Comment 2
<location path="module/device/method/adb.py" line_range="361" />
<code_context>
if not package_name:
package_name = self.package
+ # MuMu 6.0 assigns the app a new virtual display on every launch
+ self._reset_mumu_display_ids()
result = self.adb_shell([
'monkey', '-p', package_name, '-c',
</code_context>
<issue_to_address>
**ADB keeps using an old display**
When the app is stopped or relaunched through a lifecycle path that bypasses the ADB reset methods, `AppControl.app_start()` and `app_stop()` can use uiautomator2 without clearing the ADB display cache, so subsequent ADB screenshots and input reuse the old display ID and target the wrong display.
Clear the ADB display cache in the shared app lifecycle paths regardless of which start or stop method they select.
Also at `module/device/method/adb.py:179-180`, `module/device/method/adb.py:224`, `module/device/method/adb.py:412`.
</issue_to_address>
### Comment 3
<location path="module/device/method/adb.py" line_range="187" />
<code_context>
+ now = time.monotonic()
+ if now < self._mumu_display_ids_retry_at:
+ return None
+ self._mumu_display_ids_retry_at = now + self._MUMU_DISPLAY_RETRY_SEC
+ try:
+ # 1) Which display is the game running on? (skip display 0, the desktop)
</code_context>
<issue_to_address>
**Game detection stays on desktop**
When a display lookup runs before MuMu exposes the game’s focused virtual display, or a `dumpsys` call fails transiently, `_mumu_display_ids()` sets the 20-second retry deadline before checking for the display and returns `None` on a failed or not-yet-ready lookup. Screenshots and input then use display 0 even after the game display is available, so startup detection and interaction fail.
Do not cache failed or not-yet-ready lookups for the full retry window; retry promptly, including after launch.
Also at `module/device/method/adb.py:227-228`, `module/device/method/adb.py:361`.
</issue_to_address>
### Comment 4
<location path="module/device/method/adb.py" line_range="224" />
<code_context>
+ if long_id is None:
+ return None
+ logger.info(f'MuMu multi-display: game on display {display_id} ({name}), screencap id {long_id}')
+ self._mumu_display_ids_cache = (display_id, long_id)
+ return self._mumu_display_ids_cache
+ except Exception as e:
</code_context>
<issue_to_address>
**Stale display IDs overwrite resets**
When display detection overlaps an app start or stop, `_mumu_display_ids()` can finish after `_reset_mumu_display_ids()` and publish the old display ID to the cache. Later screenshots and input then target the destroyed display.
Prevent a detection started before a cache reset from publishing its result afterward.
Also at `module/device/method/adb.py:167-170`.
</issue_to_address>
### Comment 5
<location path="module/device/method/adb.py" line_range="190" />
<code_context>
+ self._mumu_display_ids_retry_at = now + self._MUMU_DISPLAY_RETRY_SEC
+ try:
+ # 1) Which display is the game running on? (skip display 0, the desktop)
+ out = self.adb_shell(['dumpsys', 'window', 'displays'])
+ display_id = None
+ current = None
</code_context>
<issue_to_address>
**Slow detection delays commands**
When a device responds slowly to a display-detection `dumpsys` call, each `dumpsys` call uses `adb_shell`’s default 10-second timeout, and an exception is swallowed by the detection fallback. A slow or unresponsive device can therefore stall screenshot or input handling before the code falls back to display 0, leaving the task delayed and potentially operating on the desktop. Use a bounded, shorter timeout for display detection and make the fallback visible to callers.
Apply a shorter explicit timeout to detection calls and surface detection failure rather than silently continuing with display 0.
Also at `module/device/method/adb.py:227`.
</issue_to_address>Sourcery assessment
Approval pending. 5 findings to address first.
Blocking findings: module/device/method/adb.py:198, module/device/method/adb.py:361, module/device/method/adb.py:187, module/device/method/adb.py:224, module/device/method/adb.py:190
- 优先选择承载本包窗口的显示,系统弹窗抢焦点时退回首个非桌面前台显示 - 未就绪/异常的探测不再缓存,仅确认无虚拟显示时才短路 20s - 探测期间 app 重启(代际号变化)则丢弃结果,不写入过期显示 id - 检测用 dumpsys 走 3s 短超时 - app_start/app_stop 的缓存清理移到共用生命周期,覆盖 u2/adb 两条分支 - 新增 tests/module/device/test_mumu_display_ids.py(7 个用例,模拟 dumpsys 输出)
|
你能否先讲一下怎么可以配置出模拟器多虚拟显示 |
|
可以。这个不是配置出来的——升级到 Android 15 引擎后,应用会自己跑在独立的虚拟屏上,桌面还是在 0 号屏,不需要动任何多屏相关的设置。 我们的环境:
复现步骤:
adb connect 127.0.0.1:16416
adb shell dumpsys SurfaceFlinger --display-id
# Display 4619827820427265280 (HWC display 0): ... displayName="mumuscreen000" ← 桌面
# Display 4619827767814508545 (HWC display 1): ... displayName="mumuscreen001"
# Display 4619826888814064386 (HWC display 2): ... displayName="mumuscreen002"
adb shell dumpsys window displays | grep -E "mDisplayId=|mFocusedApp=ActivityRecord"
# mDisplayId=0 → app.lawnchair/.LawnchairLauncher(桌面)
# mDisplayId=2 → com.netease.onmyoji.../Client(游戏;屏号会变,我们也见过 3)如果只看到 mDisplayId=0、游戏也在 0 号屏,说明实例还没升到 A15 引擎。 补丁在意的两个 id 口径(实测最容易踩的坑):
所以补丁是"探测到才启用":非 0 号屏 + 该屏有前台 Activity + 屏名匹配 另外 CI 提到的那组 mock dumpsys 单测,我可以在这个 PR 上补;A12 实机回归还是得借一台旧引擎实例。 |
问题
MuMu 6.0(Android 15 引擎)给每个 app 分配独立的虚拟显示(
mumuscreenNNN),而screencap/input不带-d时默认作用于 display 0(桌面)。结果 OAS 截到的是桌面、点击也落在桌面,页面识别全部失败。本机诊断(MuMu 6.0,
127.0.0.1:16416):input同理:input -d <small display id> tap x y才能点进游戏。改动
_mumu_display_ids():从dumpsys window displays找「非 0 且带前台 ActivityRecord」的显示,dumpsys display取mumuscreenNNN名字,再用dumpsys SurfaceFlinger --display-id解析长 id;结果缓存screenshot_adb/screenshot_adb_nc:追加screencap -d <long id>click_adb/swipe_adb/long_click_adb/ motionevent 滑动:统一走_input_prefix(),即input -d <small id>app_start_adb/app_stop_adb:清缓存(每次启动虚拟显示 id 会变)dumpsys而变慢兼容性
三重门控:非 0 显示上有前台 ActivityRecord + 显示名匹配
mumuscreen\d++ SurfaceFlinger 能解析出长 id。任一不满足就返回 None,所有命令保持改动前的形状。老 MuMu 12 上游戏在 display 0,按设计不会激活。诚实声明:回退路径是按机制推理 + 历史抓帧判断的,没有在 Android 12 引擎实例上实测(手头只有 A15 实例)。有 A12 环境的朋友麻烦帮忙验一下,有问题我改。
验证
2026-10-18 起在本机 MuMu 6.0 / Android 15 上:
screenshot_adb_nc/screenshot_adb均返回 1280x720 游戏画面,任务正常完成:Page detect miss/Game page unknown)Sourcery 总结
在 MuMu 6.0 的虚拟显示屏上定位应用,确保截图和用户输入能够可靠地传送到正在运行的游戏。
Bug Fixes:
Enhancements:
Original summary in English
Sourcery 摘要
将 MuMu 6.0 应用定位到其虚拟显示屏上,确保截图和用户输入能够可靠地传递到正在运行的游戏。
错误修复:
功能增强:
Original summary in English
Sourcery 总结
将 MuMu 6.0 上的截图和输入操作定位到运行应用的虚拟显示屏。
错误修复:
功能增强:
测试:
Original summary in English
Summary by Sourcery
将 MuMu 6.0 上的截图和输入操作定位到运行应用的虚拟显示屏。
Bug Fixes:
Enhancements:
Tests: