Skip to content

fantonglang/go-mobile-automation

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

6 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

简体中文 | English

GO手机自动化SDK

功能齐全的手机自动化Golang SDK,支持编译成二进制可执行文件,适合自动化流程部署到云手机,脱离ADB连接稳定高效执行

目的

在(手机)自动化领域,其实python是绝对的主流,其他语言诸如Golang/C#/C++在整个生态中占的份额相对来说比较少。对于安卓手机自动化开发来说,我们起码有下面的工具了:

  1. Appium(多语言/全平台自动化支持)
  2. Uiautomator2/ATX (python)
  3. 各RPA平台

我们有这么多python库可以用,为什么要再做个Golang自动化SDK呢?

  1. 容易部署 - Golang一般只需要部署一个二进制可执行文件,不像python/javascript需要安装一大堆依赖,在墙内如果没有一个稳定可靠的镜像(的确,javascript有CNPM,也可以自己搭镜像),如果安装依赖挂了就没有然后了。即使下载依赖没有问题,也会有一些依赖版本冲突的问题。C#/JAVA有运行时依赖,你一般不会知道用户会使用哪个版本的.NET SDK/JDK。
  2. 部署在云手机上稳定可靠 - 由于云手机使用虚拟化技术,它不像传统机架集群会遇到各式各样的硬件文件 - 比如电池爆膨了(一般2年寿命,工作室机更差),比如电源供电不足手机断电,比如USB线接触不良,等等。云手机相对贵一点但是稳定且省人力成本。但是有些云手机使用外网远程adb连接,如果自动化脚本host在本地的PC/MACOS上远程操控云手机,稳定性非常取决于网络的稳定性。我们当然更希望使用Golang/C++的方式:编译一个二进制可执行文件,推到云手机上定时/按某种触发条件去执行,脱离ADB连接
  3. 稳定 - 事实上python能直接跑在手机上。pyto-python3这个app提供了host python脚本在手机上的可能。但是手机应用比较容易被Android系统杀死,而二进制可执行文件一般不会。

所以我们采用Golang做安卓自动化语言是有道理滴~

再说说其他自动化工具坑的地方:

  1. Appium安装复杂,而且图像识别只支持模版匹配,中间层N多,上层是well known的protocol,要优化性能或者加一些底层支持非常困难。要部署Appium到一台新的主机虚拟机,别提有多烦了
  2. UiAutomator2/ATX。这个软件库我是极其推崇,它的API支持比我们的Golang SDK要丰富,毕竟人家才是原版。但是它不提供直接将流程部署到手机的能力。这不能说是缺陷,因为考虑到自动化开发的受众群体,python是绝对绝对的主流
  3. 云扩RPA。不用说了,烂的一匹。

唯一要注意的是Android是只读文件系统,这意味着打日志的话我们可能需要批量收集,在程序里面做批量推送到日志服务器

项目启发

项目启发于OpenAtxUiautomation2 python库。我事实上在项目中首先大量使用了uiautomator2,在玩儿了云手机之后,因为有需要,自然而然就有了这个Golang库(因为Golang比C++简单,ARM Cortex A编辑不需要NDK)

它事实上是OpenAtx的又一个客户端,但是它可以跨平台执行。它实现了大部分Uiautomator2的API。这么做的好处是,OpenAtx的工具链,包括它的Inspector weditor - 这个真的很好用,而且是web应用不需要额外占我太多存储空间

而且有别于Appium,OpenAtx不需要依赖Session同时在一个App上执行流程,它可以同时操作一个App,点击另一个App的浮层。这是我喜欢并使用OpenAtx的原因。

Quick start

我分四步讲

  1. 设置安卓手机
  2. 设置开发环境
  3. 创建Golang自动化项目
  4. 部署&执行

设置安卓手机

  1. 安装你要自动化的APP(注意:安装特定的版本 - 因为不同版本同一个元素的属性很可能是不一样的 - 即使同一个版本有些属性其实也会变 - 反映到脚本中就是xpath)
$ adb install [package.apk]
  1. 下载 atx-agent: 点这里 选择 armv7 除非你部署的手机是x86手机模拟器
  2. 解压 atx-agent 并安装, 这里有安装说明: 点这里 或者看下面的命令行指令:
$ adb push atx-agent /data/local/tmp
$ adb shell chmod 755 /data/local/tmp/atx-agent
# 后台模式执行atx-agent
$ adb shell /data/local/tmp/atx-agent server -d

# 或者后台模式重启atx-agent
$ adb shell /data/local/tmp/atx-agent server -d --stop
  1. 下载 app-uiautomator-test.apk 和 app-uiautomator.apk 点这里 然后用adb install命令安装
$ adb install app-uiautomator-test.apk
$ adb install app-uiautomator.apk
  1. 给应用 "ATX" 所有权限,并且打开 "ATX" 检查有任何运行时权限请求的话,选择一直许可。
  2. 打开应用 "ATX" 点击 "启动UIAUTOMATOR", 点击 "开启悬浮窗"

设置开发环境

  1. 安装 Python3(版本 3.6+) from here
  2. 安装 weditor
$ pip3 install -U weditor
  1. 打开 weditor, 它就是你的UI Inspector,在命令行中输入,
$ weditor

创建Golang自动化项目

  1. 创建文件夹 helloworld
  2. 打开命令行并输入
$ go mod init helloworld
  1. 添加依赖
$ go get github.com/fantonglang/go-mobile-automation
  1. 添加程序入口 - 创建 main.go 文件
  2. 这里 是示例 main.go 文件

部署&执行

# 交叉编译(cross build) linux/arm 可执行文件
$ GOOS=linux GOARCH=arm go build
# 部署 - helloworld 是可执行文件名,和Go模块名是相同的
$ adb push helloworld /data/local/tmp
$ adb shell chmod 755 /data/local/tmp/helloworld
# 运行
$ adb shell /data/local/tmp/helloworld

如果你用后台模式启动程序,程序启动之后就不需要连接电脑。adb shell命令和普通linux系统是一样的。你可以输入

# nohup保证当关闭terminal session的时候,程序不会被杀死,&保证在后台运行程序
$ adb shell nohup /data/local/tmp/helloworld &

例子

这里 是一个能跑起来的例子。仔细阅读main函数的注释,里面有手机设置,环境安装,编译,调试,部署,执行的教程。

APIS

Connect to a device

Device APIS

Input Method

XPATH

UI Object

Connect to a device

有两种类型的连接:

  1. 如果程序是部署在手机上,使用如下代码:
package main

import (
	"log"
	"github.com/fantonglang/go-mobile-automation/apis"
)
...
// 不需要指定设备ID,因为程序并不需要从电脑通过ADB连接手机
d := apis.NewNativeDevice()
  1. 如果是在电脑端调试或部署,使用如下代码:
package main

import (
	"log"
	"github.com/fantonglang/go-mobile-automation/apis"
)


//这里 c574dd45 是设备ID, 你可以从adb devices指令中获取到它, 把它替换成你自己的手机设备ID
d, err := apis.NewHostDevice("c574dd45")
if err != nil {
  log.Println("failed connecting to device")
  return
}

结合上述两个代码片段,下面的代码既能在电脑端(假定是x86架构)调试的时候工作,又能在Android设备中部署之后工作。它判断运行时如果是ARM,使用手机的方式连接;反之使用电脑的方式连接。特别注意苹果MAC最近的ARM架构电脑,如果这种情况,最好判断一下系统(GOOS)

package main

import (
	"log"
	"runtime"
	"github.com/fantonglang/go-mobile-automation/apis"
)

func getDevice() *apis.Device {
	if runtime.GOARCH == "arm" {
		return apis.NewNativeDevice()
	}
	//这里 c574dd45 是设备ID, 你可以从adb devices指令中获取到它, 把它替换成你自己的手机设备ID
	_d, err := apis.NewHostDevice("c574dd45")
	if err != nil {
		log.Println("101: failed connecting to device")
		return nil
	}
	return _d
}
...
d := getDevice()

Device APIS

这部分展示如何执行常见的设备操作

Shell commands

示例: 强制停止抖音APP

d.Shell(`am force-stop com.ss.android.ugc.aweme`)

示例: 打开抖音APP

// 使用dumpsys命令,你可以找到APP的启动Activity。所以目前并不实现uiautomator2的启动APP API,以及session API。
d.Shell(`am start -n "com.ss.android.ugc.aweme/.main.MainActivity"`)

Retrieve the device info

获取详细设备信息

info, err := d.DeviceInfo()
if err != nil {
  log.Println("get device info failed")
  return
}
bytes, err := json.Marshal(info)
if err != nil {
  log.Println("error marshalling")
  return
}
fmt.Println(string(bytes))

下面是可能的输出:

{
  ...
  "version":"11",
  "serial":"c574dd45",
  ...
  "sdk":30,
  "agentVersion":"0.10.0",
  "display":{"width":1080,"height":2340}
  ...
}

获取窗口大小:

w, h, err := d.WindowSize()
if err != nil {
  log.Println("get window size failed")
  return
}
fmt.Printf("w: %d, h: %d\n", w, h)
// 设备竖屏时的输出示例: w: 1080, h: 2340
// 设备横屏时的输出示例: w: 1080, h: 2340

Clipboard

获取和设置剪切板内容

设置剪切板内容

err := d.SetClipboard("aaa")
if err != nil {
  log.Println("error clipboard")
  return
}

获取剪切板内容: 在Android大于9.0这个API并不工作. 但是大多数云手机使用低版本Android(比如7.0),所以我并不care

a, err := d.GetClipboard()
if err != nil {
  log.Println("error clipboard")
  return
}
fmt.Println(a)

Key Events

  • 打开/关闭屏幕
err := d.KeyEvent(KEYCODE_POWER) // 打开关闭屏幕都是按电源键
  • Home键
err := d.KeyEvent(KEYCODE_HOME)

d.KeyEvent 是在调用 Android 的 "input keyevent " 命令, 请参照 这个文档, 或者你如果没有翻墙工具, 参照 这个文档

Press Key

示例: 按Home键

err := d.Press("home")

Press API支持下面的按键:

VSK_HOME        = "home"
VSK_BACK        = "back"
VSK_LEFT        = "left"
VSK_RIGHT       = "right"
VSK_UP          = "up"
VSK_DOWN        = "down"
VSK_CENTER      = "center"
VSK_MENU        = "menu"
VSK_SEARCH      = "search"
VSK_ENTER       = "enter"
VSK_DELETE      = "delete"
VSK_DEL         = "del"
VSK_RECENT      = "recent" //recent apps
VSK_VOLUME_UP   = "volume_up"
VSK_VOLUME_DOWN = "volume_down"
VSK_VOLUME_MUTE = "volume_mute"
VSK_CAMERA      = "camera"
VSK_POWER       = "power"

New command timeout

设置Uiautomator服务的超时时间

err := d.SetNewCommandTimeout(300) // 单位秒

Screenshot

  • 截屏并保存文件 - 注意 Android 使用只读文件系统, 这个API只有在电脑端有效.
err := d.ScreenshotSave("sc.png")
  • 截屏并获取[]byte字节流 (推荐, 因为opencv使用cv::imdecode函数能直接读取字节流)
bytes, err := d.ScreenshotBytes()
  • 截屏并获取image.Image对象 (如果需要不同的图片编码,比如jpeg/png,使用image.Image可以帮你做到转换)
img, format, err := d.Screenshot() // img 是 image.Image 对象, format的示例: "jpeg"

UI Hierarchy

  • 获取UI结构的XML文本
content, err := d.DumpHierarchy(false, false) // content 是文本
  • 将UI结构的XML文本转换成 *xmlquery.Node 对象. (我们如果要基于snaphot执行xpath查询,那 *xmlquery.Node 对象是非常有用的 - 它不涉及Uiautomator调用,所以速度会非常快,适合广告页面识别关闭的场景)
doc, err := FormatHierachy(content) // doc 是 *xmlquery.Node 对象

Touch

模拟“手指按下触屏”,“手指按住触屏拖动”,以及“手指离开触屏”

  • 获取 touch 对象
touch := d.Touch()
  • 手指按下触屏 - 在某个位置
/* (相对于屏幕左上角)手指按下触屏,位置为
 *  x: 50% 宽度坐标
 *  y: 60% 高度坐标
 */ 
err := touch.Down(0.5, 0.6) 
  • 手指按住触屏拖动 - 到某个位置
/* (相对于屏幕左上角)然后拖动到下述位置
 *  x: 50% 宽度坐标
 *  y: 10% 高度坐标
 */ 
err := touch.Move(0.5, 0.1) 
  • 手指离开触屏
/* (相对于屏幕左上角)然后手指离开触屏,位置为
 *  x: 50% 宽度坐标
 *  y: 10% 高度坐标
 */ 
err := touch.Up(0.5, 0.1) 

Click

在指定坐标位置点击屏幕

  • 使用百分比 - 如果x,y的任意一个值在 [0,1) 的范围内,它就是表示百分比,反之如果在[1, maxScreenWidth/maxScreenHeight]的范围内,它就是绝对坐标值
/* 相对于屏幕左上角点击
 *  x: 48.1% 宽度坐标
 *  y: 24.6% 高度坐标
 */ 
err := d.Click(0.481, 0.246)
  • 使用绝对坐标 - 如果x,y的任意一个值在 [0,1) 的范围内,它就是表示百分比,反之如果在[1, maxScreenWidth/maxScreenHeight]的范围内,它就是绝对坐标值
// 相对于屏幕左上角点击(x: 481, y: 246) 
err := d.Click(481, 246)

Double Click

双击

err := d.DoubleClickDefault(0.481, 0.246)

Long Click

长按点击,相当于按下和松开之间隔了一个给定时间,默认0.5s。函数名后面带Default的一般有一个非Default版本,提供更多参数选择。

err := d.LongClickDefault(0.481, 0.246)

Swipe

滑动

  • 从起始点 (fx, fy) 滑动到终点 (tx, ty)
var fx, fy, tx, ty float32 = 0.5, 0.5, 0, 0
err := d.SwipeDefault(fx, fy, tx, ty)
  • 多点滑动, 参数可以指定多个apis.Point4Swipe对象,代表滑动途径的坐标点
// 滑动途经 起点(x=width*0.5, y=height*0.9) 到 终点(x=width*0.5,y=height*0.1),滑动总时长0.1秒
err := d.SwipePoints(0.1, apis.Point4Swipe{0.5, 0.9}, apis.Point4Swipe{0.5, 0.1})
  • 从起点(fx, fy) 拖动到 终点(tx, ty)
var fx, fy, tx, ty float32 = 0.5, 0.5, 0, 0
err := d.DragDefault(fx, fy, tx, ty)

Set Orientation

设置屏幕方向,接受下面这四种参数:

  • "n" - 代表正常的竖屏
  • "l" - 代表朝左的横屏
  • "u" - 代表倒过来的竖屏
  • "r" - 代表朝右的横屏
err := d.SetOrientation("n")

Open Quick Settings

打开快速设置菜单

err := d.OpenQuickSettings()

Open Url

打开浏览器输入URL,打开网页

err := d.OpenUrl("https://bing.com")

Show float window

显示弹窗。这个操作是openatx特有的 - 它打开一个浮层来做应用保活。在这个SDK的产品级部署上面,我们在每次流程开始时都会用d.Shell函数打开ATX应用,再使用自研的分辨率无关的控件图像识别找到“启动UIAUTOMATOR”按钮并点击,再打开ATX应用用同样的方法找到“开启悬浮窗”按钮并点击。以避免在流程执行的过程中,由于uiautomator已经被杀死而重新唤起它 - 虽然这个操作会自动发生,但是有时候会很慢。

err := d.ShowFloatWindow(true)

Input Method

用来输入文字,(会使用一个特殊的输入法)

  • 清除文字
err := d.ClearText()
  • 发送动作
err := ime.SendAction(SENDACTION_SEARCH)

支持下面的动作:

SENDACTION_GO       = 2
SENDACTION_SEARCH   = 3
SENDACTION_SEND     = 4
SENDACTION_NEXT     = 5
SENDACTION_DONE     = 6
SENDACTION_PREVIOUS = 7
  • 发送按键 - 输入文字(包括中文以及其他Unicode)
err := ime.SendKeys("aaa", true)

XPATH

在Android自动化中,XPATH是最重要的寻找UI元素的方法。快速又强大

Finding elements

  • 通过Xpath查找多个元素
els := d.XPath(`//*[@text="your-control-text"]`).All()
for _, el := range els {
  ...
}
  • 通过Xpath查找一个元素(首个或者nil)
el := d.XPath(`//*[@text="your-control-text"]`).First()
  • 通过Xpath检查元素是否存在, el.First()函数当元素不存在的时候返回nil
if d.XPath(`//*[@text="your-control-text"]`).First() != nil {
  ...
}
  • 等待元素出现
el := d.XPath(`//*[@text="your-control-text"]`).Wait(time.Minute)
if el == nil {
  log.Println("element doesn't appear within 1 minute")
  return
}
...
  • 等待元素消失
ok := d.XPath(`//*[@text="your-control-text"]`).WaitGone(time.Minute)
if !ok {
  log.Println("element doesn't disappear within 1 minute")
  return
}
  • 如果我们基于UI结构的snapshot来做Xpath查询, 这样,基于一次uiautomator的调用,我们可以执行多次Xpath查询,这在需要广告识别的场景非常有效
content, _ := d.DumpHierarchy(false, false) // content 是文本
doc, _ := FormatHierachy(content) // doc 是 *xmlquery.Node 对象
...
el := d.XPath2(`//*[@text="your-control-text"]`, doc).First()

Xpath elements API

  • Xpath元素的子元素(子级后代)
// el := d.XPath(`//*[@resource-id="com.taobao.taobao:id/rv_main_container"]`).First()
children := el.Children()
for _, c := range children {
  ...
}
  • Xpath元素的兄弟元素
// el := d.XPath(`//*[@resource-id="com.taobao.taobao:id/rv_main_container"]/android.widget.FrameLayout[1]`).First()
siblings := el.Siblings()
for _, s := range siblings {
  ...
}
  • 通过Xpath查找Xpath元素的后代元素(它基于查找Xpath元素时获取的UI结构snapshot,所以不会涉及Uiautomator调用)
// el := d.XPath(`//*[@resource-id="com.taobao.taobao:id/rv_main_container"]`).First()
children := el.Find(`//android.support.v7.widget.RecyclerView`)
for _, c := range children {
  fmt.Println(*c.Info())
}
  • 获取 bounding rect

获取控件的边框,注释中解释了返回值类型

bounds := el.Bounds()
/* bounds 的类型为 *apis.Bounds:
 * type Bounds struct {
 *	  LX int // 左上角x
 *	  LY int // 左上角y
 *	  RX int // 右下角x
 *	  RY int // 右下角y
 * }
 */
  • 获取 Rect

也是获取控件的边框,注释中解释了返回值类型

rect := el.Rect()
/* rect 的类型为 *apis.Rect:
 * type Bounds struct {
 *	  LX int      // 左上角x
 *	  LY int      // 左上角y
 *	  Width int   // 控件宽度
 *	  Height int  // 控件高度
 * }
 */
  • 获取控件在UI中显示的文本
text := el.Text() // text 是文本
  • 获取控件的所有信息 - 包括文本和边框,注释中解释了返回值类型
info := el.Info()
/* info 的类型是 *apis.Info
 * type Info struct {
 *     Text               string
 *     Focusable          bool
 *     Enabled            bool
 *     Focused            bool
 *     Scrollable         bool
 *     Selected           bool
 *     ClassName          string
 *     Bounds             *Bounds
 *     ContentDescription string
 *     LongClickable      bool
 *     PackageName        string
 *     ResourceName       string
 *     ResourceId         string
 *     ChildCount         int
 * }
*/
  • 获取元素中心点位置
x, y, ok := el.Center()
  • 点击
ok := el.Click()
  • 在控件中滑动 - 如果控件是(Recycler)List
dir := apis.SWIPE_DIR_LEFT
var scale float32 = 0.8
ok := el.SwipeInsideList(dir, scale)
/* 滑动方向dir 有四种值(int):
 * SWIPE_DIR_LEFT  = 1 // 从右向左滑动
 * SWIPE_DIR_RIGHT = 2 // 从左向右滑动
 * SWIPE_DIR_UP    = 3 // 从下到上滑动
 * SWIPE_DIR_DOWN  = 4 // 从上到下滑动

 scale 是滑动距离的比例,相对于在此滑动方向下,宽度或者高度。比如,再这个例子中,我们是从右到左横向滑动,那么scale = 0.8就意味着滑动80%的宽度距离
*/
  • 输入文字
ok := el.Type("aaa")
  • 截图 - 控件截图
img := el.Screenshot() // img 是 image.Image 类型对象

UI Object

通过属性匹配方式查找UI元素. 在大多数平台,包括iOS和Windows UIA, 这种典型的辅助功能API(UI Object)远比Xpath更快. 举例来说, windows UIA, 获取桌面根元素下的XML UI结构异常的慢, 因为获取元素的信息会有大量的夸进程COM调用,它是很慢的. 但是在安卓获取XML UI结构非常的快,基本和UI Object方式一样快. 而且Xpath还更强大,支持更有表现力的查询。

与其你使用UI Object方式查找元素,我会更建议你使用上一节的Xpath方式。我给两种方式提供了类似的元素操作API,比如Info, Click, Wait等。

有些API在UI Object中看起来不那么自然,比如Count,那是因为获取所有元素在UI Object可能会牵涉非常多次的Uiautomator调用。与其让用户因为一个不那么好的查询条件等太长时间,不如把底层暴露给用户,让用户自己决定实现。

construct query

UI Object API事实上在调用这几个API前是不调用Uiautomator的:

  1. (*UIObject).Get() *UiElement,
  2. (*UIObject).Wait(timeout time.Duration) int,
  3. (*UIObject).WaitGone() bool
  • 基于属性值构造UI Object查询
uo := d.UiObject(apis.NewUiObjectQuery("resourceId", `com.taobao.taobao:id/rv_main_container`)) // 这个 api 接受多个 NewUiObjectQuery 参数, AND关系
  • 构造兄弟元素UI Object查询 - 这个API的Uiautomator返回有点怪,稍微注意一下
c := d.UiObject(apis.NewUiObjectQuery("resourceId", `com.taobao.taobao:id/sv_search_view`)).Sibling(apis.NewUiObjectQuery("className", "android.widget.FrameLayout"))
  • 构造后代元素UI Object查询 - 为了和uiautomation2 python库尽可能保持API一致,我使用Child的方法名称,而不是Descendant
c := d.UiObject(apis.NewUiObjectQuery("resourceId", `com.taobao.taobao:id/rv_main_container`)).Child(apis.NewUiObjectQuery("className", "android.widget.LinearLayout"))
  • 构建指数UI Object查询 - 一个查询条件可能返回多个元素,当你通过Count API知道一共有多少个匹配的元素时,你可以指定小于Count值的Index(从0开始),用于指定获取哪个元素
c := d.UiObject(
		apis.NewUiObjectQuery("className", `android.support.v7.widget.RecyclerView`)).Index(0)

execute ui object query

执行UI Object查询

  • 获取第一个UI元素
el := d.UiObject(apis.NewUiObjectQuery("resourceId", `com.taobao.taobao:id/rv_main_container`)).Get() // 返回 *apis.UiElement 对象
  • 获取匹配查询的元素数量
count := d.UiObject(apis.NewUiObjectQuery("resourceId", `com.taobao.taobao:id/rv_main_container`)).Count()
  • 获取第N个元素 - 代码示例片段中 - 是第三个( .Index(2),因为index值是从0开始的)
el := d.UiObject(apis.NewUiObjectQuery("resourceId", `com.taobao.taobao:id/rv_main_container`)).Index(2).Get()
  • 等待元素出现
count := d.UiObject(apis.NewUiObjectQuery("resourceId", `com.taobao.taobao:id/rv_main_container`)).Wait(time.Minute)
// 返回匹配的元素的数量,如果在参数给定的时间内不出现匹配元素,返回 -1
  • 等待元素消失
ok := d.UiObject(apis.NewUiObjectQuery("resourceId", `com.taobao.taobao:id/rv_main_container`)).WaitGone(time.Minute)
// 如果消失,返回true,反之false

ui object element apis

UI Object元素API

  • 获取控件的所有信息 - 包括文本和边框,注释中解释了返回值类型
info := el.Info() // info的类型与xpath元素的同名API相同
  • 获取 bounding rect

获取控件的边框,注释中解释了返回值类型

bounds := el.Bounds() // bounds的类型与xpath元素的同名API相同
  • 获取 Rect

也是获取控件的边框,注释中解释了返回值类型

rect := el.Rect() // rect的类型与xpath元素的同名API相同
  • 获取元素中心点位置
x, y, ok := el.Center()
  • 点击
ok := el.Click()
  • 获取控件在UI中显示的文本
text := el.Text() // text 是文本
  • 在控件中滑动 - 如果控件是(Recycler)List
dir := apis.SWIPE_DIR_LEFT
var scale float32 = 0.8
ok := el.SwipeInsideList(dir, scale)
/* 滑动方向dir 有四种值(int):
 * SWIPE_DIR_LEFT  = 1 // 从右向左滑动
 * SWIPE_DIR_RIGHT = 2 // 从左向右滑动
 * SWIPE_DIR_UP    = 3 // 从下到上滑动
 * SWIPE_DIR_DOWN  = 4 // 从上到下滑动

 scale 是滑动距离的比例,相对于在此滑动方向下,宽度或者高度。比如,再这个例子中,我们是从右到左横向滑动,那么scale = 0.8就意味着滑动80%的宽度距离
*/
  • 输入文字
ok := el.Type("aaa")
  • 截图 - 控件截图
img := el.Screenshot() // img 是 image.Image 类型对象

如果想支持作者,左边是微信打赏码;如果想和作者交朋友或者一起做好玩的编程事情,右边是微信加好友的二维码

image image

About

安卓自动化golang SDK,支持云手机,稳定高效且不强依赖ADB

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages