如何用电脑批量操作多部手机
如果你有很多手机,然后需要在这些手机上同时执行相同的操作,这个时候如果能有一种办法批量操作,将会大大提高效率,节省很多时间。本文将介绍基于uiautomator2实现的群控手机方案。
uiautomator2 是 一种 Android 自动化测试框架,提供了点击、长按、输入文本、滑动、拖拽、截屏等方法,能够模拟用户的各种动作。
本方案采用python的web端框架django+vue前端框架实现,支持在页面选择手机,执行配置的命令,多部手机会同时执行操作(页面做的比较简陋,见谅)。
大致的流程也比较简单,基于在页面配置的命令,调用uiautomator2的相关api实现,有兴趣的朋友也可以了解下uiautomator2相关的接口,动手来实现一遍
这里贴上前端的实现代码
<template><div class="common-layout"><el-container><el-header><div style="font-weight: bolder;text-align: center;font-size: 30px">手机群控</div></el-header><el-main><el-row><el-col :span="12"><el-card class="box-card"><div style="display: flex;justify-content: space-between;align-items:center"><div style="font-weight: bolder;">设备列表</div><el-button link type="text" @click="getDeviceList">刷新</el-button></div><el-table :data="deviceListData" style="width: 100%;" height="75vh" @selection-change="selectDevices" ref="deviceTable"><el-table-column type="selection" width="55"/><el-table-column prop="sn" label="设备编号"/><el-table-column prop="name" label="设备名称"/><el-table-column prop="status" label="状态"/><el-table-column label="操作"><template slot-scope="scope"><el-button link type="text" size="small" @click="editDevice(scope.row)">编辑</el-button><!--<el-button link type="primary" size="small">删除</el-button>--></template></el-table-column></el-table></el-card></el-col><el-col :span="12"><el-card class="box-card"><div style="display: flex;justify-content: space-between;align-items:center"><div style="font-weight: bolder;">命令列表</div><el-button link type="text" @click="addCommandVisible = true">添加</el-button></div><el-table :data="commandListData" style="width: 100%" height="75vh" ><el-table-column width="55"/><el-table-column prop="name" label="名称"/><el-table-column prop="creatTime" label="创建时间"/><el-table-column label="操作"><template slot-scope="scope"><el-button link type="primary" size="small" @click="updateCommand(scope.row)">编辑</el-button><el-button link type="primary" size="small" @click="deleteCommand(scope.row.name)">删除</el-button><el-button link type="primary" size="small" @click="executeCommand(scope.row.name)">执行</el-button></template></el-table-column></el-table></el-card></el-col></el-row></el-main></el-container><el-dialog :visible.sync="addCommandVisible" title="添加/编辑命令"><el-input v-model="commandInfo.name" autocomplete="off" placeholder="请输入命令名称"></el-input><el-table :row-class-name="tableRowClassName" :data="commandInfo.commands" border style="width: 100%"><el-table-column align="center" width="100px" type="index" label="序号"></el-table-column><el-table-column align="center" prop="operation" label="操作"><template slot-scope="scope"><el-select @change="operationChange(scope.row.operation,scope.row.index)" size="small"v-model="scope.row.operation" placeholder="请选择操作类型"><el-option v-for="item in operationOption" :key="item.value" :label="item.label":value="item.value"></el-option></el-select></template></el-table-column><el-table-column align="center" prop="param" label="参数"><template #default="scope"><el-input size="small" v-model="scope.row.param" placeholder="非必填,有则填写"></el-input></template></el-table-column><el-table-column align="center" prop="delayTime" label="延时"><template #default="scope"><el-input size="small" v-model="scope.row.delayTime" placeholder="填写延时时长,单位秒"></el-input></template></el-table-column><el-table-column align="center" prop="date" label="操作"><template slot-scope="scope"><el-button link type="primary" size="small" @click="addForm">添加</el-button><el-button link type="primary" size="small" v-if="commandInfo.commands.length>1"@click="removeIdx(scope.row,scope.row.index)" style="color:rgb(216,30,6)">删除</el-button></template></el-table-column></el-table><template #footer><span class="dialog-footer"><el-button @click="addCommandVisible = false">取消</el-button><el-button type="primary" @click="addCommand">确定</el-button></span></template></el-dialog><el-dialog title="编辑设备" :visible.sync="updateDeviceVisible"><el-form :model="deviceInfo"><el-form-item label="设备编号" :label-width="formLabelWidth"><el-input v-model="deviceInfo.sn" autocomplete="off" disabled></el-input></el-form-item><el-form-item label="设备名称" :label-width="formLabelWidth"><el-input v-model="deviceInfo.name" autocomplete="off"></el-input></el-form-item></el-form><div slot="footer" class="dialog-footer"><el-button @click="updateDeviceVisible = false">取 消</el-button><el-button type="primary" @click="updateDevice">确 定</el-button></div></el-dialog></div>
</template>
<script>import {getDeviceList, updateDevice,getCommandList,addCommand,deleteCommand,executeCommand} from '@/api/device'export default {name: 'device',data() {return {formLabelWidth: '140px',addCommandVisible: false,commandInfo: {name:"",commands: [{operation: '',param: '',delayTime: ''}]},operationOption: [{value: '单击',label: '单击'},{value: '返回',label: '返回'},{value: '右滑',label: '右滑'},{value: '截屏',label: '截屏'},{value: '点击Home键',label: '点击Home键'},{value: '卸载',label: '卸载'},{value: '输入文字',label: '输入文字'},{value: '左滑',label: '左滑'}],deviceListData: [],commandListData: [],deviceInfo: {sn: "",name: ""},executeParam:{devices:[],name:""},updateDeviceVisible:false,}},mounted() {this.getDeviceList()this.getCommandList()},methods: {selectDevices(val) {let selection = this.$refs.deviceTable.selection;this.executeParam.devices = []for (let item of selection){this.executeParam.devices.push(item.sn)}console.log(JSON.stringify(this.executeParam.devices))},getDeviceList() {getDeviceList().then(res => {if (res.code === 0) {this.deviceListData = res.data;} else {this.$message.error('查询失败!')}this.loading = false}).catch(err => {this.loading = falsethis.$message.error(err)})},operationChange(operation, index) {this.commandInfo.commands[index].operation = operation},editDevice(data){this.deviceInfo = datathis.updateDeviceVisible = true;},updateDevice() {updateDevice(this.deviceInfo).then(res => {if (res.code === 0) {this.getDeviceList()} else {this.$message.error('查询失败!')}this.loading = falsethis.updateDeviceVisible = false;}).catch(err => {this.loading = falsethis.$message.error(err)})},getCommandList() {getCommandList().then(res => {if (res.code === 0) {this.commandListData = res.data;} else {this.$message.error('查询失败!')}this.loading = false}).catch(err => {this.loading = falsethis.$message.error(err)})},addCommand() {addCommand(this.commandInfo).then(res => {if (res.code === 0) {this.getCommandList()} else {this.$message.error('命令添加失败!')}this.loading = falsethis.addCommandVisible = false;}).catch(err => {this.loading = falsethis.$message.error(err)})},updateCommand(data){this.addCommandVisible = true;this.commandInfo.name = data.namethis.commandInfo.commands = data.commands},deleteCommand(name){deleteCommand(name).then(res => {if (res.code === 0) {this.getCommandList()} else {this.$message.error('查询失败!')}this.loading = false}).catch(err => {this.loading = falsethis.$message.error(err)})},executeCommand(name){if (this.executeParam.devices.length === 0){this.$message.error('请先在左侧选择设备!')return}this.executeParam.name = nameexecuteCommand(this.executeParam).then(res => {if (res.code === 0) {this.$message.success('执行完成!')} else {this.$message.error('执行失败!')}this.loading = false}).catch(err => {this.loading = falsethis.$message.error(err)})},// 添加indextableRowClassName({row, rowIndex}) {row.index = rowIndex},// 添加操作addForm() {if (this.isDataComplete()) {this.commandInfo.commands.push({operation: '',param: '',delayTime: ''})} else {this.$message({message: '请完善信息后再添加',type: 'warning'})}},isDataComplete() {return this.commandInfo.commands.every((item) => item.operation)},// 删除操作removeIdx(item, index) {this.commandInfo.commands.splice(index, 1)this.$message({message: '删除成功',type: 'success'})}}}
</script>
<style scoped></style>
实际的效果可以看下视频演示
如何用电脑批量操作多部手机
相关文章:

如何用电脑批量操作多部手机
如果你有很多手机,然后需要在这些手机上同时执行相同的操作,这个时候如果能有一种办法批量操作,将会大大提高效率,节省很多时间。本文将介绍基于uiautomator2实现的群控手机方案。 uiautomator2 是 一种 Android 自动化测试框架&…...

Delphi 程序例子(DPI变化自动感知及显示器相关功能演示)
目录 一、前言 二、Delphi 演示程序(D12版本,用D11也都可以) 1. 演示程序功能: 2. 程序界面: 3. 程序源代码下载(有偿): 一、前言 系列文章: 彻底搞懂 Windows 显示…...

mysql主从复制的步骤和使用到的操作命令有哪些?
步骤: 配置主服务器(Master): 启用二进制日志记录(binary logging)。配置主服务器的唯一标识(server-id)。创建用于复制的专用复制账户。 配置从服务器(Slave࿰…...

[AIGC] Java CompletableFuture:简介及示例
Java 8 引入了一个名为 CompletableFuture 的新库,正如其名称所示,该库提供了一种名为 “Completable Future” 的新 API,其主要目的是支持异步编程,并通过可搜索的操作将这些异步操作进行聚合管控。 文章目录 CompletableFuture …...

五步定位性能瓶颈
一、着手测试前的准备:优化数据流向与系统架构分析 在进行性能测试或系统优化之前,明确数据流向和系统架构的细节是至关重要的步骤。这不仅能够帮助识别潜在的瓶颈,还能确保测试用例设计的全面性与针对性。以下是关键步骤和方法:…...

currentTarget指向监听者Target:指向触发者
在JavaScript的事件处理中,currentTarget 和 target 是两个重要的属性,它们常常用于区分事件处理函数当前绑定的元素和实际触发事件的元素。这两个属性的意义可以用下面的方式解释: currentTarget 指向监听者:这意味着currentTa…...

OpenAI宫斗剧番外篇: “Ilya与Altman联手对抗微软大帝,扫除黑恶势力”,“余华”和“莫言”犀利点评
事情是这样的。 小编我是一个重度的智谱清言用户,最近智谱清言悄悄上线了一个“划词引用”功能后,我仿佛打开了新世界的大门。我甚至用这个小功能,玩出来了即将为你上映的《OpenAI宫斗剧番外篇》。 3.5研究测试:hujiaoai.cn 4研…...

网关路由SpringCloudGateway、nacos配置管理(热更新、动态路由)
文章目录 前言一、网关路由二、SpringCloudGateway1. 路由过滤2. 网关登录校验2.1 鉴权2.2 网关过滤器2.3 登录校验2.3.1 JWT2.3.2 登录校验过滤器 3. 微服务从网关获取用户4. 微服务之间用户信息传递 三、nacos配置管理问题引入3.1 配置共享3.1.1 在Nacos中添加共享配置3.1.2 …...

关于linux的防护,以及群集你要知道的有哪些11-搭建Zabbix监控系统
1、zabbix具备功能 主机的性能监控、网络设备性能监控、数据库性能监控、多种警告方式、详细报表图表绘制 2、zabbix的监测对象 Linux服务器、Windows服务器、路由器、交换机等网络设备 3、zabbix的监控架构 server-client架构:适用于网络比较简单,…...

腾讯云环境安装单机版minio
Minio 下载安装 wget https://dl.min.io/server/minio/release/linux-amd64/minio修改minio 文件为可执行文件 chmod x minio3、启动,随机端口启动 ./minio server /data/miniodata # 或者指定密码执行 MINIO_ACCESS_KEYmyminioadmin MINIO_SECRET_KEYmyminioadm…...

蓝桥杯2023(十四届)省赛——统计日期(八重神子)
统计日期 2.日期统计 - 蓝桥云课 (lanqiao.cn) 其实一开始我想直接暴力的,然后写着写着突然觉得可以优化一下: 优化方法:先找所有2023的位置,记录初始和最后的位置 找出所有合法日期的位置,使用前缀和,计…...

【Redis基础知识一】
Redis基础知识One Redis简介为什么用 Redis 作为 MySQL 的缓存?1.Redis 具备高性能2.Redis具备高并发 Redis包含的数据结构Redis里面数据结构的应用场景Redis里面数据结构的实现 Redis线程模型补充什么是跳表跳表特性 Redis采用单线程为什么还这么快 Redis简介 Redi…...

如何在go项目中实现发送邮箱验证码、邮箱+验证码登录
前期准备 GoLand :2024.1.1 下载官网:https://www.jetbrains.com/zh-cn/go/download/other.html Postman: 下载官网:https://www.postman.com/downloads/ 效果图(使用Postman) Google: QQ: And …...

Docker 部署 Nginx 实现一个极简的 负载均衡
背景: Nginx是异步框架的网页服务器,其常用作反向代理(负载均衡器)。在一般的小项目中, 服务器不多, 如果不考虑使用服务注册与发现, 使用Nginx 可以容易实现负载均衡。 在特此写一个快速入门 Nginx 的技术贴, 使用 Docker 部署 Nginx, 实现一个极简的加权轮询负载均…...

Java刷题总结(面试)
1、String类 String不可变 java 中String是 immutable的,也就是不可变,一旦初始化,其引用指向的内容是不可变的。 也就是说,String str “aa”;str“bb”;第二句不是改变“aa”所存储地址的内容…...

ipad air6电容笔推荐,2024十大高性价比电容笔排行榜!
电容笔作为ipad的最佳拍档,为学生党和打工人带来了极大的便利,二者搭配效率真的大大提升,但是,如何选购一支适合自己的电容笔呢?作为一个对数码设备非常感兴趣并且有一定了解的人,我根据自己多年的使用经…...

Java Memorandum
Java Memorandum 1 定义安全的集合2 collection集合用迭代器删除元素时避免并发修改异常3 异常捕获4 RequestBody和RequestParam和Parameter区别4.1 RequestBody4.2 RequestParam4.3 Parameter 1 定义安全的集合 void old() {ArrayList<Apple> apples new ArrayList<…...

大数据学习之 Hadoop部署
Hadoop部署 Linux桌面模式关闭 # 设置 systemctl set-default multi-user.target # 重启 reboot防火墙关闭 systemctl status firewalld systemctl stop firewalld # 关闭开机自启 systemctl disable firewalld配置Java环境 echo $JAVA_HOME java -version # Java配置 # 上传ja…...

xxe漏洞--xml外部实体注入漏洞
1.xxe漏洞介绍 XXE(XML External Entity Injection)是一种攻击技术,它允许攻击者注入恶意的外部实体到XML文档中。如果应用程序处理XML输入时未正确配置,攻击者可以利用这个漏洞访问受影响系统上的敏感文件、执行远程代码、探测内…...

Nginx反向代理与负载均衡:让网站像海豚一样灵活
引言:"当网站遇上海豚:Nginx让数据流动更流畅!"想象一下,你的网站是一片繁忙的海域,而Nginx就像一群聪明的海豚,它们不仅能够迅速地找到最佳的捕食路线(反向代理)…...

企业应考虑的优秀云安全措施
作为云客户,企业有责任确保正确使用他们提供的工具来保证数据和应用程序的安全。让德迅云安全来跟大家一起研究一些典型企业应该考虑的优秀云安全措施。 在数据安全和隐私方面,企业是否在努力跟上疫情的发展?企业不是一个人。就像多年以前,C…...

如何将老板的游戏机接入阿里云自建K8S跑大模型(下)- 安装nvidia/gpu-operator支持GPU在容器中共享
文章目录 安装nvidia/gpu-operator支持GPU在容器中共享 安装nvidia/gpu-operator支持GPU在容器中共享 安装 nvidia/gpu-operator遇到两个问题: 由于我们都懂的某个原因,导致某些镜像一直现在不成功。 解决办法,准备一个🪜&#…...

代码随想录-Day16
104. 二叉树的最大深度 方法一:深度优先搜索 class Solution {public int maxDepth(TreeNode root) {if (root null) {return 0;} else {int leftHeight maxDepth(root.left);int rightHeight maxDepth(root.right);return Math.max(leftHeight, rightHeight) …...

31.@Anonymous
1►@Anonymous原理 大家应该已经习惯我的教学套路,很多时候都是先使用,然后讲述原理。 上节课我们使用了注解@Anonymous,然后接口就可以直接被访问到了,不用token!不用token!不用token!。 我们一般知道,注解是给程序看的,给机器看的,当然也是给程序员看的。注解如果…...

oracle 表同一列只取最新一条数据写法
select * from (select t.*,row_number() over(partition by 去重列名 order by 排序列名 desc) as rnfrom 表名)where rn1 1.row_number() over(....): 为每条数据分配一个行号,1.2.3....这样的 2.partition by : 以某列作为分组,每个分组行号从1开始…...

C语言游戏实战(12):植物大战僵尸(坤版)
植物大战僵尸 前言: 本游戏使用C语言和easyx图形库编写,通过这个项目我们可以深度的掌握C语言的各种语言特性和高级开发技巧,以及锻炼我们独立的项目开发能力, 在开始编写代码之前,我们需要先了解一下游戏的基本规则…...

提权方式及原理汇总
一、Linux提权 1、SUID提权 SUID(设置用户ID)是赋予文件的一种权限,它会出现在文件拥有者权限的执行位上,具有这种权限的文件会在其执行时,使调用者暂时获得该文件拥有者的权限。 为可执行文件添加suid权限的目的是简…...

【leetcode----二叉树中的最大路径和】
二叉树中的 路径 被定义为一条节点序列,序列中每对相邻节点之间都存在一条边。同一个节点在一条路径序列中 至多出现一次 。该路径 至少包含一个 节点,且不一定经过根节点。 路径和 是路径中各节点值的总和。 给你一个二叉树的根节点 root ,…...

Rust: 编译过程中链接器 `cc` 没有找到
这个错误信息表明在编译过程中链接器 cc 没有找到。cc 通常是 C 编译器的符号链接,它指向系统上的实际 C 编译器,如 gcc 或 clang。这个错误通常意味着你的系统缺少必要的编译工具链。 要解决这个问题,你需要确保你的系统上安装了 C 编译器。…...

【vue-3】动态属性绑定v-bind
1、文本动态绑定: <input type"text" v-bind:value"web.url"> 简写: <input type"text" :value"web.url"> 2、文字样式动态绑定 <b :class"{textColor:web.fontStatus}">vue学…...