Android USB调试模式下自动上下滑动(Go实现)

news2024/11/20 1:48:07

简介

有的时候要对手机UI界面进行滑动测试, 手动或许太消耗时间, 理由Android USB调试模式对UI进行上下滑动测试。

adb指令

使用adb --help 可以查看所有的adb支持指令, 但这里我们只需要上下, 使用到的指令:

  1. adb devices #列举所有设备
    在这里插入图片描述

  2. adb -s 序列号 shell wm size # 获取指定设备的屏幕分辨率
    ![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/c650c29d6be44e9d95fae456ee4ffd29.png

  3. adb -s 序列号 shell input swipe startX startY endX endY
    通过startX,startY 起点 到 endX,endY 终点, 实现左/右/上/下滑动

代码

action.go

package main

import (
	"encoding/json"
	"fmt"
	"io"
	"os"
	"os/exec"
	"path/filepath"
	"strconv"
	"strings"
	"sync"
	"time"
)

type Direction int

const (
	Move_LEFT  Direction = iota
	Move_RIGHT           = 1
	Move_UP              = 2
	Move_DOWN            = 3
)

type IJsonUnmarshal interface {
	FromJsonBytes(src []byte) error
}

type ActionConfig struct {
	BaseConfig  CommandConfig `json:"base_config,omitempty"`
	ActionItems []ActionItem  `json:"action_items,omitempty"`
}

func (a *ActionConfig) FromJsonBytes(src []byte) (err error) {
	err = json.Unmarshal(src, a)
	return
}

type ActionItem struct {
	Direction   Direction `json:"direction"`
	BeforeSleep int64     `json:"before_sleep,omitempty"`
	AfterSleep  int64     `json:"after_sleep,omitempty"`
	StartX      int       `json:"start_x"`
	StartY      int       `json:"start_y"`
	EndX        int       `json:"end_x"`
	EndY        int       `json:"end_y"`
}

type DeviceInfoItem struct {
	Width        int
	Height       int
	SerialNumber string
}

func getActionConfig() (actCfgs ActionConfig, err error) {
	var f *os.File
	f, err = os.Open("./cfg.json")
	if nil != err {
		return
	}
	defer f.Close()
	var src []byte
	src, err = io.ReadAll(f)
	if nil != err {
		return
	}
	err = actCfgs.FromJsonBytes(src)
	if nil != err {
		return
	}
	if actCfgs.BaseConfig.WorkPath == "." {
		var exePath string
		exePath, err = os.Executable()
		fmt.Println("1111: ", exePath)
		if err != nil {
			return
		}
		actCfgs.BaseConfig.WorkPath = filepath.Dir(exePath)
	}

	return
	//actCfgs = ActionConfig{
	//	ActionItems: []ActionItem{
	//		{Move_UP, 0, 5 * time.Second, 0, 0, 0, 0},
	//		{Move_UP, 0, 5 * time.Second, 0, 0, 0, 0},
	//		{Move_UP, 0, 5 * time.Second, 0, 0, 0, 0},
	//		{Move_UP, 0, 5 * time.Second, 0, 0, 0, 0},
	//		{Move_UP, 0, 5 * time.Second, 0, 0, 0, 0},
	//		{Move_UP, 0, 5 * time.Second, 0, 0, 0, 0},
	//		{Move_DOWN, 0, 5 * time.Second, 0, 0, 0, 0},
	//		{Move_DOWN, 0, 5 * time.Second, 0, 0, 0, 0},
	//		{Move_DOWN, 0, 5 * time.Second, 0, 0, 0, 0},
	//		{Move_DOWN, 0, 5 * time.Second, 0, 0, 0, 0},
	//		{Move_DOWN, 0, 5 * time.Second, 0, 0, 0, 0},
	//		{Move_DOWN, 0, 5 * time.Second, 0, 0, 0, 0},
	//	},
	//}
	//actCfgs.BaseConfig, err = GetBaseCommandConfig()
}

func doCommandAction(cmdCfg CommandConfig, callback func(output []byte) error) error {
	fmt.Println(cmdCfg)
	// 创建Cmd结构体
	cmd := exec.Command(cmdCfg.ExePath, cmdCfg.Arguments...)
	cmd.Dir = cmdCfg.WorkPath

	// 运行命令并获取输出
	output, err := cmd.CombinedOutput()
	if err != nil {
		fmt.Println("1-Error:", err)
		return err
	}

	err = callback(output)

	return err
}

func getSerialNumberAction(cmdCfg CommandConfig) (serials []string, err error) {
	cmdCfg.Arguments = []string{"devices"}
	err = doCommandAction(cmdCfg, func(output []byte) error {
		lines := strings.Split(string(output), "\n")
		serials = make([]string, 0)
		for i, line := range lines {
			if 0 == i {
				continue
			}
			fields := strings.Fields(line)
			if len(fields) > 0 {
				serials = append(serials, fields[0])
				//fmt.Println(fields[0])
			}
		}
		return nil
	})
	return
}

func getDeviceInfo(cmdCfg CommandConfig, serialNumber string) (item DeviceInfoItem, err error) {
	item.SerialNumber = serialNumber
	cmdCfg.Arguments = []string{"-s", serialNumber, "shell", "wm", "size"}
	err = doCommandAction(cmdCfg, func(output []byte) error {
		fields := strings.Fields(string(output))
		if 3 > len(fields) {
			return fmt.Errorf("fail to get device info")
		}
		strs := strings.Split(fields[2], "x")
		var temp int64
		temp, _ = strconv.ParseInt(strs[0], 10, 64)
		item.Width = int(temp)
		temp, _ = strconv.ParseInt(strs[1], 10, 64)
		item.Height = int(temp)

		return nil
	})
	return
}

func getActionItem(item DeviceInfoItem, actionItem *ActionItem) error {
	actionItem.StartX = item.Width / 2
	actionItem.EndX = item.Width / 2
	actionItem.StartY = item.Height / 2
	switch actionItem.Direction {
	case Move_UP:
		actionItem.EndY = actionItem.StartY / 2
	case Move_DOWN:
		actionItem.EndY = actionItem.StartY + actionItem.StartY/2
	case Move_LEFT:
	case Move_RIGHT:
	default:
		break
	}
	return nil
}

func deviceAction(waiter *sync.WaitGroup, actCfgs ActionConfig, serialNumber string) error {
	defer waiter.Done()
	item, err := getDeviceInfo(actCfgs.BaseConfig, serialNumber)
	if nil != err {
		return err
	}
	for i := range actCfgs.ActionItems {
		err = getActionItem(item, &actCfgs.ActionItems[i])
		if nil != err {
			return err
		}
	}

	cfg := actCfgs.BaseConfig
	cfg.Arguments = []string{"-s", serialNumber, "shell", "input", "swipe", "startX", "startY", "endX", "endY"}

	for {
		for i := range actCfgs.ActionItems {
			fmt.Println(actCfgs.ActionItems[i])
			time.Sleep(time.Duration(actCfgs.ActionItems[i].BeforeSleep) * time.Millisecond)
			cfg.Arguments[len(cfg.Arguments)-4] = strconv.Itoa(actCfgs.ActionItems[i].StartX)
			cfg.Arguments[len(cfg.Arguments)-3] = strconv.Itoa(actCfgs.ActionItems[i].StartY)
			cfg.Arguments[len(cfg.Arguments)-2] = strconv.Itoa(actCfgs.ActionItems[i].EndX)
			cfg.Arguments[len(cfg.Arguments)-1] = strconv.Itoa(actCfgs.ActionItems[i].EndY)
			if err = doCommandAction(cfg, func(output []byte) error {
				return nil
			}); nil != err {
				return err
			}
			time.Sleep(time.Duration(actCfgs.ActionItems[i].AfterSleep) * time.Millisecond)
		}
	}
}

func StartAction() error {
	var actCfgs, err = getActionConfig()
	var waiter sync.WaitGroup
	var serials []string
	serials, err = getSerialNumberAction(actCfgs.BaseConfig)
	if nil != err {
		return err
	}

	if 0 >= len(serials) {
		fmt.Println("not devices!!!")
		return nil
	}

	for i := range serials {
		waiter.Add(1)
		go deviceAction(&waiter, actCfgs, serials[i])
	}
	waiter.Wait()
	return nil
}

config.go

package main

import (
	"fmt"
	"os"
	"path/filepath"
)

const (
	adbDefaultPath = "D:\\Softwares\\Paths\\Android\\android-sdk\\platform-tools"
)

type CommandConfig struct {
	WorkPath  string   `json:"work_path"`
	ExePath   string   `json:"exe_path"`
	Arguments []string `json:"arguments,omitempty"`
}

func GetBaseCommandConfig() (cfg CommandConfig, err error) {
	var (
		exePath string
	)
	exePath, err = os.Executable()
	fmt.Println("1111: ", exePath)
	if err != nil {
		return
	}
	cfg.WorkPath = filepath.Dir(exePath)
	cfg.ExePath = fmt.Sprintf("%s\\%s", adbDefaultPath, "adb.exe")
	fmt.Println("11112: ", cfg)
	return
}

main.go

package main

import (
	"fmt"
)

func main() {
	err := StartAction()
	if nil != err {
		fmt.Println(err)
		return
	}

}

配置文件, 放到程序同级目录
cfg.json

{
  "base_config": {
    "work_path": ".",
    "exe_path": "D:/Softwares/Paths/Android/android-sdk/platform-tools/adb.exe"
  },
  "action_items": [
    {
      "direction": 2,
      "before_sleep": 0,
      "after_sleep": 2000
    }
  ]
}

最后

将手机调试模式打开, 连上电脑,执行程序,需要什么动作在配置文件:

  1. 支持多台设备;
  2. 支持多个动作, 都可以在配置文件中添加;
  3. 支持动作前后延时;

其他

ADB指令基本用法&shell指令
ADB指令基本用法(官网)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1709728.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Git简单理解

Git 概述 Git 是一个免费的开源的,分布式版本控制系统,可以快速高效的处理从小型到大型的各种项目 Git占地面积小,性能极快,具有廉价的本地库,方便的暂存区和多个工作流分支等特性 版本控制 版本控制是一种记录文件…

gitea的git库备份与恢复

文章目录 gitea库的备份与恢复概述笔记实验环境更新git for windows更新 TortoiseGit备份已经存在的gitea的git库目录使用gitea本身来备份所有git库目录将gitea库恢复到新目录m1m2m3启动gitea - 此时已经恢复完成FETCH_HEAD 中有硬写位置再查一下app.ini, 是否改漏了。m1m2 总结…

操作系统 - 文件管理

文件管理 考纲内容 文件 文件的基本概念;文件元数据和索引节点(inode) 文件的操作:建立,删除,打开,关闭,读,写 文件的保护;文件的逻辑结构;文件的物理结构目录 目录的基…

SpringBoot启动流程分析之ApplicationEnvironmentPreparedEvent事件发布(四)

SpringBoot启动流程分析之ApplicationEnvironmentPreparedEvent事件发布(四) 目录: 文章目录 SpringBoot启动流程分析之ApplicationEnvironmentPreparedEvent事件发布(四)构建环境1、创建ConfigurableEnvironment对象…

正点原子[第二期]Linux之ARM(MX6U)裸机篇学习笔记-24.3,4 SPI驱动实验-I.MX6U SPI 寄存器

前言: 本文是根据哔哩哔哩网站上“正点原子[第二期]Linux之ARM(MX6U)裸机篇”视频的学习笔记,在这里会记录下正点原子 I.MX6ULL 开发板的配套视频教程所作的实验和学习笔记内容。本文大量引用了正点原子教学视频和链接中的内容。…

宠物空气净化器推荐:性价比首选,希喂、小米、352真实PK

宠物浮毛和异味常常困扰着宠物家庭,不仅会在家中四处散布,还可能成为过敏原,引发如打喷嚏和皮肤痒等不适症状。宠物的尿液、粪便气味以及它们自身散发的体味,同样会降低室内空气质量,影响居住的舒适度。为了解决这些问…

linux系统常用压缩和解压命令

文章目录 Ubuntu 系统中的文件压缩与解压指南一、常用的压缩和解压工具二、tar 工具三、gzip 工具四、bzip2 工具五、zip 和 unzip 工具六、7z 工具乱码批量解压脚本七、总结 Ubuntu 系统中的文件压缩与解压指南 在 Ubuntu 系统中,文件压缩与解压是日常操作中非常常…

Varjo XR-4功能详解:由凝视驱动的XR自动对焦相机系统

Varjo是XR市场中拥有领先技术的虚拟现实设备供应商,其将可变焦距摄像机直通系统带入到虚拟和混合现实场景中。在本篇文章中,Varjo的技术工程师维尔蒂莫宁详细介绍了这项在Varjo XR-4焦点版中投入应用的技术。 对可变焦距光学系统的需求 目前所有其他XR头…

国内信创web中间件生态

国内信创web中间件生态 东方通 官网https://www.tongtech.com/pctype/25.html 宝蓝德 官网https://www.bessystem.com/product/0ad9b8c4d6af462b8d15723a5f25a87d/info?p101 金蝶天燕 官网 https://www.apusic.com/list-117.html 中创 官网http://www.inforbus.com…

【C++】<知识点> 标准模板库STL(下)

文章目录 六、set与multiset 1. 常用成员函数 2. pair模板 3. set 4. multiset 七、map与multimap 1. map 2. multimap 3. 应用实例 八、容器适配器 1. stack 2. queue 3. priority_queue 九、算法 六、set与multiset 1. 常用成员函数 iterator find(const T&am…

(C11) 泛型表达式

文章目录 ⭐语法⭐举例🚩判断对象类型🚩判断指针🚩函数重载🚩嵌套使用 END ⭐语法 Ref: 泛型选择 (C11 起) - cppreference.com 关键词: Genericdefault _Generic(控制表达式 , 关联列表) (C11 起) 关联列表 类型名:…

SQLI-labs-第二十三关

第二十三关 目录 第二十三关 1、判断注入点 2、判断数据库 3、判断表名 4、判断字段名 5、获取数据库的信息 6、使用group_concat() 和concat_ws() 知识点:注释符过滤绕过 思路: 分析源码可知,使用了preg_replace()函数过滤了注释符…

计算机图形学入门01:概述

1.什么是图形学? The use of computers to synthesize and manipulate visual information. 图形学是合成和操纵视觉信息的计算机应用。 百度百科:计算机图形学(Computer Graphics,简称CG)是一种使用数学算法将二维或三维图形转化为计算机显示器的栅格…

2024年统计、数据分析与大数据技术国际会议(SDBT 2024)

2024年统计、数据分析与大数据技术国际会议(SDBT 2024) 2024 International Conference on Statistics, Data Analysis, and Big Data Technology 【重要信息】 大会地点:广州 大会时间:2024年7月22日 大会官网:http…

鸿蒙开发接口UI界面:【@ohos.router (页面路由)】

页面路由 说明开发前请熟悉鸿蒙开发指导文档:gitee.com/li-shizhen-skin/harmony-os/blob/master/README.md点击或者复制转到。 本模块首批接口从API version 8开始支持。后续版本的新增接口,采用上角标单独标记接口的起始版本。页面路由需要在页面渲染完…

VUE3+TS+elementplus+Django+MySQL实现从数据库读取数据,显示在前端界面上

一、前言 前面通过VUE3和elementplus创建了一个table,VUE3TSelementplus创建table,纯前端的table,以及使用VUE3TSelementplus创建一个增加按钮,使用前端的静态数据,显示在表格中。今天通过从后端获取数据来显示在表格…

Keras深度学习框架第二十四讲:KerasNLP概述

1、KerasNLP简介 KerasNLP是一个与TensorFlow深度集成的库,旨在简化NLP(自然语言处理)任务的建模过程。它提供了一系列高级API,用于预处理文本数据、构建序列模型和执行常见的NLP任务,如情感分析、命名实体识别和机器…

PgMP:项目集管理,哪些人适合学习?

美国项目管理协会(PMI)对项目集经理(Program Manager)的角色做出如下的定义: 在最少的领导/监督下,项目集经理PgMP负责在商业和组织目的下协调管理多个相关项目。这些项目含有跨部门、组织、地理区域…

C 基础环境配置(vscode || vs)

目录 一.发展 二. 环境设置 1.vs2022 2.vscode (1.)首先下载VsCode (2)安装vsCode插件 (3)下载MinGW-W64 (4)配置文件 (5)注意把里面配置的:mingw64路径改为自己的路径 (6)示例代码 三.总结 一.发展 编程语言的发展 机器语言(打孔纸带编程),汇编语言,高级语言,一步步…

猫耳 WebSocket 跨端优化实践

前言 在现代的移动应用程序中,长连接是一种不可或缺的能力,包括但不限于推送、实时通信、信令控制等常见场景。在猫耳FM的直播业务中,我们同样使用了 WebSocket 长连接作为我们实时通信的基础。 在我们推进用户体验优化的工作中,…