2. URCap架构与生命周期

URCap 在 PolyScope 中并不是一个普通脚本或配置文件,而是一个遵循 OSGi 规范的 Java 模块。理解它的架构,核心是理解三件事:Bundle 如何组织、Activator 如何接管启动与停止、服务如何被注册和消费。只有把这三件事串起来,才能解释为什么一个 URCap 安装后能出现在示教器上,卸载后又能干净地消失。

一、OSGi Bundle 结构:URCap 的物理形态

一个 URCap 的安装包通常是 .urcap 文件,它本质上是一个 ZIP 归档,内部包含 META-INF/MANIFEST.MF、Java 类文件、资源文件以及可选的 pom.xml 或构建描述。PolyScope 在安装时会把 Bundle 放入 OSGi 框架的 Bundle 仓库,并解析其依赖。

MANIFEST.MF 是 Bundle 的“身份证”,其中几个关键头信息决定了 URCap 能否被正确加载:

  • Bundle-SymbolicName:Bundle 的唯一标识,例如 com.example.gripper,同一框架内不可重复。
  • Bundle-Version:版本号,如 1.0.0,用于依赖解析和升级判断。
  • Bundle-Activator:指定激活器类的全限定名,例如 com.example.gripper.Activator。
  • Import-Package:声明需要从其他 Bundle 导入的包,如 com.ur.urcap.api。
  • Export-Package:声明本 Bundle 对外暴露的包,供其他 Bundle 使用。

如果 Import-Package 中声明的包在框架内找不到对应导出,Bundle 会停留在 INSTALLED 状态而无法进入 RESOLVED,表现为 URCap 安装后不显示。这是实际开发中最常见的“装上了但没反应”的原因之一。

二、Activator:生命周期的入口

Activator 是 URCap 与 OSGi 框架之间的契约。它通常继承 BundleActivator 接口,实现两个方法:

public class Activator implements BundleActivator {
    @Override
    public void start(BundleContext context) throws Exception {
        // 注册服务、创建 UI 贡献、初始化资源
    }

    @Override
    public void stop(BundleContext context) throws Exception {
        // 注销服务、释放资源、清理监听器
    }
}

start() 在 Bundle 被启动时调用,此时应完成服务注册和 UI 扩展点的挂载;stop() 在 Bundle 停止或卸载前调用,必须把 start() 中申请的资源全部释放,否则会造成服务残留或内存泄漏。PolyScope 在关闭或卸载 URCap 时会触发 stop(),如果这里没有注销服务,下次安装同 SymbolicName 的 Bundle 可能因服务冲突而失败。

三、服务注册与消费:Bundle 之间的协作

URCap 的能力通常以 OSGi 服务的形式暴露。例如,一个夹爪 URCap 可能注册 GripperService,供其他 URCap 或 PolyScope 内部模块调用。注册方式如下:

context.registerService(
    GripperService.class.getName(),
    new GripperServiceImpl(),
    null
);

消费方通过 ServiceTracker 或 context.getServiceReference() 获取服务。服务注册与注销必须成对出现,且应在 start()/stop() 中完成,而不是在构造函数或静态块中,否则生命周期不可控。

四、安装、启动、停止、卸载的完整流程

从 PolyScope 的角度看,一个 URCap 的生命周期经历以下阶段:

  1. 安装:用户通过示教器或 U 盘导入 .urcap 文件,PolyScope 解压并安装 Bundle 到 OSGi 框架,状态变为 INSTALLED。
  2. 解析:框架检查 Import-Package 与 Export-Package,依赖满足后进入 RESOLVED。
  3. 启动:框架调用 Activator 的 start(),Bundle 进入 ACTIVE,UI 贡献和服务开始生效。
  4. 停止:用户禁用或系统关闭时,框架调用 stop(),Bundle 回到 RESOLVED。
  5. 卸载:Bundle 被从框架中移除,相关服务和 UI 贡献全部消失。
安装后不显示,优先检查 Bundle 是否处于 ACTIVE;如果停在 INSTALLED,多半是依赖包未解析;如果 start() 抛异常,框架会记录日志,可在 PolyScope 的日志中定位。

五、生命周期状态与关键动作对照

下面的 SVG 把 Bundle 状态、Activator 调用和用户可见结果放在同一张图中,便于对照理解。

URCap Bundle 生命周期与 Activator 调用关系 INSTALLED 已安装,未解析 RESOLVED 依赖满足,可启动 ACTIVE start() 已执行 UNINSTALLED 已移除 解析 启动 卸载 stop() 执行后回到 RESOLVED Activator 关键动作 start(BundleContext) registerService / 挂载 UI 贡献 / 初始化资源 stop(BundleContext) 注销服务 / 移除监听器 / 释放资源 用户可见结果 示教器出现 URCap 图标 服务可被其他 Bundle 调用 图标消失,服务不可用 资源已释放 Bundle 从框架移除 重新安装可再次生效

六、实操中的关键判断

在开发阶段,可以通过 PolyScope 的日志或 OSGi 控制台查看 Bundle 状态。若安装后 URCap 未出现,按以下顺序排查:

  • 检查 Bundle-SymbolicName 是否与已有 Bundle 冲突。
  • 检查 Import-Package 中的包是否都能在框架内找到导出方。
  • 检查 Bundle-Activator 类名是否拼写正确,且类在 Bundle 的 classpath 中。
  • 检查 start() 是否抛出异常,异常会导致 Bundle 无法进入 ACTIVE。
  • 检查 stop() 是否遗漏注销服务,残留服务会阻止同 SymbolicName 的 Bundle 再次启动。
卸载 URCap 时,PolyScope 会先调用 stop() 再移除 Bundle。如果 stop() 中抛出异常,卸载可能不完整,导致下次安装时出现“服务已存在”或“Bundle 无法解析”的错误。因此 stop() 中的清理逻辑必须健壮,建议用 try-catch 包裹每个注销动作。

理解 URCap 的生命周期,本质上就是理解 OSGi 框架如何管理一个 Bundle 从安装到卸载的全过程。Activator 是开发者唯一能插入自定义逻辑的入口,服务注册与注销是 Bundle 之间协作的桥梁。把这两点做对,URCap 的安装、启动、停止和卸载就能稳定可控,后续的 UI 扩展和功能实现才有可靠的基础。