You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

224 lines
7.9 KiB

import asyncio
import base64
from typing import Optional, List
from adbutils import adb
from app.core.device_manager import device_manager
from app.schemas.adb import (
ClickRequest, InputRequest, ScreenshotResponse,
InstallRequest, InstallResponse, LogcatRequest, LogcatResponse
)
from app.utils.log import get_logger
logger = get_logger(__name__)
class ADBService:
"""ADB服务类 - 实现ADB协议相关操作"""
def __init__(self):
self.adb_client = None
self._initialize_adb()
def _initialize_adb(self):
"""初始化ADB客户端"""
try:
self.adb_client = adb.connect()
logger.info("ADB客户端初始化成功")
except Exception as e:
logger.error(f"ADB客户端初始化失败: {e}")
self.adb_client = None
async def _get_device(self, device_id: str):
"""获取ADB设备对象"""
if not self.adb_client:
self._initialize_adb()
try:
device = self.adb_client.device(device_id)
return device
except Exception as e:
logger.error(f"获取ADB设备失败: {device_id}, 错误: {e}")
raise ValueError(f"设备 {device_id} 不存在或未连接")
async def click(self, device_id: str, x: int, y: int, duration: int = 100) -> dict:
"""执行点击操作"""
try:
device = await self._get_device(device_id)
# 使用adb shell input命令执行点击
if duration > 0:
# 长按
cmd = f"input touchscreen swipe {x} {y} {x} {y} {duration}"
else:
# 点击
cmd = f"input tap {x} {y}"
result = device.shell(cmd)
logger.info(f"ADB点击操作成功: {device_id} -> ({x}, {y})")
return {
"success": True,
"message": "点击操作执行成功",
"coordinates": {"x": x, "y": y},
"duration": duration
}
except Exception as e:
logger.error(f"ADB点击操作失败: {device_id}, 错误: {e}")
return {
"success": False,
"message": f"点击操作失败: {str(e)}"
}
async def input_text(self, device_id: str, text: str, clear_first: bool = False) -> dict:
"""输入文本"""
try:
device = await self._get_device(device_id)
if clear_first:
# 先清空输入框
device.shell("input keyevent KEYCODE_MOVE_END")
device.shell("input keyevent KEYCODE_DEL")
# 输入文本
result = device.shell(f"input text '{text}'")
logger.info(f"ADB文本输入成功: {device_id} -> {text}")
return {
"success": True,
"message": "文本输入成功",
"text": text
}
except Exception as e:
logger.error(f"ADB文本输入失败: {device_id}, 错误: {e}")
return {
"success": False,
"message": f"文本输入失败: {str(e)}"
}
async def screenshot(self, device_id: str) -> ScreenshotResponse:
"""获取屏幕截图"""
try:
device = await self._get_device(device_id)
# 获取截图
screenshot_data = device.screenshot()
# 转换为Base64
import io
from PIL import Image
img_buffer = io.BytesIO()
screenshot_data.save(img_buffer, format='PNG')
img_data = base64.b64encode(img_buffer.getvalue()).decode('utf-8')
logger.info(f"ADB截图成功: {device_id}")
return ScreenshotResponse(
image_data=img_data,
format="png",
width=screenshot_data.width,
height=screenshot_data.height
)
except Exception as e:
logger.error(f"ADB截图失败: {device_id}, 错误: {e}")
raise ValueError(f"截图失败: {str(e)}")
async def install_apk(self, device_id: str, apk_path: str, package_name: Optional[str] = None) -> InstallResponse:
"""安装APK"""
try:
device = await self._get_device(device_id)
# 安装APK
result = device.install(apk_path)
if result:
logger.info(f"ADB安装APK成功: {device_id} -> {apk_path}")
return InstallResponse(
success=True,
message="APK安装成功",
package_name=package_name
)
else:
logger.error(f"ADB安装APK失败: {device_id} -> {apk_path}")
return InstallResponse(
success=False,
message="APK安装失败"
)
except Exception as e:
logger.error(f"ADB安装APK异常: {device_id}, 错误: {e}")
return InstallResponse(
success=False,
message=f"APK安装异常: {str(e)}"
)
async def get_logcat(self, device_id: str, package_name: Optional[str] = None,
level: str = "V", max_lines: int = 100) -> LogcatResponse:
"""获取日志"""
try:
device = await self._get_device(device_id)
# 构建logcat命令
cmd = f"logcat -d -v time"
if package_name:
cmd += f" | grep {package_name}"
if level != "V":
cmd += f" *:{level}"
# 执行命令
result = device.shell(cmd)
# 处理结果
lines = result.strip().split('\n') if result.strip() else []
logs = lines[-max_lines:] if len(lines) > max_lines else lines
logger.info(f"ADB获取日志成功: {device_id}")
return LogcatResponse(
logs=logs,
total_lines=len(logs)
)
except Exception as e:
logger.error(f"ADB获取日志失败: {device_id}, 错误: {e}")
return LogcatResponse(
logs=[],
total_lines=0
)
async def get_device_info(self, device_id: str) -> dict:
"""获取设备信息"""
try:
device = await self._get_device(device_id)
# 获取设备信息
model = device.shell("getprop ro.product.model").strip()
android_version = device.shell("getprop ro.build.version.release").strip()
sdk_version = int(device.shell("getprop ro.build.version.sdk").strip())
return {
"device_id": device_id,
"model": model,
"android_version": android_version,
"sdk_version": sdk_version,
"status": "online"
}
except Exception as e:
logger.error(f"获取设备信息失败: {device_id}, 错误: {e}")
return {
"device_id": device_id,
"model": "Unknown",
"android_version": "Unknown",
"sdk_version": 0,
"status": "error"
}
async def list_devices(self) -> List[str]:
"""列出所有ADB设备"""
try:
if not self.adb_client:
self._initialize_adb()
devices = self.adb_client.device_list()
device_ids = [device.serial for device in devices]
logger.info(f"ADB设备列表: {device_ids}")
return device_ids
except Exception as e:
logger.error(f"获取ADB设备列表失败: {e}")
return []