diff --git a/.idea/workspace.xml b/.idea/workspace.xml index b1716b3..fe82f53 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -5,12 +5,8 @@ - - - - - + - { + "keyToString": { + "ASKED_ADD_EXTERNAL_FILES": "true", + "Python.123.executor": "Run", + "Python.Main.executor": "Run", + "RunOnceActivity.ShowReadmeOnStart": "true", + "SHARE_PROJECT_CONFIGURATION_FILES": "true", + "git-widget-placeholder": "main", + "javascript.nodejs.core.library.configured.version": "22.18.0", + "javascript.nodejs.core.library.typings.version": "22.18.0", + "last_opened_file_path": "F:/company code/AI item/20250820/iOSAI", + "node.js.detected.package.eslint": "true", + "node.js.detected.package.tslint": "true", + "node.js.selected.package.eslint": "(autodetect)", + "node.js.selected.package.tslint": "(autodetect)", + "nodejs_package_manager_path": "npm", + "settings.editor.selected.configurable": "com.gitee.ui.GiteeSettingsConfigurable", + "vue.rearranger.settings.migration": "true" } -}]]> +} @@ -169,8 +165,9 @@ - - + + + - - + + \ No newline at end of file diff --git a/Module/DeviceInfo.py b/Module/DeviceInfo.py index db0b41f..816d5b7 100644 --- a/Module/DeviceInfo.py +++ b/Module/DeviceInfo.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- import os +import signal import sys import time import json @@ -32,6 +33,8 @@ class Deviceinfo(object): self.deviceModelList: List[DeviceModel] = [] # 最大可连接设备限制 self.maxDeviceCount = 6 + # 操作锁 + self._lock = threading.Lock() # ===== iproxy:一次性完成 路径定位 + 环境变量配置 + 启动器准备 ===== try: @@ -97,7 +100,6 @@ class Deviceinfo(object): # 监听设备连接(死循环,内部捕获异常) # ---------------------------- def startDeviceListener(self): - while True: try: lists = Usbmux().device_list() @@ -106,7 +108,6 @@ class Deviceinfo(object): LogManager.warning(f"usbmuxd 连接失败: {e}。请确认已安装 iTunes/Apple Mobile Device Support,并在手机上“信任此电脑”") time.sleep(2) continue - # 新接入设备 for device in lists: if (device not in self.deviceArray) and (len(self.deviceArray) < self.maxDeviceCount): @@ -116,7 +117,6 @@ class Deviceinfo(object): self.deviceArray.append(device) except Exception as e: LogManager.error(f"连接设备失败 {device.udid}: {e}", device.udid) - # 拔出设备处理 self._removeDisconnected(lists) time.sleep(1) @@ -161,44 +161,88 @@ class Deviceinfo(object): # 5) 本地端口 -> 设备端口 的映射(投屏:本地 self.screenProxy -> 设备 9100) target = self.relayDeviceScreenPort(identifier) - self.pidList.append({"target": target, "id": identifier}) + # 加个非空判断 + if target is not None: + with self._lock: + self.pidList.append({"target": target, "id": identifier}) + + # 安全杀死iproxy进程 + def _terminate_proc(self, p: subprocess.Popen): + if not p: + return + if p.poll() is not None: + return + try: + p.terminate() # 先温柔 + p.wait(timeout=3) + except Exception: + try: + if os.name == "posix": + try: + # 如果 iproxy 启动时用了 setsid,这里可杀整个进程组 + os.killpg(os.getpgid(p.pid), signal.SIGKILL) + except Exception: + p.kill() + else: + p.kill() # Windows 直接 kill + p.wait(timeout=2) # 一定要 wait,避免僵尸 + except Exception: + pass # ---------------------------- # 处理拔出设备:发通知、关掉 iproxy、移出状态 # ---------------------------- def _removeDisconnected(self, current_list): - set1 = set(self.deviceArray) - set2 = set(current_list) - difference = list(set1 - set2) # 在旧集合中但不在新集合中 -> 已拔出 + # 1) 计算“被拔出”的 UDID 集合 —— 用 UDID,而不是对象做集合运算 + try: + prev_udids = {getattr(d, "udid", None) for d in self.deviceArray if getattr(d, "udid", None)} + now_udids = {getattr(d, "udid", None) for d in current_list if getattr(d, "udid", None)} + except Exception as e: + LogManager.error(f"收集 UDID 失败:{e}", "") + return + removed_udids = prev_udids - now_udids + if not removed_udids: + return - for i in difference: - udid = i.udid - # 1) 通知前端:type = 2 - for a in list(self.deviceModelList): - if udid == a.deviceId: - a.type = 2 - try: - self.manager.send(a.toDict()) - except Exception as e: - LogManager.warning(f"发送下线事件失败:{e}", udid) - self.deviceModelList.remove(a) + # 2) 加锁,避免多线程同时改三个列表 + if not hasattr(self, "_lock"): + self._lock = threading.RLock() + with self._lock: + # 2.1 通知前端并清理 deviceModelList + for udid in list(removed_udids): + for a in list(self.deviceModelList): + if udid == getattr(a, "deviceId", None): + a.type = 2 + try: + self.manager.send(a.toDict()) + except Exception as e: + LogManager.warning(f"发送下线事件失败:{e}", udid) + try: + self.deviceModelList.remove(a) + except ValueError: + pass - # 2) 关掉对应的 iproxy + # 2.2 关闭该 UDID 的所有 iproxy + survivors = [] for k in list(self.pidList): - if udid == k["id"]: - target = k.get("target") + kid = k.get("id") + if kid in removed_udids: + p = k.get("target") try: - if target and target.poll() is None: - target.kill() - except Exception: - pass - self.pidList.remove(k) + self._terminate_proc(p) + except Exception as e: + LogManager.warning(f"关闭 iproxy 异常:{e}", kid) + # 不再把该项放回 survivors,相当于移除 + else: + survivors.append(k) + self.pidList = survivors - # 3) 从已连接集合中移除 - try: - self.deviceArray.remove(i) - except Exception: - pass + # 2.3 从已连接集合中移除(按 UDID 过滤,避免对象引用不一致导致 remove 失败) + self.deviceArray = [d for d in self.deviceArray if getattr(d, "udid", None) not in removed_udids] + + # 3) 打点 + for udid in removed_udids: + LogManager.info("设备已拔出,清理完成(下线通知 + 端口映射关闭 + 状态移除)", udid) # ---------------------------- # 根目录与 iproxy 可执行文件定位 diff --git a/script/ScriptManager.py b/script/ScriptManager.py index 6e16251..3f7f4d8 100644 --- a/script/ScriptManager.py +++ b/script/ScriptManager.py @@ -972,18 +972,3 @@ class ScriptManager(): LogManager.error(f"检测不到收件箱", udid) raise Exception("当前页面找不到收件箱,重启") - # 双击收件箱 定位到消息的位置 - - # if count == 4: - # r = el.bounds # 可能是命名属性,也可能是 tuple - # cx = int((r.x + r.width / 2) if hasattr(r, "x") else (r[0] + r[2] / 2)) - # cy = int((r.y + r.height / 2) if hasattr(r, "y") else (r[1] + r[3] / 2)) - # session.double_tap(cx, cy) # 可能抛异常:方法不存在 - # LogManager.info(f"双击收件箱 定位到信息", udid) - # count = 0 - - def test(self, udid): - client = wda.USBClient(udid) - session = client.session() - session.appium_settings({"snapshotMaxDepth": 10}) - print(client.source())