性能优化
This commit is contained in:
@@ -3,24 +3,38 @@ import os
|
||||
import signal
|
||||
import sys
|
||||
import time
|
||||
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||
|
||||
import wda
|
||||
import threading
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
from typing import List, Dict, Optional
|
||||
|
||||
from tidevice import Usbmux, ConnectionType
|
||||
from tidevice._device import BaseDevice
|
||||
from Entity.DeviceModel import DeviceModel
|
||||
from Entity.Variables import WdaAppBundleId
|
||||
from Module.FlaskSubprocessManager import FlaskSubprocessManager
|
||||
from Utils.LogManager import LogManager
|
||||
from Utils.SubprocessKit import check_output as sp_check_output, popen as sp_popen
|
||||
|
||||
|
||||
class Deviceinfo(object):
|
||||
"""设备生命周期管理:以 deviceModelList 为唯一真理源"""
|
||||
|
||||
def __init__(self):
|
||||
...
|
||||
# ✅ 新增:连接线程池(最大 6 并发)
|
||||
self._connect_pool = ThreadPoolExecutor(max_workers=6)
|
||||
...
|
||||
|
||||
if os.name == "nt":
|
||||
self._si = subprocess.STARTUPINFO()
|
||||
self._si.dwFlags |= subprocess.STARTF_USESHOWWINDOW
|
||||
self._si.wShowWindow = subprocess.SW_HIDE # 0
|
||||
else:
|
||||
self._si = None
|
||||
|
||||
self.deviceIndex = 0
|
||||
self.screenProxy = 9110
|
||||
self.pidList: List[Dict] = [] # 仅记录 iproxy 进程
|
||||
@@ -46,13 +60,14 @@ class Deviceinfo(object):
|
||||
pass
|
||||
|
||||
self._creationflags = 0x08000000 if os.name == "nt" else 0
|
||||
|
||||
self._popen_kwargs = dict(
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
cwd=str(self.iproxy_dir),
|
||||
shell=False,
|
||||
text=True,
|
||||
creationflags=self._creationflags,
|
||||
creationflags=0x08000000 if os.name == "nt" else 0, # CREATE_NO_WINDOW
|
||||
encoding="utf-8",
|
||||
bufsize=1,
|
||||
)
|
||||
@@ -84,10 +99,10 @@ class Deviceinfo(object):
|
||||
# endregion
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# ✅ 2. 主监听循环(已用“时间窗口+USB 层兜底”重写)
|
||||
# 主监听循环 → 只负责“发现”和“提交任务”
|
||||
# ------------------------------------------------------------------
|
||||
def startDeviceListener(self):
|
||||
MISS_WINDOW = 5.0 # 5 秒连续失踪才判死刑
|
||||
MISS_WINDOW = 5.0
|
||||
while True:
|
||||
try:
|
||||
lists = Usbmux().device_list()
|
||||
@@ -97,38 +112,36 @@ class Deviceinfo(object):
|
||||
continue
|
||||
|
||||
now_udids = {d.udid for d in lists if d.conn_type == ConnectionType.USB}
|
||||
# ✅ USB 层真断兜底
|
||||
usb_sn_set = self._usb_enumerate_sn()
|
||||
|
||||
need_remove = None
|
||||
# 1. 失踪判定(同旧逻辑)
|
||||
need_remove = []
|
||||
with self._lock:
|
||||
for udid in list(self._model_index.keys()):
|
||||
if udid not in now_udids:
|
||||
last = self._last_seen.get(udid, time.time())
|
||||
if time.time() - last > MISS_WINDOW and udid not in usb_sn_set:
|
||||
need_remove = udid
|
||||
need_remove.append(udid)
|
||||
else:
|
||||
self._last_seen[udid] = time.time()
|
||||
for udid in need_remove:
|
||||
self._remove_model(udid)
|
||||
|
||||
if need_remove:
|
||||
self._remove_model(need_remove)
|
||||
|
||||
# 新增设备(原逻辑不变)
|
||||
for d in lists:
|
||||
if d.conn_type != ConnectionType.USB:
|
||||
continue
|
||||
udid = d.udid
|
||||
with self._lock:
|
||||
if udid in self._model_index:
|
||||
continue
|
||||
if not self.is_device_trusted(udid):
|
||||
continue
|
||||
if len(self.deviceModelList) >= self.maxDeviceCount:
|
||||
continue
|
||||
try:
|
||||
self.connectDevice(udid)
|
||||
except Exception as e:
|
||||
LogManager.error(f"连接设备失败 {udid}: {e}", udid)
|
||||
# 2. 发现新设备 → 并发连接
|
||||
with self._lock:
|
||||
new_udids = [d.udid for d in lists
|
||||
if d.conn_type == ConnectionType.USB and
|
||||
d.udid not in self._model_index and
|
||||
len(self.deviceModelList) < self.maxDeviceCount]
|
||||
if new_udids:
|
||||
futures = {self._connect_pool.submit(self._connect_device_task, udid): udid
|
||||
for udid in new_udids}
|
||||
for f in as_completed(futures, timeout=10):
|
||||
udid = futures[f]
|
||||
try:
|
||||
f.result(timeout=8) # 单台 8 s 硬截止
|
||||
except Exception as e:
|
||||
LogManager.error(f"连接任务超时/失败: {e}", udid)
|
||||
|
||||
time.sleep(1)
|
||||
|
||||
@@ -137,7 +150,7 @@ class Deviceinfo(object):
|
||||
# ------------------------------------------------------------------
|
||||
def _usb_enumerate_sn(self) -> set[str]:
|
||||
try:
|
||||
out = subprocess.check_output(["idevice_id", "-l"], text=True, timeout=3)
|
||||
out = sp_check_output(["idevice_id", "-l"], text=True, timeout=3)
|
||||
return {line.strip() for line in out.splitlines() if line.strip()}
|
||||
except Exception:
|
||||
return set()
|
||||
@@ -259,8 +272,10 @@ class Deviceinfo(object):
|
||||
self._port_in_use.remove(port)
|
||||
self._port_pool.append(port)
|
||||
|
||||
# -------------------- 单台设备连接(未改动) --------------------
|
||||
def connectDevice(self, udid: str):
|
||||
# ------------------------------------------------------------------
|
||||
# 线程池里真正干活的地方(原 connectDevice 逻辑搬过来)
|
||||
# ------------------------------------------------------------------
|
||||
def _connect_device_task(self, udid: str):
|
||||
if not self.is_device_trusted(udid):
|
||||
LogManager.warning("设备未信任,跳过 WDA 启动", udid)
|
||||
return
|
||||
@@ -269,6 +284,7 @@ class Deviceinfo(object):
|
||||
except Exception as e:
|
||||
LogManager.error(f"启动 WDA 失败: {e}", udid)
|
||||
return
|
||||
|
||||
width, height, scale = 0, 0, 1.0
|
||||
try:
|
||||
size = d.window_size()
|
||||
@@ -276,19 +292,35 @@ class Deviceinfo(object):
|
||||
scale = d.scale
|
||||
except Exception as e:
|
||||
LogManager.warning(f"读取屏幕信息失败:{e}", udid)
|
||||
|
||||
port = self._alloc_port()
|
||||
model = DeviceModel(udid, port, width, height, scale, type=1)
|
||||
self._add_model(model)
|
||||
|
||||
# 先做完所有 IO,再抢锁写内存
|
||||
try:
|
||||
d.app_start(WdaAppBundleId)
|
||||
d.home()
|
||||
except Exception as e:
|
||||
LogManager.warning(f"启动/切回桌面失败:{e}", udid)
|
||||
time.sleep(2)
|
||||
self.pidList = [item for item in self.pidList if item.get("id") != udid]
|
||||
|
||||
time.sleep(2) # 原逻辑保留
|
||||
|
||||
target = self.relayDeviceScreenPort(udid, port)
|
||||
if target:
|
||||
self.pidList.append({"target": target, "id": udid})
|
||||
|
||||
# 毫秒级临界区
|
||||
with self._lock:
|
||||
if udid in self._model_index: # 并发防重
|
||||
return
|
||||
self._add_model(model)
|
||||
if target:
|
||||
self.pidList.append({"target": target, "id": udid})
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# 原函数保留(改名即可)
|
||||
# ------------------------------------------------------------------
|
||||
def connectDevice(self, udid: str):
|
||||
"""对外保留接口,实际走线程池"""
|
||||
self._connect_pool.submit(self._connect_device_task, udid)
|
||||
|
||||
# -------------------- 工具方法(未改动) --------------------
|
||||
def is_device_trusted(self, udid: str) -> bool:
|
||||
@@ -327,14 +359,12 @@ class Deviceinfo(object):
|
||||
def _get_pid_by_port(self, port: int) -> Optional[int]:
|
||||
try:
|
||||
if os.name == "nt":
|
||||
cmd = ["netstat", "-ano", "-p", "tcp"]
|
||||
out = subprocess.check_output(cmd, text=True)
|
||||
out = sp_check_output(["netstat", "-ano", "-p", "tcp"], text=True)
|
||||
for line in out.splitlines():
|
||||
if f"127.0.0.1:{port}" in line and "LISTENING" in line:
|
||||
return int(line.strip().split()[-1])
|
||||
else:
|
||||
cmd = ["lsof", "-t", f"-iTCP:{port}", "-sTCP:LISTEN"]
|
||||
out = subprocess.check_output(cmd, text=True)
|
||||
out = sp_check_output(["lsof", "-t", f"-iTCP:{port}", "-sTCP:LISTEN"], text=True)
|
||||
return int(out.strip().split()[0])
|
||||
except Exception:
|
||||
return None
|
||||
@@ -375,4 +405,4 @@ class Deviceinfo(object):
|
||||
for p in candidates:
|
||||
if p.exists():
|
||||
return p
|
||||
raise FileNotFoundError(f"iproxy not found, tried: {[str(c) for c in candidates]}")
|
||||
raise FileNotFoundError(f"iproxy not found, tried: {[str(c) for c in candidates]}")
|
||||
|
||||
Reference in New Issue
Block a user