fix: clean failed UIAutomator2 startups before retrying

This commit is contained in:
chenmingxuan committed 2026-10-12 10:58:47 +08:00
1 parent 4d6560dbd4
commit 3fbcdfefea
4 files changed
+304 -5

No files matched your search

+46 -2
View File
@@ -247,6 +247,48 @@ def _ensure_maestro_not_installed(device_id: str) -> None:
_uninstall_package(device_id, MAESTRO_PACKAGE)
class UIAutomatorConnectionError(RuntimeError):
"""Terminal connection failure with device context and the original cause."""
def __init__(self, device_id: str, attempts: int, cause: Exception) -> None:
# The library appends its whole output buffer on every readiness poll.
# Keep the original exception as __cause__, but do not repeat those
# identical startup lines dozens of times in the user-facing failure.
lines = [line for arg in cause.args for line in str(arg).splitlines()]
detail = "\n".join(dict.fromkeys(lines)) or str(cause)
super().__init__(
f"UIAutomator2 连接失败(设备:{device_id};已尝试 {attempts} 次)。"
f"\n底层原因:{type(cause).__name__}: {detail}"
)
class _ManagedUIAutomatorDevice(u2.Device):
"""Release a failed launch before the constructor can lose its handle.
UIAutomator2 registers exit cleanup only after startup succeeds. Its
partially constructed Device is not returned when readiness fails, so
the caller cannot stop that launch. Intercept the startup boundary here
instead of modifying the installed library or killing device processes.
"""
def start_uiautomator(self) -> None:
try:
super().start_uiautomator()
except BaseException:
# _process is set only for a server launched by this instance;
# attaching to a server already on the device leaves it None.
if self._process is not None:
try:
super().stop_uiautomator(wait=False)
except Exception as cleanup_error:
# Cleanup must never hide the startup error or interrupt.
logger.warning(
f"Failed to stop this UIAutomator2 launch on {self._dev.serial}: "
f"{cleanup_error}"
)
raise
class UIAutomatorClient:
"""UIAutomator2 client for Android screen data retrieval.
@@ -297,7 +339,7 @@ class UIAutomatorClient:
if attempt:
time.sleep(1.0 * attempt)
try:
self._device = u2.connect(self._device_id)
self._device = _ManagedUIAutomatorDevice(self._device_id)
break
except Exception as exc:
last_error = exc
@@ -307,7 +349,9 @@ class UIAutomatorClient:
)
else:
self._awake_strategy = None
raise last_error
raise UIAutomatorConnectionError(
self._device_id, attempt + 1, last_error
) from last_error
logger.info("UIAutomator2 connected successfully")
return self._device