记录:python+UIAutomator2 远程手机

一、简介

该方法是通过uiautomator2连接手机,通过pyqt5显示手机信息。
1、使用条件
1、必须安装uiautomator2
python -m uiautomator2 init
pip install uiautomator2

二、代码

1、phoneShow.py

```python
import functools
import os,sys,time
from  ShowDemo import analyz
import uiautomator2 as u2
import adbutils
import websocket
from PyQt5.QtCore import QThread, pyqtSignal, Qt, QObject
from PyQt5.QtWidgets import (QWidget, QHBoxLayout,
                             QLabel, QApplication, QVBoxLayout, QPushButton, QLineEdit, QMessageBox, QFileDialog)
from PyQt5.QtGui import QPixmap, QPainter

#Pyqt5的键值与android的键值对应关系
KEY_WORD={"16777220":"KEYCODE_ENTER","16777216":111,"16777235":19,"16777237":20,"16777234":21,"16777236":22,"16777219":67,"16777223":112,"16777217":61,"32":"KEYCODE_SPACE",
          "16777252":115,"48":7,"49":8,"50":9,"51":10,"52":11,"53":12,"54":13,"55":14,"56":15,"57":16,"65":29,"66":30,"67":31,"68":32,"69":33,"70":34,"71":35,
          "72":36,"73":37,"74":38,"75":39,"76":40,"77":41,"78":42,"79":43,"80":44,"81":45,"82":46,"83":47,"84":48,"85":49,"86":50,"87":51,"88":52,"89":53,"90":54,}
#连接手机,并将手机的屏幕信息保存至PC端
class worker(QThread):
	#自定义信号
    sinOut = pyqtSignal(str)  
    def __init__(self, parent=None):
        super(worker, self).__init__(parent)
        d = adbutils.adb.device()
        #通过adb开启转发端口7912
        lport = d.forward_port(7912)
        self.ws = websocket.WebSocket()
        #开启websocket服务,并连接
        self.ws.connect("ws://localhost:{}/minicap".format(lport))
        #创建存放手机截屏的文件夹
        os.makedirs("screenshots", exist_ok=True)
        self.state =True
    def __del__(self):
        self.state = False
    def run(self):
    	#循环接收手机发送的屏幕信息
        while self.state:
            data = self.ws.recv()
            if not isinstance(data, (bytes, bytearray)):
            	#如果接收的信息,不是字节类型就不存放
                continue
            with open("screenshots/image.jpg" , "wb") as f:
            	#将接收的信息存放为本地文件
                f.write(data)
                #发送自定义信号
                self.sinOut.emit("OK")
#通过点击屏幕的位置,获取手机中该位置所在元素的各项信息
class GetPosition():
	#定义并初始化的元素的边界值
    bounds = [0,0,0,0]
    #定义并初始化的元素的深度
    level = 0
    #定义并初始化该元素的所有信息
    xmlLine = []
    #定义并初始化该元素的id信息
    resource_id = ""
    def __init__(self,data,dot):
    	#经处理后的屏幕信息,类型:list
        self.data = data
        #点击位置的坐标
        self.dot = dot
        self.analyzeData() 
    def analyzeData(self):
    	#遍历每行的信息
        for line in self.data:
        	#判断line[-1]是否为字典类型
            if isinstance(line[-1], dict):
            	#获取元素的边界值信息,并且将str格式化:[0,1080][0,1920]->['0','1080','0','1920']
                bounds = str(line[-1].get("bounds")).replace("][",',').replace("[","").replace("]","").split(",")
                #判断元素是否正常
                if len(bounds) == 4:
                	#将字符串转化为int类型
                    x1,y1,x2,y2 = int(bounds[0]),int(bounds[1]),int(bounds[2]),int(bounds[3])
                    #判断该点的位置,是否处于该元素边界内
                    if self.dot[0] >=x1 and self.dot[0]<=x2 and self.dot[1] >=y1 and self.dot[1] <=y2:		
                    	#找出满足条件中深度最大的元素
                        if int(line[1]) > self.level:
                            self.level = int(line[1])
                            self.bounds = [x1,y1,x2,y2]
                            self.xmlLine = line
                            self.resource_id = line[-1].get("resource-id")

#PC界面
class Example(QWidget):


    def __init__(self):
        super().__init__()
        self.timeing = 0
        self.element = {}
        self.wind = [450,800]
        self.dots = [0,0]
        self.initUI()
        #启动读取手机屏幕信息的线程
        self.thread = worker()
        self.thread.sinOut.connect(self.showImage)
        self.thread.start()
        #通过uiautomator2连接手机
        self.d = u2.connect_usb()
        #解析手机的界面信息,将xml解析为list
        data = analyz.getXmlData(str=self.d.dump_hierarchy())
        #获取点击位置的元素信息
        self.pos = GetPosition(data, [0, 0])

    def __del__(self):
        self.d.set_fastinput_ime(False)
    def showImage(self,str):
    	#将手机屏幕信息显示至PC界面中
        self.Qlable.setPixmap(QPixmap("screenshots/image.jpg"))
    def mousePressEvent (self, event):
    	#监听鼠标按下的信息
        width,height = self.d.window_size()
        x = ((event.x()-20)*width)/self.wind[0]
        y = ((event.y()-20)*height)/self.wind[1]
        self.dots[0] = x
        self.dots[1] = y
        self.timeing = time.time()
    def mouseReleaseEvent(self,event):
    	#监听鼠标释放信息
        width, height = self.d.window_size()
        x = ((event.x() - 20) * width) / self.wind[0]
        y = ((event.y() - 20) * height) / self.wind[1]
        if x>=0 and x<width and y>0 and y<height:
        	#处理滑动操作
            if self.dots[0] != x or self.dots[1] != y:
                self.d.swipe(self.dots[0], self.dots[1], x, y,steps=4)
            else:
            	#处理左键点击操作
                if event.button() == 1 and time.time() -self.timeing < 0.3:
                    self.d.click(x,y)
                #处理左键长按操作
                elif event.button() == 1:
                    self.d.long_click(x, y,0.5)
                data = analyz.getXmlData(str=self.d.dump_hierarchy())
                self.pos = GetPosition(data,[x,y])
                #将获取的元素信息,显示至界面
                if self.pos.xmlLine !=[]:
                    self.element.get("lable_id").setText('resource-id : %s '%self.pos.resource_id)
                    self.element.get("lable_text").setText('lable_text : %s '%self.pos.xmlLine[-1].get("text"))
                    self.element.get("lable_class").setText('lable_class : %s '%self.pos.xmlLine[-1].get("class"))
                    self.element.get("lable_package").setText('lable_package : %s '%self.pos.xmlLine[-1].get("package"))
                    self.element.get("lable_bounds").setText('lable_bounds : %s '%self.pos.xmlLine[-1].get("bounds"))
                #点击显示界面时,将焦点锁定至显示界面
                self.Qlable.setFocus()
        else:
        	#点击显示界面外时,将释放焦点
            self.Qlable.clearFocus()
    def keyPress(self,key):
    	#向手机发送按键信息
        self.d.press(key)
        print(key)
    def line_change(self,line):
    	#通过输入框向,手机输入信息
        if self.pos.resource_id != "":
        	#通过id定位
            self.d(resourceId=self.pos.resource_id).send_keys(line.text())
        else:
            QMessageBox.about(self, "警告", "  该元素无ID ! ! !  ")
    def keyPressEvent(self,event):
    	#监听键盘信息
        #判断图片显示界面是否为焦点
        if self.Qlable.hasFocus():
            global KEY_WORD
            key_str = str(event.key())
            if key_str in KEY_WORD:
                key = KEY_WORD.get(key_str)
                self.d.press(key)
            else:
                print("不存在的键")
    def wheelEvent (self, event):
    	#监听滚轮事件
        width, height = self.d.window_size()
        anDY = event.angleDelta().y()
        if anDY >0:
            self.d(scrollable=True).scroll.vert.backward()
        else:
            self.d(scrollable=True).scroll(steps=50)
    def openfile(self):
    	#选择文件
        get_filename_path, state = QFileDialog.getOpenFileName(self,"浏览","L:/Users/xu/Downloads","Text Files (*.apk)")
        if state !="":
            QMessageBox.about(self, "警告", "  点击OK后,开始安装APP  ")
            time.sleep(0.5)
            result = os.system("adb install %s"%get_filename_path)
            if result ==0:
                QMessageBox.about(self, "提示", "  安装成功!  ")
            else:
                QMessageBox.about(self, "提示", "  安装失败!  ")

    def action(self,command):
    	#定义“熄屏”、“亮屏”操作
        if command =="BRIGHT":
            self.d.screen_on()
        elif command =="QUENCH":
            self.d.screen_off()
    def initUI(self):
    	#定义界面信息
        index = 1
        width  = 450 * index
        height = 800 * index
        mainBox = QHBoxLayout()
        #左面界面显示
        vbox1 = QVBoxLayout()
        vbox1_H1 = QHBoxLayout()
        vbox1_H2 = QHBoxLayout()
        self.Qlable = QLabel()
        self.Qlable.setPixmap(QPixmap("screenshots/image.jpg"))

        btn_home = QPushButton("主页")
        btn_back = QPushButton("返回")
        btn_recent = QPushButton("任务栏")
        btn_browse = QPushButton("浏览")
        btn_bright = QPushButton("亮屏")
        btn_quench = QPushButton("熄屏")

        btn_home.clicked.connect(functools.partial(self.keyPress, "HOME"))
        btn_back.clicked.connect(functools.partial(self.keyPress, "BACK"))
        btn_recent.clicked.connect(functools.partial(self.keyPress, "RECENT"))
        btn_browse.clicked.connect(self.openfile)
        btn_bright.clicked.connect(functools.partial(self.action, "BRIGHT"))
        btn_quench.clicked.connect(functools.partial(self.action, "QUENCH"))

        vbox1.addWidget(self.Qlable)

        vbox1_H1.addWidget(btn_home)
        vbox1_H1.addWidget(btn_back)
        vbox1_H1.addWidget(btn_recent)

        vbox1_H2.addWidget(btn_browse)
        vbox1_H2.addWidget(btn_bright)
        vbox1_H2.addWidget(btn_quench)

        vbox1.addLayout(vbox1_H1)
        vbox1.addLayout(vbox1_H2)



        #右面显示
        vbox2 = QVBoxLayout()
        vbox2_H = QHBoxLayout()
        edit_line = QLineEdit("")
        btn_send = QPushButton("发送")
        lable_id = QLabel('resource-id : \"\" ')
        lable_text = QLabel('text : \"\" ')
        lable_class = QLabel('class : \"\" ')
        lable_package = QLabel('package : \"\" ')
        lable_bounds = QLabel('bounds : \"\" ')
        lable_id.setWordWrap(True)
        lable_text.setWordWrap(True)
        lable_class.setWordWrap(True)
        lable_package.setWordWrap(True)
        lable_bounds.setWordWrap(True)
        lable_id.setTextInteractionFlags(Qt.TextSelectableByKeyboard | Qt.TextSelectableByMouse)
        lable_text.setTextInteractionFlags(Qt.TextSelectableByKeyboard | Qt.TextSelectableByMouse)
        lable_class.setTextInteractionFlags(Qt.TextSelectableByKeyboard | Qt.TextSelectableByMouse)
        lable_package.setTextInteractionFlags(Qt.TextSelectableByKeyboard | Qt.TextSelectableByMouse)
        lable_bounds.setTextInteractionFlags(Qt.TextSelectableByKeyboard | Qt.TextSelectableByMouse)
        # edit_line.textChanged.connect(functools.partial(self.line_change, edit_line))
        btn_send.clicked.connect(functools.partial(self.line_change, edit_line))

        vbox2_H.addWidget(edit_line)
        vbox2_H.addWidget(btn_send)

        vbox2.addWidget(lable_id)
        vbox2.addWidget(lable_text)
        vbox2.addWidget(lable_class)
        vbox2.addWidget(lable_package)
        vbox2.addWidget(lable_bounds)

        vbox2.addLayout(vbox2_H)


        mainBox.addLayout(vbox1)
        mainBox.addLayout(vbox2)
        self.element.update({"lable_id":lable_id})
        self.element.update({"lable_text":lable_text})
        self.element.update({"lable_class":lable_class})
        self.element.update({"lable_package":lable_package})
        self.element.update({"lable_bounds":lable_bounds})
        self.setLayout(mainBox)
        self.setWindowTitle('远程手机')
        self.show()

if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())

2、analyz.py

import xml
import xml.etree.ElementTree as ET
from ShowDemo import phoneShow
"""
实现从xml文件中读取数据
"""
# 全局唯一标识
unique_id = 1

# 遍历所有的节点
def walkData(root_node, level, result_list):
    global unique_id
    temp_list = [unique_id, level, root_node.tag, root_node.attrib]
    result_list.append(temp_list)
    unique_id += 1

    # 遍历每个子节点
    children_node = root_node.getchildren()
    if len(children_node) == 0:
        return
    for child in children_node:
        walkData(child, level + 1, result_list)
    return


def getXmlData(file_name=None,str=None):
	#1、通过文件名解析xml文件
	#2、直接解析xml的字符串
    if file_name == None and str !=None:
        file = open("data_analyz.xml","w",encoding='utf-8-sig')
        file.write(str)
        file.close()
        file_name = "data_analyz.xml"
    level = 1  # 节点的深度从1开始
    result_list = []
    root = ET.parse(file_name).getroot()
    walkData(root, level, result_list)
    return result_list

if __name__ == '__main__':
    d = getXmlData("data_analyz.xml")

    g = phoneShow.GetPosition(d,[200,200])
    print(g.xmlLine)
    print(g.data)

三、目录结构
在这里插入图片描述

四、一些截图
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值