GKD的使用
1.介绍
GKD是基于 无障碍 + 高级选择器 + 订阅规则 的自定义屏幕点击 APP。
所以可以用来执行一些自动化操作,比如跳过该死的广告或者实现一些常用的动作。
软件可以添加第三方订阅规则,或者自己进行规则构建。
2.操作
添加自定义规则前需要首先保存快照,在需要执行操作的界面点击悬浮框或者截屏等操作保存快照。
1 | 快照是一个 zip 文件, 保存了设备在某个时间点的状态, 包含以下内容 |
抓取快照时必须开启无障碍权限, 否则无法获取 界面信息 和 无障碍节点信息。
然后打开HTTP服务,在手机或电脑的浏览器打开链接。其中非本机设备因为跨域需要下载脚本文件,根据提示安装相应浏览器插件和脚本即可。
之后就看到手机抓取的所有快照,查看快照并且找到自动化操作对应的选择器或者位置,就可以编写规则了
3.演示
1.自动查看微博原图–选择器
首先在手机端打开相关设置

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


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

我们需要编写选择器来指定到这个位置,选择器有多种方式,比如根据原图这个文本进行定位
1 | @[vid="iv_dialog_item"] < * + [text*="原图"] |

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

复制的规则如下
1 | { |
然后可以对规则进行相应的修改,比如名称、描述等
有时需要修改Key,因为它是用来对不同规则进行区分和排序
1 | { |
然后在手机打开本地订阅并添加这条规则组


这时一般就能够实现长按图片后自动点击查看原图了,但是有时可能因为点击过快或者浮窗动画导致操作失效,此时可以添加等待时间actionDelay来解决
1 | { |
2.微博去广告–定位后位置+多动作
微博有时候会有游戏广告/推荐,想找例子的时候就找不到
广告的表现为未关注、广告标志和立即下载/立即游玩等
推荐的表现为未关注和推荐标志(这种还没想到办法)

这种可以根据关键文字立即下载来确定用户信息框所属选择器
1 | @* + [desc*="立即下载"] |
然后根据位置信息点击,如图左下角(top,left)(70,1032)

然后点击不感兴趣

1 | [text*="不感兴趣"] |
规则如下
1 | { |
关键字有很多,所以可以用逻辑表达式
1 | @* + [desc*="立即下载"||desc*="进入小游戏"||desc*="点击试玩"] |
4.选择器
1.语法
最简单的情况下, 目标节点已经有唯一的 id/text 标识
另一种常见的情况是节点没有有效属性(不唯一), 这时我们需要根据周围的节点去定位
与 CSS 类似, 一个选择器由 属性选择器 和 关系选择器 交叉组成, 并且开头末尾必须是属性选择器
示例 div > img 的结构是 属性选择器 关系选择器 属性选择器, 它表示选择父节点是 div 的 img 节点, 这与 相同 CSS 语法 语义一致
另外 属性选择器 和 关系选择器 之间必须强制用 空白字符(空格/换行/回车/制表) 隔开
1 | 合法: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 | A +(3n+0) B -> A +(3n) B -> A +3n 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 + B或M > N匹配结果遵循
||的短路原则, 优先判断左侧的A + B, 如果满足A + B那就是B. 不满足A + B时, 判断右侧表达式, 如果满足M > N那就是N, 当然也可以在内部使用@自定义目标节点逻辑且表达式 ->
(A + B) && (M > N), 表示匹配节点满足A + B和M > 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 直接获取子孙节点