国庆节有时间写就写了一篇。
这个文件夹下面是主要的 API,框架进行实例化的时候,API 源头就在这里
#一段封装的例子
import wpyscripts.manager as manager
#省略
def __init__(self):
self.device = manager.get_device()
self.engine = manager.get_engine()
self.logger = manager.get_logger()
self.reporter = manager.get_reporter()
[跳转 wetest 文件夹] device.py 上个文章讲过,来看下实例化的 engine 的文件
#核心导入的类库
import threading
from wpyscripts.common.adb_process import *
from wpyscripts.wetest.element import Element
from wpyscripts.common.protocol import Commands, TouchEvent
from wpyscripts.common.socket_client import SocketClient
from wpyscripts.common.rpc_thread import RPCReceiveThread
from wpyscripts.common.wetest_exceptions import *
上面类库按字面意思,大概可以明白线程模式,通过 adb,封装了一个 Element 类在 element.py 里面,下面的 3 个类库是通信相关的,这个后面在讲,最后一个包装的异常模块。
这里核心类是 GameEngine(),在使用前依然需要初始化
#官方例子
engine=manager.get_engine()
logger=manager.get_logger()
version=engine.get_sdk_version()
logger.debug("Version Information : {0}".format(version))
# 省略
def get_sdk_version(self): #line 121
""" 获取引擎集成的SDK的版本信息
"""
ret = self.send_command_with_retry(Commands.GET_VERSION, 1) #核心函数
engine = ret.get("engine", None)
sdk_version = ret.get("sdkVersion", None)
engine_version = ret.get("engineVersion", None)
ui_type = ret.get("sdkUIType", None)
version = VersionInfo(engine_version, engine, sdk_version, ui_type)
return version
get_sdk_version 是实例化后通过这个看反馈集成 sdk 版本信息,做为框架前置开发必备的。
send_command_with_retry() 是核心函数,使用了 adb forward 通过命令序列进行转发
def send_command_with_retry(self,command, param=None , timeout=20): #self.send_command_with_retry(Commands.GET_VERSION, 1)
for i in range(0,2):
try:
ret = self.socket.send_command(command, param,timeout)
return ret
except Exception as e:
ret = excute_adb_process("forward --list") #核心数据
logger.info("adb forward list : " + str(ret))
ret = forward(self.port, unity_sdk_port)# with retry...
logger.info("after reforward list : " + str(ret))
try:
self.socket = SocketClient(self.address, self.port)
except Exception as e :
logger.exception(e)
time.sleep(5)
Commands.GET_VERSION 是一个类似枚举类。
class Commands(object):
GET_VERSION = 100 # 获取版本号
FIND_ELEMENTS = 101 # 查找节点
FIND_ELEMENT_PATH = 102 # 模糊查找
GET_ELEMENTS_BOUND = 103 # 获取节点的位置信息
GET_ELEMENT_WORLD_BOUND = 104 # 获取节点的世界坐标
GET_UI_INTERACT_STATUS = 105 # 获取游戏的可点击信息,包括scene、可点击节点,及位置信息
GET_CURRENT_SCENE = 106 # 获取Unity的Scene名称
跳转 Commands
对通信熟悉的了解,会发现这个是一个内建的协议,用于通信处理的内容。
下面其他函数是包装 Ga 功能的 APi 返回 engine.py
def find_element(self, name):
"""
通过GameObject.Find查找对应的GameObject
:param name:
GameObject.Find的参数
:Usage:
>>>import wpyscripts.manager as manager
>>>engine=manager.get_engine()
>>>button=engine.find_element('/Canvas/Panel/Button')
:return:
a instance of Element if find the GameObject,else return None
example:
{"object_name":"/Canvas/Panel/Button",
"instance":4257741}
:rtype: Element
:raise:
"""
ret = self.send_command_with_retry(Commands.FIND_ELEMENTS, [name]) #通知查找节点 [name]?未知
if ret:
ret = ret[0]
if ret["instance"] == -1:
return None
else:
return Element(ret["name"], ret["instance"])
else:#这2句可以省略,如果ret不为真,返回就是None.
return None
根据注释里面的 Usage 部分就能看到基础使用方式,'/Canvas/Panel/Button‘是通过 GAutomatorView 工具中间的空间数那边复制拿到的,如果在封装框架时可以用 PO 模式或者配置模式
#配置模式 通过任意配置.py文件 然后type创建占用内存很小的类,等同class LoginMain:PanelMiddleButton是类变量,类内部属性也是在字典内。
LoginMain =type('LoginMain',(object,),dict(PanelMiddleButton='/Canvas/Panel/Button'))
第二种是使用字典缓存,通过 txt 写入内容在缓存到字典内,推荐是第一种。
ret = self.send_command_with_retry(Commands.FIND_ELEMENTS, [name]) #这个[name发现不理解]
Commands.FIND_ELEMENTS 对应的协议号,我们继续深究 send_command_with_retry 函数。ret 本质从哪里来的
ret = self.socket.send_command(command, param,timeout)
#往上翻 line 113
self.socket = SocketClient(self.address, self.port)
跳转 from wpyscripts.common.socket_client import SocketClient
class SocketClient(object):
def __init__(self, _host='localhost', _port=27018):
self.host = _host
self.port = _port
self.connect()
def connect(self):
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #Tcp
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) #开启Nagle算法
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) #保持长链接
self.socket.connect((self.host, self.port))
socket 后面跟着那段是从 C 继承过来的,可以通过配置来决定功能。然看下 line 101 行。
def send_command(self, cmd, params=None, timeout=20):
# if params != None and not isinstance(params, dict):
# raise Exception('Params should be dict')
if not params:
params = ""
command = {}
command["cmd"] = cmd
command["value"] = params
for retry in range(0, 2):
try:
self.socket.settimeout(timeout)
self._send_data(command)
ret = self._recv_data()
return ret
except WeTestRuntimeError as e:
raise e
except socket.timeout:
self.socket.close()
self.connect()
raise WeTestSDKError("Recv Data From SDK timeout")
except socket.error as e:
time.sleep(1)
print("Retry...{0}".format(e.errno))
self.socket.close()
self.connect()
continue
raise Exception('Socket Error')
params 没有内容就是一个字符串,然后下面缓存一个 command 字典,字典里面 cmd 为了传入上文的 Commands 里面的协议号,params 传入字符串.python 允许这样套入 [字符串],等于 key 对应的 value 在这里是一个列表,所以使用时可以把 [name] 的长度和内容加个打印看看。
self.socket.settimeout(timeout) #每个socket设置超时
self._send_data(command)
ret = self._recv_data()
开闭了 2 个函数_send_data 和 _recv_data 函数。
函数前面带一个下划线代表在当前类内部使用,但可以被实例化后调用。注意函数前面带二个下划线的话就不能被实例化后直接调用,会抛错,这是 python 一种内部设定。
def _send_data(self, data):
try:
serialized = json.dumps(data)
except (TypeError, ValueError) as e: #出现这里 一般是指转不了json对象内存的。
raise WeTestInvaildArg('You can only send JSON-serializable data')
length = len(serialized)
buff = struct.pack("i", length)
self.socket.send(buff)
if six.PY3:
self.socket.sendall(bytes(serialized, encoding='utf-8'))
else:
self.socket.sendall(serialized)
客户端-->服务端,这里讲的是基础的 socket 发送数据和带结构体,dumps 是正序列化数据,json 库可以自己替换成 ujson,但框架的原则是不用安装其他类库就能使用,可以自己修改。结构体是 int32 通过 fmt 压入到 struct.pack 压入缓存区,消息体是上面那段 json.dumps 内存压成二进制,在通过 sendall()
bytes() 估计是做保护转换写法吧,发现框架中有很多保护转换写法。
def _recv_data(self):
deserialized = self.recv_package()
if deserialized['status'] != 0:
message = "Error code: " + str(deserialized['status']) + " msg: " + deserialized['data']
raise WeTestSDKError(message)
return deserialized['data']
这里等于是服务端-->本地缓存-->客户端处理,这里可以知道 ga 后端的数据结构,这里面又包裹了一个函数 recv_package().
说明客户端接收的数据 status==0,就是正确的,直接返回回包里面的 data,如果!=0 会 raise 错误条件,说明正确回包里面最少 items 里面有 2 个 key,一个是 status,一个是 data。改造和写得时候具体还是打印个输出看看。这时不用运行也可以基本判断出来 tcp -socket 传输什么样的数据包,回包是什么。根据导入库,双端对象之间协议类型是 Tcp-Rpc,用于自动化框架是用 adb 端口转发。
下文在继续介绍其他的。