继续优化批量停止脚本
This commit is contained in:
28
.idea/workspace.xml
generated
28
.idea/workspace.xml
generated
@@ -5,7 +5,9 @@
|
||||
</component>
|
||||
<component name="ChangeListManager">
|
||||
<list default="true" id="eceeff5e-51c1-459c-a911-d21ec090a423" name="Changes" comment="20250904-初步功能已完成">
|
||||
<change beforePath="$PROJECT_DIR$/Utils/ThreadManager.py" beforeDir="false" afterPath="$PROJECT_DIR$/Utils/ThreadManager.py" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/resources/bb5886c82b356593c2b3d917d578862cf0abf9c0/bgv.png" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/Module/DeviceInfo.py" beforeDir="false" afterPath="$PROJECT_DIR$/Module/DeviceInfo.py" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/script/ScriptManager.py" beforeDir="false" afterPath="$PROJECT_DIR$/script/ScriptManager.py" afterDir="false" />
|
||||
</list>
|
||||
<option name="SHOW_DIALOG" value="false" />
|
||||
@@ -57,6 +59,7 @@
|
||||
"Python.123.executor": "Run",
|
||||
"Python.Main.executor": "Run",
|
||||
"Python.Test.executor": "Run",
|
||||
"Python.test (1).executor": "Run",
|
||||
"Python.test.executor": "Run",
|
||||
"Python.tidevice_entry.executor": "Run",
|
||||
"RunOnceActivity.ShowReadmeOnStart": "true",
|
||||
@@ -179,6 +182,28 @@
|
||||
<option name="INPUT_FILE" value="" />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
<configuration name="test (1)" type="PythonConfigurationType" factoryName="Python" temporary="true" nameIsGenerated="true">
|
||||
<module name="iOSAI" />
|
||||
<option name="ENV_FILES" value="" />
|
||||
<option name="INTERPRETER_OPTIONS" value="" />
|
||||
<option name="PARENT_ENVS" value="true" />
|
||||
<envs>
|
||||
<env name="PYTHONUNBUFFERED" value="1" />
|
||||
</envs>
|
||||
<option name="SDK_HOME" value="" />
|
||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
|
||||
<option name="IS_MODULE_SDK" value="true" />
|
||||
<option name="ADD_CONTENT_ROOTS" value="true" />
|
||||
<option name="ADD_SOURCE_ROOTS" value="true" />
|
||||
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/test.py" />
|
||||
<option name="PARAMETERS" value="" />
|
||||
<option name="SHOW_COMMAND_LINE" value="false" />
|
||||
<option name="EMULATE_TERMINAL" value="false" />
|
||||
<option name="MODULE_MODE" value="false" />
|
||||
<option name="REDIRECT_INPUT" value="false" />
|
||||
<option name="INPUT_FILE" value="" />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
<configuration name="test" type="PythonConfigurationType" factoryName="Python" temporary="true" nameIsGenerated="true">
|
||||
<module name="iOSAI" />
|
||||
<option name="ENV_FILES" value="" />
|
||||
@@ -204,6 +229,7 @@
|
||||
</configuration>
|
||||
<recent_temporary>
|
||||
<list>
|
||||
<item itemvalue="Python.test (1)" />
|
||||
<item itemvalue="Python.test" />
|
||||
<item itemvalue="Python.123" />
|
||||
<item itemvalue="Python.Test" />
|
||||
|
||||
@@ -317,59 +317,86 @@ class Deviceinfo(object):
|
||||
|
||||
# 检测usb是否超时
|
||||
def _usb_client_with_timeout(self,udid: str, timeout: 8):
|
||||
LogManager.info(f"[CONNECT FLOW] 即将创建 USBClient(超时 {timeout}s)", udid)
|
||||
with ThreadPoolExecutor(max_workers=1) as exe:
|
||||
fut = exe.submit(wda.USBClient, udid, 8100)
|
||||
return fut.result(timeout=timeout)
|
||||
|
||||
try:
|
||||
client = fut.result(timeout=timeout)
|
||||
LogManager.info("[CONNECT FLOW] USBClient 创建成功", udid)
|
||||
return client
|
||||
except Exception as e:
|
||||
LogManager.error(f"[CONNECT FLOW] USBClient 创建失败(超时或异常): {e}", udid)
|
||||
return None
|
||||
# ------------------------------------------------------------------
|
||||
# 线程池里真正干活的地方(原 connectDevice 逻辑搬过来)
|
||||
# ------------------------------------------------------------------
|
||||
def _connect_device_task(self, udid: str):
|
||||
if not self.is_device_trusted(udid):
|
||||
LogManager.warning("设备未信任,跳过 WDA 启动", udid)
|
||||
return
|
||||
LogManager.info(f"[CONNECT FLOW] >>>>>>>>> 开始处理设备 {udid} >>>>>>>>>", udid)
|
||||
|
||||
d = self._usb_client_with_timeout(udid, 8) # 8 秒必返
|
||||
if d is None:
|
||||
LogManager.info(f"设备链接超时,{udid}")
|
||||
# 1. 信任检测
|
||||
trusted = self.is_device_trusted(udid)
|
||||
LogManager.info(f"[CONNECT FLOW] 信任检测结果 trusted={trusted}", udid)
|
||||
if not trusted:
|
||||
LogManager.warning("[CONNECT FLOW] 设备未信任,直接返回", udid)
|
||||
return
|
||||
LogManager.info("[CONNECT FLOW] 信任检测通过,准备创建 USBClient", udid)
|
||||
|
||||
# 2. 创建 WDA 客户端(带超时)
|
||||
try:
|
||||
d = wda.USBClient(udid, 8100)
|
||||
d = self._usb_client_with_timeout(udid, 8)
|
||||
if d is None:
|
||||
LogManager.error("[CONNECT FLOW] USBClient 返回 None(超时或异常),直接返回", udid)
|
||||
return
|
||||
LogManager.info("[CONNECT FLOW] USBClient 创建成功", udid)
|
||||
except Exception as e:
|
||||
LogManager.error(f"启动 WDA 失败: {e}", udid)
|
||||
LogManager.error(f"[CONNECT FLOW] USBClient 抛异常: {e}", udid)
|
||||
return
|
||||
|
||||
# 3. 读取屏幕信息
|
||||
width, height, scale = 0, 0, 1.0
|
||||
try:
|
||||
size = d.window_size()
|
||||
width, height = size.width, size.height
|
||||
scale = d.scale
|
||||
LogManager.info(f"[CONNECT FLOW] 屏幕信息 width={width} height={height} scale={scale}", udid)
|
||||
except Exception as e:
|
||||
LogManager.warning(f"读取屏幕信息失败:{e}", udid)
|
||||
LogManager.warning(f"[CONNECT FLOW] 读取屏幕信息失败: {e},继续使用默认值", udid)
|
||||
|
||||
# 4. 分配端口
|
||||
port = self._alloc_port()
|
||||
model = DeviceModel(udid, port, width, height, scale, type=1)
|
||||
LogManager.info(f"[CONNECT FLOW] 分配投屏端口 {port}", udid)
|
||||
|
||||
# 先做完所有 IO,再抢锁写内存
|
||||
# 5. 启动 WDA
|
||||
try:
|
||||
LogManager.info("[CONNECT FLOW] 正在启动 WDA 应用", udid)
|
||||
d.app_start(WdaAppBundleId)
|
||||
d.home()
|
||||
LogManager.info("[CONNECT FLOW] WDA 应用启动完成,等待 2s", udid)
|
||||
except Exception as e:
|
||||
LogManager.warning(f"启动/切回桌面失败:{e}", udid)
|
||||
|
||||
time.sleep(2) # 原逻辑保留
|
||||
LogManager.warning(f"[CONNECT FLOW] 启动 WDA 失败: {e},仍继续", udid)
|
||||
time.sleep(2)
|
||||
|
||||
# 6. 启动 iproxy
|
||||
LogManager.info(f"[CONNECT FLOW] 准备启动 iproxy 本地 {port} -> 设备 9100", udid)
|
||||
target = self.relayDeviceScreenPort(udid, port)
|
||||
if target is None:
|
||||
LogManager.error("[CONNECT FLOW] iproxy 启动失败,释放端口并返回", udid)
|
||||
self._free_port(port)
|
||||
return
|
||||
LogManager.info("[CONNECT FLOW] iproxy 启动成功,进程已注册", udid)
|
||||
|
||||
# 毫秒级临界区
|
||||
# 7. 抢锁写内存
|
||||
with self._lock:
|
||||
if udid in self._model_index: # 并发防重
|
||||
if udid in self._model_index:
|
||||
LogManager.warning(f"[CONNECT FLOW] 并发重复,已存在内存中,放弃本次任务", udid)
|
||||
self._terminate_proc(target) # 避免孤儿
|
||||
self._free_port(port)
|
||||
return
|
||||
model = DeviceModel(udid, port, width, height, scale, type=1)
|
||||
self._add_model(model)
|
||||
if target:
|
||||
self.pidList.append({"target": target, "id": udid})
|
||||
self.pidList.append({"target": target, "id": udid})
|
||||
LogManager.info(f"[CONNECT FLOW] 设备模型已加入内存,当前在线数: {len(self.deviceModelList)}", udid)
|
||||
|
||||
LogManager.info(f"[CONNECT FLOW] <<<<<<<<< 设备 {udid} 处理完成 <<<<<<<<", udid)
|
||||
# ------------------------------------------------------------------
|
||||
# 原函数保留(改名即可)
|
||||
# ------------------------------------------------------------------
|
||||
@@ -380,31 +407,41 @@ class Deviceinfo(object):
|
||||
# -------------------- 工具方法(未改动) --------------------
|
||||
def is_device_trusted(self, udid: str) -> bool:
|
||||
try:
|
||||
LogManager.info(f"[CONNECT FLOW] 开始信任检测", udid)
|
||||
d = BaseDevice(udid)
|
||||
d.get_value("DeviceName")
|
||||
name = d.get_value("DeviceName")
|
||||
LogManager.info(f"[CONNECT FLOW] 信任检测成功,DeviceName={name}", udid)
|
||||
return True
|
||||
except Exception:
|
||||
except Exception as e:
|
||||
LogManager.warning(f"[CONNECT FLOW] 信任检测失败: {e}", udid)
|
||||
return False
|
||||
|
||||
def relayDeviceScreenPort(self, udid: str, port: int) -> Optional[subprocess.Popen]:
|
||||
LogManager.info(f"[CONNECT FLOW] 进入 relayDeviceScreenPort,目标端口 {port}", udid)
|
||||
if not self._spawn_iproxy:
|
||||
LogManager.error("iproxy 启动器未就绪", udid)
|
||||
LogManager.error("[CONNECT FLOW] _spawn_iproxy 未初始化,返回 None", udid)
|
||||
return None
|
||||
|
||||
for attempt in range(5):
|
||||
if not self._is_port_open(port):
|
||||
LogManager.info(f"[CONNECT FLOW] 端口 {port} 检测为空闲", udid)
|
||||
break
|
||||
LogManager.warning(f"端口 {port} 仍被占用,第 {attempt+1} 次重试释放", udid)
|
||||
LogManager.warning(f"[CONNECT FLOW] 端口 {port} 仍被占用,第 {attempt + 1}/5 次尝试释放", udid)
|
||||
pid = self._get_pid_by_port(port)
|
||||
if pid and pid != os.getpid():
|
||||
LogManager.info(f"[CONNECT FLOW] 准备 kill 占用端口 {port} 的 PID {pid}", udid)
|
||||
self._kill_pid_gracefully(pid)
|
||||
time.sleep(0.2)
|
||||
else:
|
||||
LogManager.error("[CONNECT FLOW] 连续 5 次无法释放端口,放弃", udid)
|
||||
return None
|
||||
|
||||
try:
|
||||
p = self._spawn_iproxy(udid, port, 9100)
|
||||
self._port_in_use.add(port)
|
||||
LogManager.info(f"启动 iproxy 成功,本地 {port} -> 设备 9100", udid)
|
||||
LogManager.info(f"[CONNECT FLOW] iproxy 启动完成,PID={p.pid}", udid)
|
||||
return p
|
||||
except Exception as e:
|
||||
LogManager.error(f"启动 iproxy 失败:{e}", udid)
|
||||
LogManager.error(f"[CONNECT FLOW] iproxy 启动异常: {e}", udid)
|
||||
return None
|
||||
|
||||
def _is_port_open(self, port: int) -> bool:
|
||||
|
||||
Binary file not shown.
@@ -90,16 +90,23 @@ class ThreadManager:
|
||||
|
||||
# ---------- 2. 并发等 0.2 s 收尾 ----------
|
||||
def _wait_and_clean(udid: str) -> Tuple[int, str]:
|
||||
"""子线程里只做极短 join 并清理记录"""
|
||||
with cls._lock:
|
||||
task = cls._tasks.get(udid)
|
||||
if not task:
|
||||
return 400, f"设备{udid}任务记录已丢失"
|
||||
return 400, "任务记录已丢失"
|
||||
thread = task["thread"]
|
||||
# 浅等 0.2 s,后台还没死也继续
|
||||
thread.join(0.2)
|
||||
del cls._tasks[udid] # 立即清理
|
||||
return 200, f"设备{udid}已下发停止指令"
|
||||
# 第一次等 3 秒,让“分片睡眠”有机会退出
|
||||
thread.join(timeout=3)
|
||||
# 如果还活,再补 2 秒
|
||||
if thread.is_alive():
|
||||
thread.join(timeout=2)
|
||||
# 最终仍活,记录日志但不硬杀,避免僵尸
|
||||
with cls._lock:
|
||||
cls._tasks.pop(udid, None)
|
||||
if thread.is_alive():
|
||||
LogManager.warning(f"[batch_stop] 线程 5s 未退出,已清理记录但线程仍跑 {udid}")
|
||||
return 201, "已下发停止,线程超长任务未立即结束"
|
||||
return 200, "已停止"
|
||||
|
||||
with ThreadPoolExecutor(max_workers=min(32, len(udids))) as executor:
|
||||
future_map = {executor.submit(_wait_and_clean, udid): udid for udid in udids}
|
||||
|
||||
Binary file not shown.
@@ -238,7 +238,9 @@ class ScriptManager():
|
||||
session.double_tap(x, y)
|
||||
|
||||
print("--------------------------------------------")
|
||||
event.wait(timeout=random.randint(300, 600))
|
||||
# 换成
|
||||
if not self.interruptible_sleep(event, random.randint(300, 600)):
|
||||
break
|
||||
session.swipe_up()
|
||||
|
||||
# 正常退出(外部 event 触发)
|
||||
@@ -334,8 +336,8 @@ class ScriptManager():
|
||||
|
||||
if not anchor:
|
||||
LogManager.method_info(f"数据库中的数据不足", "关注打招呼", udid)
|
||||
event.wait(timeout=30)
|
||||
continue
|
||||
if not self.interruptible_sleep(event, 30):
|
||||
continue
|
||||
|
||||
aid = anchor.get("anchorId", "")
|
||||
anchorCountry = anchor.get("country", "")
|
||||
@@ -554,7 +556,7 @@ class ScriptManager():
|
||||
print("----------------------------------------------------------")
|
||||
print("监控回复消息")
|
||||
# 执行回复消息逻辑
|
||||
self.monitorMessages(session, udid)
|
||||
self.monitorMessages(session, udid, event)
|
||||
|
||||
homeButton = AiUtils.findHomeButton(udid)
|
||||
if homeButton.exists:
|
||||
@@ -901,3 +903,14 @@ class ScriptManager():
|
||||
else:
|
||||
LogManager.method_error(f"检测不到收件箱", "检测消息", udid)
|
||||
raise Exception("当前页面找不到收件箱,重启")
|
||||
|
||||
|
||||
# 放在 ScriptManager 类外面或 utils 里
|
||||
def interruptible_sleep(self, event: threading.Event, seconds: float, slice_: float = 1.0):
|
||||
"""把一次长 sleep 拆成 1 秒一片,随时响应 event"""
|
||||
left = seconds
|
||||
while left > 0 and not event.is_set():
|
||||
timeout = min(slice_, left)
|
||||
event.wait(timeout=timeout)
|
||||
left -= timeout
|
||||
return not event.is_set() # 返回 True 表示正常睡完,False 被中断
|
||||
Binary file not shown.
Reference in New Issue
Block a user