生命周期
入口类的发现规则
宿主在入口文件中按以下顺序定位你的模块类:
- 若定义了
MODULE = SomeClass,直接用它(必须是JadeModule子类,否则报错)。推荐显式声明,无歧义。 - 否则在入口命名空间中自动查找唯一的
JadeModule子类; - 找到 0 个 →
ModuleLoadError: 入口文件中未找到 JadeModule 子类; - 找到多个 →
ModuleLoadError: 存在多个 JadeModule 子类,请显式指定 MODULE 属性。
自动查找只认写在入口文件里的类,从别处 import 进来的子类不会被误认。
class MyModule(JadeModule): ...
MODULE = MyModule # 写在文件末尾即可状态机
未加载 ──加载──▶ 已加载 ──启用──▶ 运行中
▲ ▲ │
│ └──────停用───────┘
└────────── 卸载 / 版本替换 ──────┘
(任意阶段出错 → 错误状态)| 状态 | 含义 | 能否被别的模块调用 |
|---|---|---|
| 未加载 | 已被宿主发现,还没导入 | ❌ |
| 已加载 | 导入完成,setup() 已执行 | ❌ |
| 运行中 | on_enable() 已执行 | ✅ |
| 错误 | 加载或启用失败,主界面会显示原因 | ❌ |
只有运行中的模块才响应调用,其余状态会收到"模块不可调用"错误。
生命周期钩子
| 钩子 | 调用时机 | 典型用途 |
|---|---|---|
setup(ctx) | 加载后一次 | 注册处理器、订阅事件、读取配置 |
on_enable() | 启用时(可反复) | 启动后台线程、打开连接、开始推送数据 |
on_disable() | 停用时(可反复) | 停止线程、释放资源、清空卡片/字段 |
on_unload() | 卸载或版本替换前 | 最终清理(多数情况下与 on_disable 相同即可) |
import threading
from jade_framework import JadeModule
class Poller(JadeModule):
def setup(self, ctx):
ctx.register_handler("snapshot", self._snapshot)
def on_enable(self):
self._interval = float(self.ctx.config.get("interval", 2.0))
self._stop = threading.Event()
self._thread = threading.Thread(target=self._loop, daemon=True,
name="my-poller")
self._thread.start()
def on_disable(self):
self._stop.set() # 必须能停下来
self._thread.join(timeout=self._interval + 5)
self.ctx.set_card(None) # 清卡片
self.ctx.push_fields({}) # 清底部字段
self.ctx.push_titlebar({}) # 清标题栏注册项
on_unload = on_disable几个容易踩的点
- 宿主在调用
setup前把ctx注入到模块实例上,self.ctx在任意钩子里都可用。super().setup(ctx)写不写都能跑,写上更稳妥。 on_disable之后模块实例仍然存活,setup不会再被调一次。可重复执行的初始化必须放在on_enable。- 停用是级联的:停用 A 时,所有声明
dependencies含 A 且处于启用状态的模块会被一并停用。这个级联不持久化,重新启用 A 后不会自动恢复它们。 - 后台线程统一
daemon=True+threading.Event控制退出,on_disable里join。用time.sleep的循环停不下来,进程退出时可能被挂住。
# 定时循环的标准写法:Event.wait 能被立刻唤醒
def _loop(self):
while not self._stop.is_set():
try:
self._poll()
except Exception as e:
self.ctx.log.warning("轮询异常: %s", e) # 记日志,不退出
self._stop.wait(self._interval)