将 ADB 调用从 subprocess 改造为 adbutils 库
- 移除 subprocess.run 调用,改用 adbutils 实现持久连接 - 添加 adbutils>=2.12.0 到 requirements.txt - 更新 .gitignore 添加 Python 包相关条目 - 保持原有公共接口不变,确保兼容性
This commit is contained in:
+25
@@ -27,6 +27,10 @@ share/python-wheels/
|
|||||||
*.egg
|
*.egg
|
||||||
MANIFEST
|
MANIFEST
|
||||||
|
|
||||||
|
# PyPI
|
||||||
|
*.whl
|
||||||
|
*.tar.gz
|
||||||
|
|
||||||
# PyInstaller
|
# PyInstaller
|
||||||
# Usually these files are written by a python script from a template
|
# Usually these files are written by a python script from a template
|
||||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||||
@@ -174,3 +178,24 @@ cython_debug/
|
|||||||
# PyPI configuration file
|
# PyPI configuration file
|
||||||
.pypirc
|
.pypirc
|
||||||
|
|
||||||
|
# Virtual environments
|
||||||
|
.venv/
|
||||||
|
venv/
|
||||||
|
ENV/
|
||||||
|
env.bak/
|
||||||
|
venv.bak/
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# 开发环境
|
||||||
|
.vscode
|
||||||
|
.mimocode
|
||||||
|
.codewhale
|
||||||
|
|
||||||
|
|||||||
+108
-82
@@ -14,14 +14,14 @@ import logging
|
|||||||
import os
|
import os
|
||||||
import signal
|
import signal
|
||||||
import socket
|
import socket
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from socketserver import ThreadingUnixStreamServer, StreamRequestHandler
|
from socketserver import ThreadingUnixStreamServer, StreamRequestHandler
|
||||||
|
|
||||||
|
from adbutils import AdbClient, AdbDevice
|
||||||
|
|
||||||
# ── 常量 ────────────────────────────────────────────────────────
|
# ── 常量 ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
SCRIPT_DIR = Path(__file__).parent.resolve()
|
SCRIPT_DIR = Path(__file__).parent.resolve()
|
||||||
@@ -167,68 +167,72 @@ class TimeChecker:
|
|||||||
|
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════════════════════════
|
# ═══════════════════════════════════════════════════════════════════
|
||||||
# AdbManager — ADB 操作封装
|
# AdbManager — ADB 操作封装(使用 adbutils 库)
|
||||||
# ═══════════════════════════════════════════════════════════════════
|
# ═══════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
class AdbManager:
|
class AdbManager:
|
||||||
"""封装所有 ADB CLI 操作,所有异常都被捕获,不会导致进程退出。"""
|
"""封装所有 ADB 操作,使用 adbutils 库实现持久连接。"""
|
||||||
|
|
||||||
def __init__(self, config_getter):
|
def __init__(self, config_getter):
|
||||||
self._config_getter = config_getter
|
self._config_getter = config_getter
|
||||||
|
self._client: AdbClient | None = None
|
||||||
|
self._device: AdbDevice | None = None
|
||||||
self._ready = False
|
self._ready = False
|
||||||
self._last_error = ""
|
self._last_error = ""
|
||||||
self._screen_on_since: float | None = None
|
self._screen_on_since: float | None = None
|
||||||
self._last_known_screen_on = False
|
self._last_known_screen_on = False
|
||||||
self._last_adb_check = 0 # 限频用
|
self._last_adb_check = 0
|
||||||
|
|
||||||
# ── 底层命令执行 ──
|
# ── 连接管理 ──
|
||||||
|
|
||||||
def _exec(self, args: list, timeout: int = 10) -> tuple:
|
def _ensure_client(self) -> AdbClient:
|
||||||
"""执行 ADB 命令,返回 (returncode, stdout, stderr)。永不抛出。"""
|
"""懒初始化 ADB 客户端连接。"""
|
||||||
|
if self._client is None:
|
||||||
|
self._client = AdbClient(host="127.0.0.1", port=5037)
|
||||||
|
return self._client
|
||||||
|
|
||||||
|
def _ensure_device(self) -> AdbDevice | None:
|
||||||
|
"""获取目标设备,支持串号过滤。"""
|
||||||
config = self._config_getter()
|
config = self._config_getter()
|
||||||
adb_path = config.get("adb_path", "adb")
|
target_serial = config.get("adb_device_serial", "")
|
||||||
serial = config.get("adb_device_serial", "")
|
|
||||||
|
|
||||||
cmd = [adb_path]
|
|
||||||
if serial:
|
|
||||||
cmd.extend(["-s", serial])
|
|
||||||
cmd.extend(args)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
r = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout)
|
client = self._ensure_client()
|
||||||
return r.returncode, r.stdout.strip(), r.stderr.strip()
|
if target_serial:
|
||||||
except FileNotFoundError:
|
self._device = client.device(target_serial)
|
||||||
self._ready = False
|
else:
|
||||||
self._last_error = f"ADB 未找到({adb_path}),请安装: sudo apt install adb"
|
devices = client.device_list()
|
||||||
except subprocess.TimeoutExpired:
|
self._device = devices[0] if devices else None
|
||||||
self._ready = False
|
return self._device
|
||||||
self._last_error = "ADB 命令超时"
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self._ready = False
|
self._device = None
|
||||||
self._last_error = str(e)
|
self._last_error = str(e)
|
||||||
return -1, "", self._last_error
|
return None
|
||||||
|
|
||||||
# ── 设备管理 ──
|
# ── 设备管理 ──
|
||||||
|
|
||||||
def check_available(self) -> bool:
|
def check_available(self) -> bool:
|
||||||
"""检查 ADB 是否可执行。"""
|
"""检查 ADB Server 是否可连接。"""
|
||||||
rc, _, _ = self._exec(["--version"], timeout=5)
|
try:
|
||||||
return rc == 0
|
client = self._ensure_client()
|
||||||
|
client.device_list()
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
self._last_error = f"ADB Server 不可用: {e}"
|
||||||
|
return False
|
||||||
|
|
||||||
def list_devices(self) -> list[dict]:
|
def list_devices(self) -> list[dict]:
|
||||||
"""返回 [{"serial": "...", "status": "..."}]。"""
|
"""返回 [{"serial": "...", "status": "device"}]。"""
|
||||||
rc, out, _ = self._exec(["devices"])
|
try:
|
||||||
if rc != 0:
|
client = self._ensure_client()
|
||||||
|
devices = client.device_list()
|
||||||
|
return [{"serial": d.serial, "status": "device"} for d in devices]
|
||||||
|
except Exception as e:
|
||||||
|
self._last_error = str(e)
|
||||||
return []
|
return []
|
||||||
devices = []
|
|
||||||
for line in out.split("\n"):
|
|
||||||
parts = line.strip().split("\t")
|
|
||||||
if len(parts) >= 2 and not line.startswith("List"):
|
|
||||||
devices.append({"serial": parts[0], "status": parts[1]})
|
|
||||||
return devices
|
|
||||||
|
|
||||||
def get_ready_device(self) -> str | None:
|
def get_ready_device(self) -> str | None:
|
||||||
"""返回第一个状态为 device 的串号,无则返回 None。"""
|
"""返回第一个已连接设备的串号,无则返回 None。"""
|
||||||
for d in self.list_devices():
|
for d in self.list_devices():
|
||||||
if d["status"] == "device":
|
if d["status"] == "device":
|
||||||
return d["serial"]
|
return d["serial"]
|
||||||
@@ -237,57 +241,67 @@ class AdbManager:
|
|||||||
def is_ready(self) -> bool:
|
def is_ready(self) -> bool:
|
||||||
"""检查是否有已授权的 ADB 设备可用。"""
|
"""检查是否有已授权的 ADB 设备可用。"""
|
||||||
now = time.time()
|
now = time.time()
|
||||||
# 限频:每秒最多检测一次
|
|
||||||
if now - self._last_adb_check < 1 and self._ready:
|
if now - self._last_adb_check < 1 and self._ready:
|
||||||
return True
|
return True
|
||||||
self._last_adb_check = now
|
self._last_adb_check = now
|
||||||
|
|
||||||
config = self._config_getter()
|
config = self._config_getter()
|
||||||
target = config.get("adb_device_serial", "")
|
target_serial = config.get("adb_device_serial", "")
|
||||||
|
|
||||||
devices = self.list_devices()
|
try:
|
||||||
for d in devices:
|
client = self._ensure_client()
|
||||||
if d["status"] == "device" and (not target or d["serial"] == target):
|
devices = client.device_list()
|
||||||
self._ready = True
|
|
||||||
self._last_error = ""
|
for d in devices:
|
||||||
return True
|
if not target_serial or d.serial == target_serial:
|
||||||
|
# 尝试获取设备属性以验证连接
|
||||||
|
d.getprop("ro.build.version.sdk")
|
||||||
|
self._device = d
|
||||||
|
self._ready = True
|
||||||
|
self._last_error = ""
|
||||||
|
return True
|
||||||
|
|
||||||
|
if not devices:
|
||||||
|
self._last_error = "未检测到 ADB 设备,请连接平板"
|
||||||
|
else:
|
||||||
|
self._last_error = f"未找到指定设备: {target_serial}"
|
||||||
|
except Exception as e:
|
||||||
|
self._last_error = str(e)
|
||||||
|
|
||||||
# 诊断
|
|
||||||
unauthorized = [d for d in devices if d["status"] == "unauthorized"]
|
|
||||||
if unauthorized:
|
|
||||||
self._last_error = f"设备未授权: {[d['serial'] for d in unauthorized]},请在平板上确认授权"
|
|
||||||
elif not devices:
|
|
||||||
self._last_error = "未检测到 ADB 设备,请连接平板"
|
|
||||||
else:
|
|
||||||
self._last_error = "没有已就绪的 ADB 设备"
|
|
||||||
self._ready = False
|
self._ready = False
|
||||||
|
self._device = None
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# ── 屏幕控制 ──
|
# ── 屏幕控制 ──
|
||||||
|
|
||||||
def get_screen_state(self) -> tuple[bool, str]:
|
def get_screen_state(self) -> tuple[bool, str]:
|
||||||
"""返回 (is_on, wakefulness),失败时返回上次已知状态。"""
|
"""返回 (is_on, wakefulness),失败时返回上次已知状态。"""
|
||||||
rc, out, _ = self._exec(["shell", "dumpsys", "power"])
|
try:
|
||||||
if rc != 0:
|
device = self._ensure_device()
|
||||||
return self._last_known_screen_on, "unknown"
|
if device is None:
|
||||||
|
return self._last_known_screen_on, "unknown"
|
||||||
|
|
||||||
for line in out.split("\n"):
|
output = device.shell("dumpsys power")
|
||||||
line = line.strip()
|
for line in output.split("\n"):
|
||||||
if "mWakefulness" in line:
|
line = line.strip()
|
||||||
state = line.split("=")[-1].strip()
|
if "mWakefulness" in line:
|
||||||
is_on = state == "Awake"
|
state = line.split("=")[-1].strip()
|
||||||
self._last_known_screen_on = is_on
|
is_on = state == "Awake"
|
||||||
if is_on and self._screen_on_since is None:
|
self._last_known_screen_on = is_on
|
||||||
self._screen_on_since = time.time()
|
if is_on and self._screen_on_since is None:
|
||||||
elif not is_on:
|
self._screen_on_since = time.time()
|
||||||
self._screen_on_since = None
|
elif not is_on:
|
||||||
return is_on, state
|
self._screen_on_since = None
|
||||||
|
return is_on, state
|
||||||
|
|
||||||
|
# fallback
|
||||||
|
for line in output.split("\n"):
|
||||||
|
if "Display Power" in line or "mScreenOn" in line:
|
||||||
|
is_on = "ON" in line.upper() or "=true" in line.lower()
|
||||||
|
return is_on, "unknown"
|
||||||
|
except Exception as e:
|
||||||
|
self._last_error = str(e)
|
||||||
|
|
||||||
# fallback
|
|
||||||
for line in out.split("\n"):
|
|
||||||
if "Display Power" in line or "mScreenOn" in line:
|
|
||||||
is_on = "ON" in line.upper() or "=true" in line.lower()
|
|
||||||
return is_on, "unknown"
|
|
||||||
return self._last_known_screen_on, "unknown"
|
return self._last_known_screen_on, "unknown"
|
||||||
|
|
||||||
def get_screen_on_duration(self) -> float:
|
def get_screen_on_duration(self) -> float:
|
||||||
@@ -299,23 +313,35 @@ class AdbManager:
|
|||||||
def screen_off(self) -> bool:
|
def screen_off(self) -> bool:
|
||||||
"""发送息屏命令。"""
|
"""发送息屏命令。"""
|
||||||
log.info("执行息屏操作")
|
log.info("执行息屏操作")
|
||||||
rc, _, err = self._exec(["shell", "input", "keyevent", "26"])
|
try:
|
||||||
if rc == 0:
|
device = self._ensure_device()
|
||||||
|
if device is None:
|
||||||
|
log.error("息屏失败: 无可用设备")
|
||||||
|
return False
|
||||||
|
device.shell("input keyevent 26")
|
||||||
self._screen_on_since = None
|
self._screen_on_since = None
|
||||||
return True
|
return True
|
||||||
log.error("息屏失败: %s", err)
|
except Exception as e:
|
||||||
return False
|
self._last_error = str(e)
|
||||||
|
log.error("息屏失败: %s", e)
|
||||||
|
return False
|
||||||
|
|
||||||
def screen_on(self) -> bool:
|
def screen_on(self) -> bool:
|
||||||
"""发送亮屏并解锁命令。"""
|
"""发送亮屏并解锁命令。"""
|
||||||
log.info("执行亮屏操作")
|
log.info("执行亮屏操作")
|
||||||
rc, _, err = self._exec(["shell", "input", "keyevent", "26"])
|
try:
|
||||||
if rc == 0:
|
device = self._ensure_device()
|
||||||
|
if device is None:
|
||||||
|
log.error("亮屏失败: 无可用设备")
|
||||||
|
return False
|
||||||
|
device.shell("input keyevent 26")
|
||||||
self._screen_on_since = time.time()
|
self._screen_on_since = time.time()
|
||||||
self._exec(["shell", "input", "keyevent", "82"]) # 解锁(MENU)
|
device.shell("input keyevent 82") # 解锁(MENU)
|
||||||
return True
|
return True
|
||||||
log.error("亮屏失败: %s", err)
|
except Exception as e:
|
||||||
return False
|
self._last_error = str(e)
|
||||||
|
log.error("亮屏失败: %s", e)
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════════════════════════
|
# ═══════════════════════════════════════════════════════════════════
|
||||||
|
|||||||
+2
-1
@@ -1,5 +1,6 @@
|
|||||||
# padsleep_adb 依赖
|
# padsleep_adb 依赖
|
||||||
# 本程序仅使用 Python 标准库,无需额外安装第三方包。
|
adbutils>=2.12.0
|
||||||
|
|
||||||
# 系统依赖:adb (Android Debug Bridge)
|
# 系统依赖:adb (Android Debug Bridge)
|
||||||
# 安装: sudo apt-get install adb
|
# 安装: sudo apt-get install adb
|
||||||
#
|
#
|
||||||
|
|||||||
Reference in New Issue
Block a user