背景

业务需要采集在app上执行任务的整个过程,原始方案相对复杂,修改需要协调多方人员,因而考虑是否有更轻量级的方案。

原始需求:

  • 记录完成任务的每一步操作(点击、滑动、输入等)
  • 记录操作前后的截图和布局xml

基于Adb的方案

最容易考虑到的方案是就是通过adb去实现,要获取到当前页面的xml、当前页面截图,所以只需要将每一步操作通过adb发送给手机端即可。

步骤

  1. 通过adb连接设备,编写一个agent程序接收网页操作请求,并通过adb发送指令执行
  2. adb获取当前页面xml(uiautomator dump)
  3. adb获取当前页面截图(screencap),agent通过ws发送到网页端
  4. 网页显示图片,监控鼠标点击事件,计算出点击位置
  5. 将相关操作通过adb发送到设备,模拟操作
  6. 循环步骤2-5

弄清楚流程,可以直接告诉编程LLM,代码秒成,考虑到golang依赖较少,我们直接让LLM生成golang代码。

下面介绍部分实现,比如golang调用adb,网页端传入deviceid和操作:


func executeCommand(deviceID string, action string, parameters string) error {
cmdArgs := []string{"-s", deviceID, "shell", action}
if parameters != "" {
cmdArgs = append(cmdArgs, parameters)
}
fmt.Println(cmdArgs)
cmd := exec.Command("adb", cmdArgs...)
err := cmd.Run()
if err != nil {
return err
}
return nil
}

比如截图,调用screencap截取png格式的图片:

func screenshot(deviceID string) ([]byte, error) {
cmd := exec.Command("adb", "-s", deviceID, "exec-out", "screencap", "-p")
var out bytes.Buffer
cmd.Stdout = &out
err := cmd.Run()
if err != nil {
return nil, err
}
return out.Bytes(), nil
}

JavaScript端显示:

socket.onmessage = (event) => {
if (event.data instanceof Blob) {
const url = URL.createObjectURL(event.data);
imgElement.src = url;
}
}

图片上方可以加一个div层,用来监控鼠标事件,模拟操作:

overlayElement.addEventListener('mousedown', (e) => {
startX = e.offsetX;
startY = e.offsetY;
startTime = Date.now();
}); overlayElement.addEventListener('mouseup', (e) => {
const endX = e.offsetX;
const endY = e.offsetY;
const elapsedTime = Date.now() - startTime;
const duration = Math.max(elapsedTime / 1000, 0.001); // Avoid zero division const imgStartX = (startX / imgDisplayWidth) * imgWidth;
const imgStartY = (startY / imgDisplayHeight) * imgHeight;
const imgEndX = (endX / imgDisplayWidth) * imgWidth;
const imgEndY = (endY / imgDisplayHeight) * imgHeight; if (Math.abs(imgStartX - imgEndX) > 5 || Math.abs(imgStartY - imgEndY) > 5) {
sendCommand('input swipe', `${imgStartX} ${imgStartY} ${imgEndX} ${imgEndY}`);
}
else if (duration > 500) {
// 长按
sendCommand('input swipe', `${imgStartX} ${imgStartY} ${imgEndX} ${imgEndY} ${duration / 1000}`);
}
else {
sendCommand('input tap', `${imgStartX} ${imgStartY}`);
}
});

效果与问题

效果如下:

问题也很多:

  • screencap 比较慢,测试模拟器需要600~700ms,显示起来感觉比较卡顿
  • 大部分时候,页面没操作,图片基本不变化,重复传输浪费网络
  • uiautomator dump 更夸张,2~3s

优化

图像差分传输,截图后检查下是否变化,没有变化就不发送,有变化就发送diff图像,这样JavaScript端合并图像就可以了。

diff 用最简单的策略,相同的改为全透明,不同的保留原图像,计算diff图:

// CalculateDifference 计算两个RGBA图像之间的差异, 并返回新的RGBA图像
// 如果两个图片完全一致,则返回全透明的图像
func CalculateDifference(img1, img2 image.Image) *image.NRGBA {
bounds := img1.Bounds()
diff := image.NewNRGBA(bounds) for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
for x := bounds.Min.X; x < bounds.Max.X; x++ {
c1 := img1.At(x, y).(color.NRGBA)
c2 := img2.At(x, y).(color.NRGBA) if c1 == c2 {
diff.Set(x, y, color.NRGBA{}) // 完全一致时,设置为全0
continue
} else {
diff.Set(x, y, c2)
}
// 组合RGB和Alpha通道为一个16位灰度值(分开存储Alpha通道可能更实际)
}
} return diff
}

Javascrit 接收到后可以结合上一张图进行还原,前端可以用canvas去操作diff图像进行合并:


function createImageFromBlob(blob) {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => resolve(img);
img.onerror = reject;
img.src = URL.createObjectURL(blob);
});
} async function restoreImage(diffImageBlob) {
const refImage = imgElement;
const diffImage = await createImageFromBlob(diffImageBlob); canvas.width = refImage.width;
canvas.height = refImage.height; ctx.drawImage(refImage, 0, 0);
const refImageData = ctx.getImageData(0, 0, refImage.width, refImage.height); ctx.drawImage(diffImage, 0, 0);
const diffImageData = ctx.getImageData(0, 0, diffImage.width, diffImage.height); const resultImageData = ctx.createImageData(refImage.width, refImage.height);
const refData = refImageData.data;
const diffData = diffImageData.data;
const resultData = resultImageData.data; for (let i = 0; i < refData.length; i += 4) {
// Assuming diff is non-zero means it contains the correct pixel
resultData[i] = diffData[i] !== 0 ? diffData[i] : refData[i]; // R
resultData[i + 1] = diffData[i + 1] !== 0 ? diffData[i + 1] : refData[i + 1]; // G
resultData[i + 2] = diffData[i + 2] !== 0 ? diffData[i + 2] : refData[i + 2]; // B
resultData[i + 3] = diffData[i + 3] !== 0 ? diffData[i + 3] : refData[i + 3]; // A
} ctx.putImageData(resultImageData, 0, 0);
}

结论

虽然思路可行,但是因为adb 截图和获取xml比较慢,最终方案用不了,只能换一个思路去解决。

基于uiautomator2的方案

uiautomator2 是一个python库,用python调用设备上uiautomator服务来获取页面信息、控制设备,其原理也比较简单,就是通过adb在设备上启动atxagent和server等程序,然后通过http和ws去连接设备从而实现控制。

uiautomator2可以几十ms的时间获取xml,截图也因为高效的minicap,可以提供更高的fps。

使用uiautomator2就需要将golang转成python,幸运的是直接扔给LLM,先转成python,然后让将使用adb的改成使用uiautomator2,基本上大差不差,稍微缝缝补补搞定。

这里要感慨,LLM对程序员真是好助手,做好方案设计,扔给LLM就能比较好的去实现,心有灵犀。(PS: 胸中有丘壑,LLM才是好助手)

讲一些改动, 执行命令,可以提供一个更通用的方法,方便前端直接调用:

async def execute_command(device_id, action, parameters):
try:
device = get_device(device_id)
command = getattr(device, action)
if parameters:
command(**parameters)
else:
command()
except Exception as e:
print(f"Error executing command on device {device_id}: {e}")

JS 调用

sendCommand('click', {"x": imgStartX, "y": imgStartY});

async function sendCommand(action, parameters) {
const command = JSON.stringify({ type: 'action', deviceID: deviceID, action: action, parameters: parameters });
socket.send(command);
console.log('Command:', command);
}

优化

模拟click按键,发送按键事件:

// SendKeyEvent 按下一个键(字符或功能键)
func (d *Driver) SendKeyEvent(keyCode string) error {
cmd := exec.Command("adb", d.deviceID, "shell", "input", "keyevent", keyCode)
err := cmd.Run()
if err != nil {
return err
}
return nil
}

准实时投屏的方案

上面采用的minicap,截图已经很快了,一秒钟传输几张图片,基本上满足这个场景够用了。 还有更准实时的方案吗?

专业的开源投屏控制软件 scrpy 是一个好的选择,scrpy实现原理其实类似上面的uiautomator2,会在device上启动一个server,通过server获取音视频流,以及控制。

scrpy 技术上相对更成熟,而uiautomator2依赖的minicap则缺乏维护,对安卓新版本支持不够好。

所以,在获取截图方面,也可以考虑调用scrpy的server来实现准实时控制。但是就如标题所说,从入门到放弃,上面的方案已经可以满足我们需求,没必要在这里投入更多的精力,所以这个方案放弃。

结语

本文主要记录投屏控制相关的实践过程,通过从adb方案开始,到uiautomator2,以及最后放弃scrpy方案,在这个热闹的周末,正好闲暇的时间,了解过去不曾接触的知识,也是一个有趣的过程。

Android网页投屏控制从入门到放弃的更多相关文章

  1. Android 电脑投屏工具Vysor Pro介绍

    Chrome的插件,直接到chrome的扩展程序里面搜索Vysor,安装即可 如何破解: C:\Users\lanlan.shi\AppData\Local\Google\Chrome\User Da ...

  2. 各手机PC品牌投屏功能连接方法

    一.iOS终端(iPhone/iPad)无线投屏: 1.将iPhone或iPad与必捷会议盒子连接至同一路由器: 2.滑动iPhone/iPad的屏幕,调出Airplay功能,选择需要投屏的主机,开始 ...

  3. 海豚星空扫码投屏 Android 接收端 SDK 集成 六步骤

    一 跟目录的build.gradle添加私有mevan仓库 maven {url 'http://nexus.dolphinstar.cn/repo/openmavenx'} 二 app/build. ...

  4. Ubuntu安装scrcpy手机投屏和控制(Ubuntu用QQ微信的另一种方法)

    Scrcpy 安装 snap install scrcpy adb服务安装 sudo apt-get install android-tools-adb adb配置 查看手机的USB识别号 手机通过U ...

  5. 同时支持Android 和 ios 投屏到电脑的软件,Support Android and ios screen shrare to PC - 希沃授课助手

    最近学校由粉笔黑板更换了智慧电子黑板,然后发现了一个好玩的软件. 感谢希沃公司的开发: 希沃授课助手,这是一款同时支持Android 和 ios 投屏和远程控制的. 效果很流畅,非常赞

  6. scrcpy投屏android手机到电脑

    在mac os下 投票iPhone投屏是最简单不过了,只需要用Quicktime player就可以. 但是在mac下咱投屏android的手机呢,就需要用到scrcpy了. 1.打开终端,输入命令: ...

  7. 看完小白也会使用,Android投屏神器scrcpy详细教程

    楔子 做为一个软件测试工程师,在使用手机测试的时候,缺陷附件想附上截图.视频,需要从手机把图片.视频发送到拷贝或发送到电脑,非常麻烦. 所以想到使用投屏软件,把手机的屏幕投屏到电脑,便可以直接在电脑上 ...

  8. ios11苹果手机怎么投屏到电脑

    使用过苹果手机的用户都知道,苹果手机触摸屏操作极为流畅,网页浏览也非常轻松,各种网络上的应用可以说是非常完美.iPhone的娱乐功能相当的强大,能让苹果iPhone超越了其他手机很大的距离.但是手机怎 ...

  9. QML - 实现Gstreamer投屏 投屏画面遮挡

    1.     背景介绍 中控端运行的操作系统是Android,中控软件主要功能有导航.收音机.媒体(音乐).蓝牙(连接).手机互联.行车辅助和系统设置等. 仪表端运行的操作系统是Linux,仪表软件主 ...

  10. 分享一个开源的windows安卓投屏工具,scrcpy

    看到scrcpy可能很多人会以为是大名鼎鼎的Scrcpy(一个十分强大的多线路爬虫框架),sorry今天分享的主角不是他,而是他: github地址:https://github.com/Genymo ...

随机推荐

  1. springboot3使用validation进行参数验证

    前言   今天学习了使用validation整合springboot进行字段的校验,体验下来感觉很不错,有了validation可以省下一大堆控制器里面的数据校验,例如前端发送了一个请求到我们后端,请 ...

  2. 工具类——EventManager

    EventManager using UnityEngine; using System.Collections; using System.Collections.Generic; using Un ...

  3. 一份快速入门的 Makefile 教程

    目录 一份快速入门的 Makefile 教程 关于 Makefile,你应该知道的一些事情 什么是 Makefile? Makefile 能做什么? Makefile 怎么写? Makefile 与 ...

  4. HarmonyOS SDK助力鸿蒙原生应用“易感知、易理解、易操作”

    6月21-23日,华为开发者大会(HDC 2024)盛大开幕.6月23日上午,<HarmonyOS开放能力,使能应用原生易用体验>分论坛成功举办,大会邀请了多位华为技术专家深度解读如何通过 ...

  5. V4L2视频采集操作流程和接口说明

    背景: V4L2是V4L的升级版本,为linux下视频设备程序提供了一套接口规范.包括一套数据结构和底层V4L2驱动接口. <WAV文件格式分析> 一般操作流程(视频设备): 1.打开设备 ...

  6. 全志T113-i+玄铁HiFi4开发板硬件说明书(2)

    前 言 本文档主要介绍开发板硬件接口资源以及设计注意事项等内容,测试板卡为全志T113-i+玄铁HiFi4开发板,由于篇幅问题,本篇文章共分为上下两集,点击账户可查看更多内容详情,开发问题欢迎留言,感 ...

  7. 国产自主架构!龙芯2K1000LA工业核心板正式发布!

    国产自主架构!龙芯2K1000LA工业核心板正式发布! 原创 Tronlong创龙科技 Tronlong创龙科技 2024-06-13 07:50 广东 Tronlong创龙科技 ,赞18 (点击视频 ...

  8. 煤矿安全大模型:微调internlm2模型实现针对煤矿事故和煤矿安全知识的智能问答

    煤矿安全大模型----矿途智护者 使用煤矿历史事故案例,事故处理报告.安全规程规章制度.技术文档.煤矿从业人员入职考试题库等数据,微调internlm2模型实现针对煤矿事故和煤矿安全知识的智能问答. ...

  9. VS图片

  10. Express手稿