#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Android 真机管理模块。 """ from __future__ import annotations import logging import subprocess import time from typing import Optional from utils_android.device_config import ensure_android_wireless_connected, get_android_device_serial logger = logging.getLogger(__name__) class PhysicalDeviceManager: """真机设备管理器。 支持通过配置指定 USB serial 或无线调试 ip:port。 """ def __init__(self, config): self.config = config self.serial = get_android_device_serial(config) self.boot_timeout_seconds = int(self.config.get("PHYSICAL_BOOT_TIMEOUT_SECONDS", 180)) self.boot_poll_interval_seconds = int(self.config.get("PHYSICAL_BOOT_POLL_INTERVAL_SECONDS", 5)) if self.serial: logger.info(f"真机 ADB 目标: {self.serial}") @property def device_type(self) -> str: return "physical" def _run_adb(self, args, timeout: int = 15) -> subprocess.CompletedProcess[str]: cmd = ["adb"] if self.serial: cmd.extend(["-s", self.serial]) cmd.extend(args) return subprocess.run( cmd, capture_output=True, encoding="utf-8", errors="ignore", timeout=timeout, ) def _read_output(self, result: subprocess.CompletedProcess[str]) -> str: return (result.stdout or result.stderr or "").strip() def _ensure_connected(self) -> bool: return ensure_android_wireless_connected(self.config, self.serial) def _wait_for_boot_complete(self, timeout_seconds: Optional[int] = None) -> bool: deadline = time.time() + float(timeout_seconds or self.boot_timeout_seconds) while time.time() < deadline: try: self._ensure_connected() state_result = self._run_adb(["get-state"], timeout=10) state = self._read_output(state_result).lower() if state_result.returncode != 0 or state != "device": time.sleep(self.boot_poll_interval_seconds) continue boot_result = self._run_adb(["shell", "getprop", "sys.boot_completed"], timeout=10) boot_completed = self._read_output(boot_result) if boot_result.returncode == 0 and boot_completed == "1": sdk_result = self._run_adb(["shell", "getprop", "ro.build.version.sdk"], timeout=10) if sdk_result.returncode == 0 and self._read_output(sdk_result): logger.info("真机重启完成,ADB 已恢复可用") return True except subprocess.TimeoutExpired: logger.warning("等待真机启动完成超时,继续轮询") except Exception as exc: logger.warning(f"等待真机启动完成时出现异常: {exc}") time.sleep(self.boot_poll_interval_seconds) logger.error("等待真机启动完成超时") return False def check_status(self) -> bool: try: if not self._ensure_connected(): return False state_result = self._run_adb(["get-state"], timeout=10) state = self._read_output(state_result).lower() if state_result.returncode != 0 or state != "device": logger.warning(f"真机 ADB 状态异常: {self._read_output(state_result) or state or 'unknown'}") return False verify_result = self._run_adb(["shell", "getprop", "ro.build.version.sdk"], timeout=10) if verify_result.returncode == 0 and self._read_output(verify_result): return True logger.warning(f"真机 ADB 校验失败: {self._read_output(verify_result)}") return False except subprocess.TimeoutExpired: logger.warning("检查真机状态超时") return False except FileNotFoundError: logger.error("未找到 adb,可执行文件不可用") return False except Exception as exc: logger.warning(f"检查真机状态失败: {exc}") return False def ensure_running(self) -> tuple[bool, bool]: return (self.check_status(), False) def restart(self) -> bool: logger.info("正在重启真机...") try: result = self._run_adb(["reboot"], timeout=20) if result.returncode != 0: logger.error(f"真机重启命令失败: {self._read_output(result)}") return False time.sleep(3) return self._wait_for_boot_complete() except subprocess.TimeoutExpired: logger.warning("adb reboot 等待超时,继续等待设备上线") return self._wait_for_boot_complete() except Exception as exc: logger.error(f"真机重启失败: {exc}") return False def recover(self) -> bool: logger.info("真机模式不执行 recover,直接跳过") return True def recover_from_frozen(self) -> bool: logger.warning("检测到真机可能卡死,尝试重启恢复") return self.restart() def is_player_frozen(self) -> bool: return not self.check_status()