events 事件与观察器
events 事件与观察器
events 是 ScriptX 里负责“事件分发、按键监听、Toast 监听、通知监听、触摸监听、剪贴板监听、手势监听、广播事件”的全局对象。
如果只把它理解成一个普通 EventEmitter,会漏掉一大半能力;如果只把它理解成无障碍观察器,又会忽略它其实也能做自定义事件总线。
这页会重点把这些容易混的地方讲明白:
events、events.broadcast、events.emitter(...)、new events.EventEmitter()到底有什么区别。- 哪些监听是“只注册回调就够了”,哪些还必须先
observeXxx()。 emitSticky(...)的真正行为是什么。- 按键、触摸、Toast、通知这些监听依赖什么系统能力。
- 事件回调里到底会收到什么参数,能直接拿来做什么。
先记住这 12 条
events本身就是一个事件发射器,支持on / once / emit / off这些常见写法。events.broadcast也是发射器,但它走的是当前应用包内的 Android 广播,不是单纯的内存事件。events.emitter(thread)可以创建“监听回调固定派发到某个线程”的自定义发射器。new events.EventEmitter()和events.EventEmitter()都能创建一个新的自定义发射器。events.on("clip_changed", ...)和events.on("gesture", ...)会自动拉起对应观察器。events.onToast(...)、events.onNotification(...)、events.onTouch(...)只负责注册回调,不会自动开始观察;你还得先observeToast()、observeNotification()、observeTouch()。events.onKeyDown(...)、events.onKeyUp(...)会自动开始监听按键,但如果你是自己监听events.on("key", ...)或events.on("volume_up", ...),那就要先手动observeKey()。events.setKeyInterceptionEnabled(...)不只接受true/false,也接受1/0、"true"、"1"这种值。events.observeTouch()依赖 root 和可用的getevent访问能力,没这两样就会直接抛错。emitSticky(...)对普通发射器会记住最后一次参数,但对events.broadcast来说并不会做跨广播的粘性缓存。events.recycle()会统一释放广播接收器和各类观察器,脚本收尾时很实用。events根对象默认最大监听数已经被提升到了100,而events.EventEmitter.defaultMaxListeners的默认值是10。
先分清 4 种事件对象
events
根发射器,也是各种系统观察器最终把事件投递进来的入口。
events.broadcast
同包内广播发射器。它发出去的事件会通过 Android 广播回到当前包里的脚本监听器。
events.emitter(thread?)
创建一个新的自定义发射器。
如果传 thread,这个发射器的监听回调会被派发到那个线程上执行。
new events.EventEmitter()
创建一个新的普通自定义发射器。
不绑定系统事件,也不自动观察按键/通知这些外部输入。
events.on(eventName, listener)
给 events 根发射器注册一个监听器。
参数
| 参数 | 类型 | 可填值 | 说明 |
|---|---|---|---|
eventName | 通常为 string | 自定义事件名或内建事件名 | 事件名 |
listener | function | 任意函数 | 监听回调,必填 |
返回值
events
说明
listener不是函数时会抛listener must be a function。- 同一个事件名可以挂多个监听器。
- 注册顺序默认就是触发顺序。
- 对
clip_changed和gesture这两个内建事件,注册监听后会自动开始观察。 - 对
toast、notification、touch、key这类事件,只注册监听还不够,还得先打开对应观察器。
示例
events.on("demo", function (a, b) {
log(`a=${a}, b=${b}`);
});
events.emit("demo", 1, 2);
events.addListener(eventName, listener)
events.on(...) 的别名。
返回值
events
示例
events.addListener("ready", function () {
log("ready");
});
events.once(eventName, listener)
注册一次性监听器,触发 1 次后自动移除。
返回值
events
示例
events.once("loaded", function () {
log("只会执行一次");
});
events.prependListener(eventName, listener)
把监听器插到当前事件监听队列最前面。
返回值
events
什么时候用
当你想让某个监听器先于已有监听器执行时,就用它。
示例
events.on("task", function () { log("B"); });
events.prependListener("task", function () { log("A"); });
events.emit("task");
events.prependOnceListener(eventName, listener)
在监听队列最前面插入一个“一次性监听器”。
返回值
events
示例
events.prependOnceListener("boot", function () {
log("我先跑,而且只跑一次");
});
events.emit(eventName, ...args)
触发一个事件。
返回值
boolean
说明
- 有监听器且成功派发时返回
true。 - 当前事件没有任何监听器时返回
false。 ...args会原样传给回调。
示例
events.on("calc", function (x, y) {
log(x + y);
});
const handled = events.emit("calc", 3, 4);
log(handled);
events.emitSticky(eventName, ...args)
触发一个“粘性事件”。
返回值
boolean
说明
- 对普通发射器来说,它会记住这次参数。
- 之后如果再有人
on(...)或once(...)这个事件,会立刻收到最后一次粘性参数。 - 如果是
once(...)加上来的监听器,立刻执行后不会留在监听列表里。 - 这很适合做“状态已就绪,后来者一注册就应该马上拿到当前状态”。
示例
events.emitSticky("state", { loggedIn: true });
events.on("state", function (state) {
log(JSON.stringify(state)); // 会立即收到
});
events.removeListener(eventName, listener)
移除一个指定监听器。
返回值
events
说明
- 必须传入同一个函数引用,才能精确移除。
- 只会移除当前事件里第一次匹配到的那个监听器包装。
示例
function onDemo() {
log("demo");
}
events.on("demo", onDemo);
events.removeListener("demo", onDemo);
events.off(eventName, listener)
events.removeListener(...) 的别名。
返回值
events
events.removeAllListeners(eventName?)
移除监听器。
参数
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
eventName | 通常为 string | 不传 | 不传时移除全部事件监听;传了只清某个事件 |
返回值
events
说明
- 它只移除监听器,不会自动停止已经开启的
observeToast()、observeNotification()、observeTouch()、observeKey()。 - 如果你还想释放观察器本身,记得再调
events.recycle()。
示例
events.removeAllListeners("demo");
events.removeAllListeners();
events.listeners(eventName)
取某个事件当前所有监听函数。
返回值
function[]
示例
const list = events.listeners("demo");
log(list.length);
events.listenerCount(eventName)
取某个事件当前监听器数量。
返回值
number
示例
log(events.listenerCount("demo"));
events.eventNames()
列出当前还有监听器的事件名。
返回值
array
示例
log(JSON.stringify(events.eventNames()));
events.setMaxListeners(n)
设置当前发射器的最大监听数。
参数
| 参数 | 类型 | 可填值 | 说明 |
|---|---|---|---|
n | number | 0 或任意正整数 | 0 表示不限制 |
返回值
events
说明
- 小于
0会报错。 - 当前发射器某个事件监听数达到上限后,再继续加会抛:
Too many listeners for event <eventName>。
示例
events.setMaxListeners(20);
events.getMaxListeners()
读取当前发射器的最大监听数设置。
返回值
number
示例
log(events.getMaxListeners());
events.observeKey()
开启无障碍按键监听。
返回值
events
依赖
- 需要无障碍服务已经启动。
说明
- 如果你准备监听
events.on("key", ...)、events.on("key_down", ...)、events.on("volume_up", ...)这类通用按键事件,必须先显式调用它。 events.onKeyDown(...)/events.onKeyUp(...)这两个快捷方法会自动帮你调它。
示例
events.observeKey();
events.on("key", function (keyCode, event) {
log(keyCode);
});
events.onKeyDown(keyName, listener)
监听某个按键按下。
参数
| 参数 | 类型 | 可填值 | 说明 |
|---|---|---|---|
keyName | string | 归一化按键名,例如 volume_up、back、menu | 必填 |
listener | function | function(event) {} | 必填 |
返回值
events
说明
- 会自动开启按键观察。
keyName会统一转成小写。- 回调参数只有 1 个,就是
KeyEvent。
示例
events.onKeyDown("volume_up", function (event) {
log(`down=${event.getKeyCode()}`);
});
events.onKeyUp(keyName, listener)
监听某个按键抬起。
返回值
events
回调参数
function(event) {}
示例
events.onKeyUp("back", function () {
log("back released");
});
events.onceKeyDown(keyName, listener)
只监听 1 次按键按下。
返回值
events
示例
events.onceKeyDown("volume_down", function () {
log("只接一次");
});
events.onceKeyUp(keyName, listener)
只监听 1 次按键抬起。
返回值
events
示例
events.onceKeyUp("back", function () {
log("只接一次 back up");
});
events.removeAllKeyDownListeners(keyName)
清掉某个键名对应的全部按下监听。
返回值
events
说明
- 只移除
onKeyDown/onceKeyDown这一类快捷事件名对应的监听。 - 不会停止底层按键观察器。
示例
events.removeAllKeyDownListeners("volume_up");
events.removeAllKeyUpListeners(keyName)
清掉某个键名对应的全部抬起监听。
返回值
events
示例
events.removeAllKeyUpListeners("back");
events.setKeyInterceptionEnabled(enabled)
设置“所有按键”是否允许在事件被监听后被拦截。
参数
| 参数 | 类型 | 可填值 | 说明 |
|---|---|---|---|
enabled | boolean | number | string | true / false / 1 / 0 / "true" / "false" / "1" / "0" | 是否开启全局拦截 |
返回值
events
说明
- 这里只是“允许拦截”的开关。
- 真正要拦截成功,还得满足两个条件:
- 对应按键事件确实命中了至少 1 个监听器。
- 你开启了“全局拦截”或这个键名本身被标记成可拦截。
- 一旦你开启全局拦截,它也会自动开始按键观察。
- 对字符串来说,当前实现里
"true"和"1"会被当成真;其他字符串都会按假处理。
示例
events.onKeyDown("volume_up", function () {
log("listen volume up");
});
events.setKeyInterceptionEnabled(true);
events.setKeyInterceptionEnabled(keyName, enabled)
只对某个键名开启或关闭拦截。
参数
| 参数 | 类型 | 可填值 | 说明 |
|---|---|---|---|
keyName | string | 例如 volume_up、back | 按键名 |
enabled | boolean | number | string | true / false / 1 / 0 / "true" / "false" / "1" / "0" | 是否开启该键拦截 |
返回值
events
示例
events.onKeyDown("volume_down", function () {
log("listen one key");
});
events.setKeyInterceptionEnabled("volume_down", true);
events.observeToast()
开启 Toast 监听。
返回值
events
依赖
- 需要无障碍服务已经启动。
说明
- 只调用
events.onToast(...)不会自动开启监听。 - 最稳的写法是
events.observeToast().onToast(...)。
示例
events.observeToast();
events.onToast(function (toastEvent) {
log(toastEvent.getText());
});
events.onToast(listener)
注册 Toast 事件监听器。
参数
| 参数 | 类型 | 说明 |
|---|---|---|
listener | function | 回调形如 function(toastEvent) {} |
返回值
events
说明
- 它本质上等价于监听根发射器上的
"toast"事件。 - 不会自动开启 Toast 观察器。
events.observeNotification()
开启通知监听。
返回值
events
依赖
- 需要无障碍服务已经启动。
示例
events.observeNotification();
events.onNotification(function (notification) {
log(notification.getTitle());
});
events.onNotification(listener)
注册通知事件监听器。
参数
| 参数 | 类型 | 说明 |
|---|---|---|
listener | function | 回调形如 function(notificationEvent) {} |
返回值
events
说明
- 它监听的是根发射器上的
"notification"事件。 - 不会自动开启通知观察器。
events.observeTouch()
开启触摸坐标监听。
返回值
events
依赖
- 需要 root。
- 需要可用的
getevent访问能力。
说明
- 启动失败时会直接抛错。
- 默认触摸节流间隔是
10ms。 - 只有“手指按下时的新触点”或“按下状态下坐标发生变化”才会发事件。
- 抬起阶段不会继续发
touch事件点。
示例
events.observeTouch();
events.onTouch(function (point) {
log(`x=${point.x}, y=${point.y}`);
});
events.onTouch(listener)
注册触摸点监听器。
参数
| 参数 | 类型 | 说明 |
|---|---|---|
listener | function | 回调形如 function(point) {} |
返回值
events
说明
- 回调拿到的是
android.graphics.Point,常用字段是point.x、point.y。 - 它本身不会自动开启触摸观察器。
events.removeAllTouchListeners()
移除所有 touch 监听器。
返回值
events
说明
- 只清监听器,不会自动停止已启动的触摸监视器。
- 想彻底停掉监听,请用
events.recycle()。
events.setTouchEventTimeout(timeout)
设置触摸事件的节流间隔。
参数
| 参数 | 类型 | 可填值 | 说明 |
|---|---|---|---|
timeout | number | 0 或任意正数 | 两次触摸点事件至少间隔多少毫秒 |
返回值
events
说明
- 小于
0的值会被修正到0。 0表示尽量不节流,只要检测到满足条件的点变化就发。
示例
events.setTouchEventTimeout(30);
events.getTouchEventTimeout()
读取当前触摸节流间隔。
返回值
number
示例
log(events.getTouchEventTimeout());
events.recycle()
统一释放事件系统占用的观察器和广播接收器。
返回值
null
会释放哪些东西
events.broadcast的广播接收器- 按键监听
- 触摸监听
- Toast 监听
- 通知监听
- 剪贴板监听
- 手势监听
什么时候用
- 脚本准备结束时,想把监听资源收干净。
- 你切换了一整套监听逻辑,不想留旧观察器继续跑。
示例
events.recycle();
events.emitter(thread?)
创建一个新的自定义发射器。
参数
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
thread | thread | 不传 | 可选。传了以后,监听回调会派发到这个线程 |
返回值
emitter
说明
- 不传参数时,效果接近
new events.EventEmitter()。 - 传参时必须传
threads.start(...)、threads.currentThread()、threads.getMainThread()这类返回的线程句柄。 - 传别的对象会抛:
events.emitter(thread) requires a threads thread object。
示例
const worker = threads.start(function () {});
worker.waitFor();
const emitter = events.emitter(worker);
emitter.on("demo", function (text) {
log(text);
});
emitter.emit("demo", "run on worker");
events.broadcast
同包广播型发射器。
它是什么
它本身也是一个 EventEmitter 风格对象,支持:
ononceemitemitStickyoffremoveAllListeners
和普通发射器的关键区别
emit(...)不是只在内存里派发,而是发一个当前包内广播。- 监听回调最终会回到主线程句柄去执行。
- 传递的参数会先做 JSON 兼容序列化。
- 当前实现里,广播接收器是在第一次
emit(...)或emitSticky(...)时才惰性注册的,不是你一on(...)就立刻注册。
参数序列化规则
| 传入类型 | 广播里会变成什么 |
|---|---|
number / boolean / string | 原值 |
array / Iterable | JSON 数组 |
普通对象 / Map | JSON 对象 |
function | 变成函数的字符串表示 |
| 循环引用对象 | 递归位置会被写成 null |
关于 emitSticky(...)
对 events.broadcast 来说,emitSticky(...) 当前和 emit(...) 一样,都是发广播;它不会在广播层帮你做“后来监听也能补收到上一次”的持久粘性缓存。
纯监听场景要注意
如果你的脚本只想“等别人发广播给我”,而自己一开始完全不主动 emit(...),那当前实现下广播接收器可能还没有注册完成。
也就是说,events.broadcast 更适合“自己会主动发、同时也会收”的场景;如果你要做纯监听,最好把“先完成一次初始化发射”这件事考虑进去。
示例
events.broadcast.on("hello", function (payload) {
log(JSON.stringify(payload));
});
events.broadcast.emit("hello", { from: "script", ok: true });
events.EventEmitter
自定义发射器构造入口。
用法
这两种写法都可以:
const emitter1 = events.EventEmitter();
const emitter2 = new events.EventEmitter();
返回值
emitter
说明
- 它创建出来的是独立发射器,不会自动接入系统按键、Toast、通知这些观察器。
- 更适合做你自己脚本内部的事件总线。
events.EventEmitter.defaultMaxListeners
自定义发射器的默认最大监听数。
类型
number
默认值
10
说明
- 设成
0表示不限制。 - 小于
0会报错。 - 它影响“没有单独调过
setMaxListeners(...)的发射器”。 - 根对象
events自己在运行时里已经被单独设成100,所以它不跟着这里的默认值走。
示例
events.EventEmitter.defaultMaxListeners = 30;
const emitter = new events.EventEmitter();
log(emitter.getMaxListeners()); // 30
toastEvent.packageName
Toast 事件来源包名。
类型
string
示例
events.observeToast().onToast(function (toastEvent) {
log(toastEvent.packageName);
});
toastEvent.texts
Toast 事件里带出来的全部文本列表。
类型
string[]
说明
有些 Toast 会带多段文本,这里拿到的是完整数组。
toastEvent.getText()
拿 Toast 文本列表里的第一条文本。
返回值
string | null
示例
events.observeToast().onToast(function (toastEvent) {
log(toastEvent.getText());
});
notificationEvent.packageName
通知来源包名。
类型
string
notificationEvent.key
通知键值。
类型
null
说明
当前实现里这个字段始终是 null,先不要把它当成可用通知 ID 用。
notificationEvent.getTitle()
读通知标题。
返回值
string | null
示例
events.observeNotification().onNotification(function (notificationEvent) {
log(notificationEvent.getTitle());
});
notificationEvent.getText()
读通知正文。
返回值
string | null
notificationEvent.click()
尝试点击通知的主 PendingIntent。
返回值
boolean
说明
- 有的通知没有可点击意图,这时会返回
false。 - 系统把
PendingIntent判成已失效时也会返回false。
示例
events.observeNotification().onNotification(function (notificationEvent) {
if (notificationEvent.getTitle() === "测试通知") {
notificationEvent.click();
}
});
notificationEvent.delete()
尝试执行通知的删除意图。
返回值
boolean
说明
- 不是所有通知都有删除动作。
- 没有删除意图时会返回
false。
常用内建事件名
exit
脚本退出时自动触发。
events.on("exit", function () {
log("script exiting");
});
clip_changed
剪贴板变化事件。
监听它时会自动开启剪贴板观察。
回调参数:
textclipData
events.on("clip_changed", function (text, clipData) {
log(text);
});
gesture
无障碍手势事件。
监听它时会自动开启手势观察。
常见值包括:
updownleftrightleft_rightright_leftup_downdown_upleft_upleft_downright_upright_downup_leftup_rightdown_leftdown_right
events.on("gesture", function (name) {
log(name);
});
key
通用按键事件。
要先 events.observeKey()。
回调参数:
keyCodeevent
events.observeKey();
events.on("key", function (keyCode, event) {
log(keyCode);
});
key_down / key_up
通用按下 / 抬起事件。
要先 events.observeKey()。
原始键名事件,例如 volume_up
每个按键还会额外发一类“键名事件”,键名来自 Android 的 KEYCODE_XXX 去掉前缀后转小写。
例如音量加通常就是 volume_up。
events.observeKey();
events.on("volume_up", function (event) {
log("pressed");
});
toast
Toast 事件。
要先 events.observeToast()。
notification
通知事件。
要先 events.observeNotification()。
touch
触摸点事件。
要先 events.observeTouch()。
回调参数是 Point(x, y)。
监听器顺序与元事件
newListener
当有新监听器加入时触发。
回调参数:
eventNamelistener
removeListener
当监听器被移除时触发。
回调参数:
eventNamelistener
示例
events.on("newListener", function (eventName) {
log(`add ${eventName}`);
});
常见报错与原因
监听器不是函数
listener must be a function
这说明你把 listener 漏掉了,或者传进去的不是函数。
监听数量超限
Too many listeners for event demo
这说明当前事件监听器数量已经达到 getMaxListeners() 的上限。
键名为空
keyName must not be empty
这说明你传给 onKeyDown(...)、onKeyUp(...) 或 setKeyInterceptionEnabled(keyName, ...) 的键名是空串。
无障碍没开
无障碍服务未启动,无法监听按键事件
无障碍服务未启动,无法监听 Toast 事件
无障碍服务未启动,无法监听通知事件
这说明依赖无障碍的观察器还没有可用。
触摸观察失败
触摸事件监听需要 root 权限和可用的 getevent 访问能力
这说明当前设备环境不满足触摸监听要求。
events.emitter(thread) 参数不对
events.emitter(thread) requires a threads thread object
这说明你传的不是 threads 返回的线程句柄对象。
