简单流程图
前置条件
手机开好adb连接上即可,可以百度下
- 打开开发者选项
- 勾选上usb调试
交互部分
因为屏幕数据交互量大,这里采用了WebSocket来进行交互
什么是WebSocket?
www.ruanyifeng.com/blog/2017/0…
var ws = new WebSocket("wss://echo.websocket.org");
ws.onopen = function(evt) {
console.log("Connection open ...");
ws.send("Hello WebSockets!");
};
ws.onmessage = function(evt) {
console.log( "Received Message: " + evt.data);
ws.close();
};
ws.onclose = function(evt) {
console.log("Connection closed.");
};
大概简单看一眼就能懂,简单来说可以连接一个websocket服务器,双方就可以进行数据交互了,比起原生C++那样使用socket要简单很多
后端部分
有提供代码,如果不感兴趣可以直接下载跑就行
pip3 install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple
python3 main.py
语言:Python
Lib:
Airtest(网易的一个自动化测试库,里面有封装好的方法用起来简单方便)
websockets
使用asyncio 启动一个 websocket服务
# 把ip换成自己本地的ip
start_server = websockets.serve(main_logic, '127.0.0.1', 18080)
asyncio.get_event_loop().run_until_complete(start_server)
asyncio.get_event_loop().run_forever()
主要逻辑部分,第一次连接的时候输入账号密码,后面持续监测命令
# 服务器端主逻辑
# websocket和path是该函数被回调时自动传过来的,不需要自己传
async def main_logic(websocket, path):
await check_permit(websocket)
await recv_msg(websocket)
检查密码,简单对第一次的消息进行判断
async def check_permit(websocket):
while True:
recv_str = await websocket.recv()
cred_dict = recv_str.split(":")
if cred_dict[0] == "admin" and cred_dict[1] == "123456":
response_str = "congratulation, you have connect with server\r\nnow, you can do something else"
await websocket.send(json.dumps({"data": response_str}))
return True
else:
response_str = "sorry, the username or password is wrong, please submit again"
await websocket.send(response_str)
逻辑处理,这里只做了两种的处理
get_device_list 获取设备列表
get_device_frame 获取设备当前画面帧
# 接收客户端消息并处理,这里只是简单把客户端发来的返回回去
async def recv_msg(websocket):
while True:
try:
recv_text = await websocket.recv()
request_dict = json.loads(recv_text)
print(request_dict)
if request_dict['action'] == 'get_device_list':
result = android.adb.devices()
await websocket.send(json.dumps({'action': 'get_device_list', 'data': result}))
if request_dict['action'] == 'get_device_frame':
serialno = request_dict['parameter']['device']
device = device_list.get(serialno)
if not device:
device = Android(serialno=serialno)
device_list[serialno] = device
result = device.screen_proxy.get_frame_from_stream()
await websocket.send(
json.dumps({'action': 'get_device_frame', 'data': base64.b64encode(result).decode('utf-8')}))
except:
pass
完整代码
import asyncio
import base64
import json
import websockets
# 检测客户端权限,用户名密码通过才能退出循环
from airtest.core.android import Android
android = Android()
device_list = {}
async def check_permit(websocket):
while True:
recv_str = await websocket.recv()
cred_dict = recv_str.split(":")
if cred_dict[0] == "admin" and cred_dict[1] == "123456":
response_str = "congratulation, you have connect with server\r\nnow, you can do something else"
await websocket.send(json.dumps({"data": response_str}))
return True
else:
response_str = "sorry, the username or password is wrong, please submit again"
await websocket.send(response_str)
# 接收客户端消息并处理,这里只是简单把客户端发来的返回回去
async def recv_msg(websocket):
while True:
try:
recv_text = await websocket.recv()
request_dict = json.loads(recv_text)
print(request_dict)
if request_dict['action'] == 'get_device_list':
result = android.adb.devices()
await websocket.send(json.dumps({'action': 'get_device_list', 'data': result}))
if request_dict['action'] == 'get_device_frame':
serialno = request_dict['parameter']['device']
device = device_list.get(serialno)
if not device:
device = Android(serialno=serialno)
device_list[serialno] = device
result = device.screen_proxy.get_frame_from_stream()
await websocket.send(
json.dumps({'action': 'get_device_frame', 'data': base64.b64encode(result).decode('utf-8')}))
except:
pass
# 服务器端主逻辑
# websocket和path是该函数被回调时自动传过来的,不需要自己传
async def main_logic(websocket, path):
await check_permit(websocket)
await recv_msg(websocket)
# 把ip换成自己本地的ip
start_server = websockets.serve(main_logic, '127.0.0.1', 18080)
asyncio.get_event_loop().run_until_complete(start_server)
asyncio.get_event_loop().run_forever()
前端代码
语言:Vue
Lib: 无
启动websocket客户端
initWebSocket () {
this.ws = new WebSocket('ws://127.0.0.1:18080')
this.connected = false
//申请一个WebSocket对象,参数是服务端地址,同http协议使用http://开头一样,WebSocket协议的url使用ws://开头,另外安全的WebSocket协议使用wss://开头
this.ws.onopen = () => {
//当WebSocket创建成功时,触发onopen事件
this.ws.send('admin:123456') //将消息发送到服务端, 进行登陆验证
this.connected = true
}
this.ws.onmessage = (e) => {
//当客户端收到服务端发来的消息时
console.log(e.data)
const data = JSON.parse(e.data)
this.callbackList.forEach(item => {
item(data)
})
}
this.ws.onclose = () => {
//当客户端收到服务端发送的关闭连接请求时,触发onclose事件
// setTimeout(()=>{
// this.initWebSocket()
// })
}
this.ws.onerror = () => {
//如果出现连接、处理、接收、发送数据失败的时候触发onerror事件
setTimeout(() => {
this.initWebSocket()
}, 1000)
}
}
当连接出错的时候,自动一秒后重试
所有的组件都在连接上后在创建
<div id="app">
<DeviceList v-if="connected" @chose_device="(e)=>{ currentDevice = e }"/>
<Panel v-if="connected && currentDevice" :device="currentDevice"></Panel>
</div>
简单封装了个函数用于请求
request (action, parameter) {
if (!this.ws) {
return
}
const query = {
action,
parameter
}
this.ws.send((JSON.stringify(query)))
},
所有的组建都可以创建一个消息的回调,然后根据action来判断自己是不是要处理这个事件
onMessage (message) {
if(message.action === 'get_device_frame') {
this.image = 'data:image/jpg;base64,' + message.data
this.$nextTick(()=>{
this.request('get_device_frame', {device: this.device})
})
}
},
这里后段发回来的已经是一张张图片的base64了,直接拼上data:image/jpg;base64, 传递给img做src即可显示图片