1.介绍

GKD是基于 无障碍 + 高级选择器 + 订阅规则 的自定义屏幕点击 APP。

所以可以用来执行一些自动化操作,比如跳过该死的广告或者实现一些常用的动作。

软件可以添加第三方订阅规则,或者自己进行规则构建。

2.操作

添加自定义规则前需要首先保存快照,在需要执行操作的界面点击悬浮框或者截屏等操作保存快照。

1
2
快照是一个 zip 文件, 保存了设备在某个时间点的状态, 包含以下内容
截图、设备信息、界面信息、无障碍节点信息

抓取快照时必须开启无障碍权限, 否则无法获取 界面信息 和 无障碍节点信息。

然后打开HTTP服务,在手机或电脑的浏览器打开链接。其中非本机设备因为跨域需要下载脚本文件,根据提示安装相应浏览器插件和脚本即可。

之后就看到手机抓取的所有快照,查看快照并且找到自动化操作对应的选择器或者位置,就可以编写规则了

3.演示

1.自动查看微博原图–选择器

首先在手机端打开相关设置

image-20250311133933056

在某个可以查看原图并保存的图片界面抓取快照并打开浏览器,点击查看

image-20250311133754804

image-20250311134128516

点击原图按钮,可以看到按钮对应的位置高亮

image-20250311134153792

我们需要编写选择器来指定到这个位置,选择器有多种方式,比如根据原图这个文本进行定位

1
@[vid="iv_dialog_item"] < * + [text*="原图"]

image-20250311134648979

定位到后可以点击复制规则

image-20250311134744407

复制的规则如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
id: 'com.sina.weibo',
name: '微博',
groups: [
{
key: 1,
name: '[ChangeMe]规则名称-2025-03-11 13:47:59',
desc: '[ChangeMe]本规则由GKD网页端审查工具生成',
rules: [
{
activityIds: 'com.sina.weibo.preview.MediaPreviewActivity',
matches: '@[vid="iv_dialog_item"] < * + [text*="原图"]',
},
],
},
],
}

然后可以对规则进行相应的修改,比如名称、描述等

有时需要修改Key,因为它是用来对不同规则进行区分和排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
id: 'com.sina.weibo',
name: '微博',
groups: [
{
key: 0,
name: '查看原图',
desc: '长按图片后自动点击查看原图',
rules: [
{
activityIds: 'com.sina.weibo.preview.MediaPreviewActivity',
matches: '@[vid="iv_dialog_item"] < * + [text*="原图"]',
},
],
},
],
}

然后在手机打开本地订阅并添加这条规则组

image-20250311135229428

image-20250311135303491

这时一般就能够实现长按图片后自动点击查看原图了,但是有时可能因为点击过快或者浮窗动画导致操作失效,此时可以添加等待时间actionDelay来解决

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
id: 'com.sina.weibo',
name: '微博',
groups: [
{
key: 0,
name: '查看原图',
desc: '长按图片后自动点击查看原图',
rules: [
{
actionDelay: 500,
activityIds: 'com.sina.weibo.preview.MediaPreviewActivity',
matches: '@[vid="iv_dialog_item"] < * + [text*="原图"]',
},
],
},
],
}

2.微博去广告–定位后位置+多动作

微博有时候会有游戏广告/推荐,想找例子的时候就找不到

广告的表现为未关注、广告标志和立即下载/立即游玩等

推荐的表现为未关注和推荐标志(这种还没想到办法)

image-20250314180034293

这种可以根据关键文字立即下载来确定用户信息框所属选择器

1
@* + [desc*="立即下载"]

然后根据位置信息点击,如图左下角(top,left)(70,1032)

image-20250314180705998

然后点击不感兴趣

image-20250314181241494

1
[text*="不感兴趣"]

规则如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
{
id: 'com.sina.weibo',
name: '微博',
groups: [
{
key: 0,
name: '去广告',
desc: '有特殊关键字的广告',
rules: [
{
key:0,
activityIds: 'com.sina.weibo.MainTabActivity',
matches: '@* + [desc*="立即下载"]',
position:{left:"1032",top:"70"}
},
{
key:1,
activityIds: 'com.sina.weibo.MainTabActivity',
matches: '[text*="不感兴趣"]',
actionDelay:800,
},
],
},
],
}

关键字有很多,所以可以用逻辑表达式

1
@* + [desc*="立即下载"||desc*="进入小游戏"||desc*="点击试玩"]

4.选择器

1.语法

最简单的情况下, 目标节点已经有唯一的 id/text 标识

另一种常见的情况是节点没有有效属性(不唯一), 这时我们需要根据周围的节点去定位

与 CSS 类似, 一个选择器由 属性选择器关系选择器 交叉组成, 并且开头末尾必须是属性选择器

示例 div > img 的结构是 属性选择器 关系选择器 属性选择器, 它表示选择父节点是 divimg 节点, 这与 相同 CSS 语法 语义一致

另外 属性选择器 和 关系选择器 之间必须强制用 空白字符(空格/换行/回车/制表) 隔开

1
2
合法:div > img
非法:div>img

属性选择

1
@TextView[a=1][b^='2'][c*='a'||d.length>7&&e=false][!(f=true)][g.plus(1)>0]

@ 表示选择此节点, 一条规则最后属性选择器 @ 生效, 如果没有 @, 取最后一个属性选择器

TextView 代表节点的 name 属性, 而且与 CSS 相似, * 表示匹配任意属性

为了方便书写规则, 上述 TextView 等价 [name='TextView'||name$='.TextView']

[] 内部是一个 逻辑表达式/布尔表达式/取反表达式

1
取反表达式中的 ! 后面必须是 (...), !!(...) 是非法的

逻辑表达式 有两个操作符 ||&&. && 优先级更高

1
[a>1||b>1&&c>1||d>1] 等价于 [a>1||(b>1&&c>1)||d>1]

并列的 [] 视为使用 && 的逻辑表达式,

1
[a=1][b=1] 等价于 [a=1&&b=1]

值表达式

值表达式分两类:变量 和 字面量

值表达式 示例
变量 标识符 a
成员表达式 a.b
调用表达式 a(b,c)
字面量 null null
boolean false
int 114514
string 'ikun'

操作符

操作符 名称 说明
= equal 等于
!= notEqual 不等于
> more 大于
>= moreEqual 大于或等于
< less 小于
<= lessEqual 小于或等于
^= startsWith 以…开头
!^= notStartsWith 不以…开头
*= contains 包含…
!*= notContains 不包含…
$= endsWith 以…结尾
!$= notEndsWith 不以…结尾
~= matches 正则匹配…
!~= notMatches 正则不匹配…

关系表达式

关系表达式 表示查找节点的范围, 有两种

  • 元组表达式 (a1,a2,a3,a_n), 其中 a1, a2, a3, a_n 是常量有序递增正整数, 示例 (1), (2,3,5)
  • 多项式表达式 (an+b), 其中 a 和 b 是常量整数, 它是元组表达式的另一种表示, 这个元组的数满足集合 {an+b|an+b>=1,n>=1} 如果集合为空集则表达式非法
    当 a<=0 时, 它具有等价的元组表达式
    示例 (-n+4) 等价于 (1,2,3)
    示例 (-3n+10) 等价于 (1,4,7)
    当 a>0 时, 它表示无限的元组表达式
    示例 (n), 它表示 (1,2,3,...) 一个无限的元组
    示例 (2n-1), 它表示 (1,3,5,...) 一个无限的元组

关系选择

关系选择器关系操作符关系表达式 构成, 用于连接两个属性选择器

关系操作符表示查找节点的方向

操作符 名称 选择器
+ 前置兄弟节点 * + [_id=33]
- 后置兄弟节点 * - [_id=32]
> 祖先节点 * > [_id=90]
< 直接子节点 * < [_id=89]
<< 子孙节点(深度先序) * <<2 [_id=29]

还有-> 用于查询过程中, 表示当前节点的之前节点

比如 C ->1 B > A, 由 A 找到 B 后, 此时的 C 就是 B 的之前节点即 A, 这适合想快速去之前节点重新查询关系的场景

表达式简写

一般情况下, 并不需要写严格完整的表达式, 使用简化写法更方便快捷

当 a=0 或 b=0 时, 括号可以省略, 以 + 为例

1
2
3
A +(3n+0) B -> A +(3n) B -> A +3n B

A +(0n+3) B -> A +(+3) B -> A +3 B

当 a=0 且 b=1 时, an+b 可以省略, 以 + 为例

1
A +(0n+1) B -> A + B

此外 A + B,A > B 都与等价的 CSS 语法语义相同

当 a=1 且 b=0 且操作符是 >, 可以进一步简写, 比如

1
A >(1n+0) B -> A >n B -> A B

这与等价的 CSS 语法语义相同

进阶

选择器也可以组合为 逻辑表达式/取反表达式

只有一个格式要求: 单个选择器必须强制使用 () 包裹

  • 一般选择器 -> A + B(A + B)

  • 逻辑或表达式 -> (A + B) || (M > N), 表示匹配节点满足 A + BM > N

    匹配结果遵循 || 的短路原则, 优先判断左侧的 A + B, 如果满足 A + B 那就是 B. 不满足 A + B 时, 判断右侧表达式, 如果满足 M > N 那就是 N, 当然也可以在内部使用 @ 自定义目标节点

  • 逻辑且表达式 -> (A + B) && (M > N), 表示匹配节点满足 A + BM > N, 结果取右侧表达式结果, 结果为 N, 当然也可以在内部使用 @ 自定义目标节点

  • 取反表达式 -> !(A + B), 表示匹配节点应不满足 A + B, 需要注意的是 !(@A + B) 中的 @ 是无效的, 它的匹配结果只能是末尾节点 B

此外 逻辑且表达式 的优先级更高, 即 (A + B) || (C + B) && (M > N) 等价于 (A + B) || ((C + B) && (M > N))

匹配顺序

选择器的匹配顺序是 从右往左匹配

例如 FrameLayout > TextView, 它是先从 根节点/事件节点 找到 TextView, 然后再判断 parent 是不是 FrameLayout

2.属性方法

null

null无任何属性方法, 并且在无特殊说明时, 属性方法当调用者或者参数是null时, 均返回null

boolean

方法名 参数 返回类型 描述
toInt int 转为 0 或 1
or boolean boolean a || b
and boolean boolean a && b
not boolean !a
ifElse T, T T if(a) T else T T 是任意类型, 两个参数均支持为 null

int

方法名 参数 返回类型 描述
toString string 转为10进制字符串
toString int string 转为对应进制的字符串
plus int string 加上
minus int string 减去
times int string 乘以
div int string 除以
rem int string 取余
more int boolean a > b
moreEqual int boolean a >= b
less int boolean a < b
lessEqual int boolean a <= b

string

标识符 属性类型 描述
length int 字符串长度
方法名 参数 返回类型 描述
get int string 获取对应索引字符串
at int string 同上,但是参数传负数时从最后一个字符取
substring int string 截取指定索引到结尾字符串
substring int,int string 截取指定间隔字符串
toInt int 转为10进制数字
toInt int int 转为指定进制的数字
indexOf string int 查找指定字符串的索引
indexOf string,int int 从指定索引开始查找指定字符串的索引

node

[!IMPORTANT]

以 _ 开头的属性只能在网页快照调试工具上使用, 真机不可用

标识符 属性类型 描述
_id int
_pid int
id string
vid string
name string
text string
desc string
clickable boolean
focusable boolean
checkable boolean
checked boolean
editable boolean
longClickable boolean
visibleToUser boolean
left int
top int
right int
bottom int
width int
height int
childCount int
index int
depth int
parent node 获取父节点
方法名 参数 返回类型 描述
getChild int node 获取指定索引的子节点

context

context 具有 node 的所有属性方法

标识符 属性类型 描述
prev context 右侧属性选择器的节点上下文 最右侧的 prev=null
current node 当前节点 current.id=id
方法名 参数 返回类型 描述
getPrev int context 快捷获取深层 prev getPrev(0) -> prev getPrev(1) -> prev.prev getPrev(2) -> prev.prev.prev

global

global 具有 context 的所有属性方法, 并且没有任何属性方法能获取 global 的引用

方法名 参数 返回类型 描述
equal T, T boolean a==b T 是任意类型, a,b 允许 null
notEqual T, T boolean a!=b

示例

1
[name=''][prev.name=''][current.name=''][parent.name='']
  • 第一个 name 来自 global, 实际上任何 xxx/xxx() 都来自 global
  • 第二个 prev.name 来自 context
  • 后续的 current.name/parent.name 都来自 node

3.选择示例

选择根节点

  • [parent=null]
  • [depth=0]

选择子节点

选择一个 ImageView 节点, 并要求其父节点是 LinearLayout

LinearLayout > ImageView

选择一个 ImageView 节点, 并要求其父节点的父节点是 ViewGroup[vid="avatar_layout"]

ViewGroup[vid="avatar_layout"] >2 ImageView

选择一个 ImageView[vid="search_icon"] 节点, 并要求祖先节点是 FrameLayout[vid="content_layout"], n可以是任意正整数, 下同

FrameLayout[vid="content_layout"] >n ImageView[vid="search_icon"]

末尾子节点

选择一个 ImageView 节点, 并要求是其父节点是最后一个子节点

ImageView[index=parent.childCount.minus(1)]

选择兄弟节点

选择一个 LinearLayout[desc="搜索栏,按钮"] 节点, 并要求其兄弟节点是 ViewGroup[desc="个人主页,按钮"]

  • ViewGroup[desc="个人主页,按钮"\] + LinearLayout[desc="搜索栏,按钮"]
  • @LinearLayout[desc="搜索栏,按钮"\] - ViewGroup[desc="个人主页,按钮"]

选择一个 [text$="顶级入耳"] 节点, 并要求其兄弟节点是 [vid="cover_layout"]

  • [vid="cover_layout"\] +n [text$="顶级入耳"]
  • @[text$="顶级入耳"\] -n [vid="cover_layout"]

选择父节点

选择一个 ViewGroup[desc^="直播"] 节点, 并要求其子节点是 FrameLayout[vid="cover_layout"]ViewGroup[vid="live_text_container"]TextView[vid="title_v2"]

  • FrameLayout[vid="cover_layout"] < ViewGroup[desc^="直播"]
  • ViewGroup[vid="live_text_container"] <3 ViewGroup[desc^="直播"]
  • [TextView[vid="title_v2"]

4.查询优化

主动查询

一般的选择器如 LinearLayout > TextView, 选择器需要从 根节点/事件节点 使用深度先序顺序遍历子孙节点并判断是否满足条件

如果屏幕上有 n 个节点, 判断逻辑需要运行 n 次

但是如果选择器是类似 TextView <3 LinearLayout <2 [parent=null], 由于在右侧标明了从根节点查找

因此上面的 深度先序顺序遍历子孙节点 将省略, 只进行一次判断, 不需要判断 n 次

但是如果你在子查询里使用 <<n, 例如 TextView <<n [parent=null]

根据上面说明的优化, 虽然也只有一次判断, 但是由于 <<n 导致内部的子判断也是 n 次, 实际上没有优化判断次数

5.快速查询

此优化需要设置 fastQuery 来启用
并且节点在快照面板被标识 可快速查找, 否则查找不到

查询场景

一般情况下, 选择器 [vid="name"] 需要从 根节点/事件节点 使用深度先序顺序遍历子孙节点并判断是否满足条件

但是 Android 提供了如下两个快速获取节点的 Api,这使得可以通过 id/vid/text 直接获取子孙节点

6.API参数