手机控制中心 01 屏幕查看 WebSocket - Vue && Python

1,009 阅读3分钟

aewrd-e2ynq.gif

简单流程图

image.png

前置条件

手机开好adb连接上即可,可以百度下

  1. 打开开发者选项
  2. 勾选上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要简单很多

后端部分

有提供代码,如果不感兴趣可以直接下载跑就行

github.com/xianyue3903…

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()

前端代码

github.com/xianyue3903…

语言: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即可显示图片