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

简介

有的时候要对手机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指令基本用法(官网)

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

AI Agent教育行业落地案例

【AI赋能教育】揭秘Duolingo背后的AI Agent,让学习更高效、更有趣! ©作者|Blaze 来源|神州问学 引言 随着科技的迅猛发展,人工智能技术已经逐步渗透到我们生活的各个方面。而随着AI技术的广泛应用,教育培训正引领着一场新的…

SHELL编程(三)网络基础命令 Makefile

目标 一、网络基础及相关命令(一)网络相关命令(二)重启网络服务 二、Makefile(一)标签式语法(二)目标:依赖 式语法1. 格式2. 编译流程:预处理 编译 汇编 链接3. 目标和伪…

7B2PRO5.4.2主题 wordpress主题开心版免授权源码

这款7B2 PRO主题也是很多小伙伴儿喜欢的一个主题,有伙伴儿反馈说想学习下新版本,这不就来了,免受权开心版本可供学习使用,要运营还是尊重下版权到官网进行购买吧。 下载:7B2PRO5.4.2 wordpress主题免授权直接安装_麦…

常见的几种数据库通过SQL对表信息进行查询

一、前言 我们查询数据库表的信息,一般都使用界面化的连接工具查看,很少使用SQL语句去查,而且不同的数据库SQL语句又各自有差异。但如果通过代码去获取数据库表的信息,这时就需要通过SQL语句去查了,这个在逆向代码生成…

接口测试及接口测试常用的工具详解

🍅 视频学习:文末有免费的配套视频可观看 首先,什么是接口呢? 接口一般来说有两种,一种是程序内部的接口,一种是系统对外的接口。 系统对外的接口:比如你要从别的网站或服务器上获取资源或信息…

python打造自定义汽车模块:从设计到组装的全过程

新书上架~👇全国包邮奥~ python实用小工具开发教程http://pythontoolsteach.com/3 欢迎关注我👆,收藏下次不迷路┗|`O′|┛ 嗷~~ 目录 一、引言 二、定义汽车模块与核心类 三、模拟汽车组装过程 四、抽象与封装 五、完整汽车…

08、SpringBoot 源码分析 - 自动配置深度分析一

SpringBoot 源码分析 - 自动配置深度分析一 refresh和自动配置大致流程如何自动配置SpringBootApplication注解EnableAutoConfiguration注解AutoConfigurationImportSelector自动配置导入选择器DeferredImportSelectorHandler的handleDeferredImportSelectorGroupingHandler的r…

前端笔记-day07

学成在线网站 文章目录 效果图代码展示index.htmlindex.cssbase.css 效果图 代码展示 index.html <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-w…

怎么挑选护眼灯?分析选护眼台灯该注意什么问题

各位家长可能已经注意到一个令人关切的现象&#xff1a;戴眼镜的孩子人数在不断上升&#xff0c;许多孩子正在接受眼部治疗。眼睛健康的问题变得越来越普遍&#xff0c;这无疑令人担忧。在当今数字化时代&#xff0c;孩子们每日需长时间阅读和使用电子设备&#xff0c;这对他们…

H6246 60V降压3.3V稳压芯片 60V降压5V稳压芯片IC 60V降压12V稳压芯片

H6246降压稳压芯片是一款电源管理芯片&#xff0c;为高压输入、低压输出的应用设计。以下是对该产品的详细分析&#xff1a; 一、产品优势 宽电压输入范围&#xff1a;H6246支持8V至48V的宽电压输入范围&#xff0c;使其能够适应多种不同的电源环境&#xff0c;增强了产品的通用…

如何制定一个有效的现货黄金投资策略(EEtrade)

制定一个有效的现货黄金投资策略涉及多方面的考量。以下是几个步骤和考虑因素&#xff0c;可以帮助您建立一个坚实的投资策略&#xff1a; 1. 设立清晰的投资目标 决定您投资现货黄金的主要目的。是否是为了短期利润&#xff0c;长期保值增值&#xff0c;还是为了投资组合的多…

新增长100人研讨会:台州制造业企业共探数字驱动下的业绩增长策略

2024年5月17日&#xff0c;纷享销客联合鑫磊压缩机&#xff0c;在台州举办了一场主题为“数字化驱动下的业绩增长策略”的研讨会。本次会议汇聚台州多家制造行业的10余位数字化管理者&#xff0c;共同探讨在数字化转型浪潮中&#xff0c;制造业如何实现业绩的持续增长。 鑫磊压…

数据库(9)——DQL基础查询

数据查询 数据查询是SQL中最复杂的&#xff0c;语法结构为 SELECT 字段列表 FROM 表名列表 WHERE 条件列表 GROUP BY 分组字段列表 HAVING 分组后字段列表 ORDER BY 排序字段列表 LIMIT 分页参数 查询多个字段 SELECT 字段1&#xff0c;字段2...FROM…

QT:协议概述

文章目录 概念帧结构&#xff1a;通信流程 示例&#xff1a;请求帧&#xff1a;响应帧&#xff1a; 概念 帧结构&#xff1a; | SOF (1 byte) | Frame Length (1 byte) | Command (1 byte) | Data Field (N bytes) | Checksum (1 byte) | 通信流程 示例&#xff1a; 请求帧&a…

使用js实用工具库lodash做对象的深拷贝

const lodash require(lodash)let obj {user: {name: xutongbao}}let objCopy lodash.cloneDeep(obj)objCopy.user.name xuconsole.log(obj)console.log(objCopy)https://www.lodashjs.com/ 人工智能学习网站 https://chat.xutongbao.top 参考链接&#xff1a; https://…

2024儿科常用心理评估量表汇总,附详细操作步骤与评定标准

在社会的快速发展以及家庭教育模式的转变下&#xff0c;儿童心理健康问题正逐步成为公众瞩目的焦点。焦虑症、抑郁症、适应障碍等儿科常见的症状&#xff0c;不仅对孩子的身心健康构成威胁&#xff0c;更可能在他们的学习旅程和社交互动中制造重重障碍。 儿科医师常用评估量表…

ABB焊接功能介绍

1.基本配置 1.2配置Robot Ware Arc 2.焊接语句 2.1直线焊接语句 过渡点指令必须位于起弧指令与熄弧指令之间&#xff0c;不能单独使用。 2.2直线焊接示例 2.3圆弧焊接语句 2.4圆弧焊接示例 2.5摆动参数 关于ABB焊接机器人摆动参数设定 一般情况下&#xff0c;主要设置以…

springboot 两个相同类型的Bean使用@Resouce加载

问题描述 有两个相同类型的Bean 使用Service等注解注入或者Bean注入启动以后报错&#xff1a; qualifying bean of type com.fasterxml.jackson.databind.ObjectMapper available: expected single matching bean but found 2提示有相同的类型两个。 解决 * 每个Bean Resour…

一文搞透常见的Python编码陷阱(下)(分析+案例)

一个认为一切根源都是“自己不够强”的INTJ 个人主页:用哲学编程-CSDN博客专栏:每日一题——举一反三Python编程学习Python内置函数 Python-3.12.0文档解读 目录 一、当心多目标赋值中的可变变量 示例 扩展讲解 示例 增广赋值中的可变变量 示例 扩展讲解 示例 总…

C#中的事件聚合器实现方法

概述&#xff1a;_对象之间的关系_是使代码库难以理解和难以维护的原因。为了更好地理解它&#xff0c;我们求助于马丁福勒&#xff08;Martin Fowler&#xff09;&#xff1a;事件聚合器是间接的简单元素。在最简单的形式中&#xff0c;您可以让它注册到您感兴趣的所有源对象&…