整理组件

系统中的所有组件都会组合成一个根组件实例树。树中的父级组件负责将其他组件的实例声明为其子级,并为其提供功能。同时,子组件可以将功能公开回给父级。这些组件实例和 capability 关系构成了组件拓扑

任何父级组件及其所有子级组件都会在树中形成一个名为“realm”的组。借助 Realm,父级可以控制哪些功能会流入和流出其组件子树,从而创建功能边界。这种封装允许在内部重新整理 realm,而不会影响依赖于其公开功能的外部组件。

该图显示了组件实例如何组织成树状结构,以及父级组件如何通过“capability 路由”确定每个子级可用的 capability。

在上图中,fuchsia.example.Foo 的协议功能会通过组件实例树从提供方路由到客户端。组件使用 use 关键字声明其所需的功能:

{
    // Information about the program to run.
    program: {
        // Use the built-in ELF runner to run core binaries.
        runner: "elf",
        // The binary to run for this component.
        binary: "bin/client",
    },

    // Capabilities required by this component.
    use: [
        { protocol: "fuchsia.example.Foo" },
    ],
}

组件使用组件清单的 capabilities 部分声明其实现或提供的功能。这样,组件框架便会知道该 capability 及其提供方。请参阅以下 provider.cml 示例:

{
    // Information about the program to run.
    program: {
        // Use the built-in ELF runner to run core binaries.
        runner: "elf",
        // The binary to run for this component.
        binary: "bin/provider",
    },

    // Capabilities provided by this component.
    capabilities: [
        { protocol: "fuchsia.example.Foo" },
    ],
    // Capabilities routed through this component.
    expose: [
        {
            protocol: "fuchsia.example.Foo",
            from: "self",
        },
    ],
}

expose 关键字可让此组件通过其父级将 capability 提供给其他 realm,其中可能还包括此组件的子级提供的 capability。在本例中,capability 的来源为 self,因为此组件是提供方。

父级组件控制领域内的capability 路由,从客户端组件到提供程序创建显式路径。请参阅以下 parent.cml 清单示例:

{
    children: [
        {
            name: "provider",
            url: "fuchsia-pkg://fuchsia.com/foo-package#meta/provider.cm",
        },
        {
            name: "client",
            url: "fuchsia-pkg://fuchsia.com/foo-package#meta/client.cm",
        },
    ],
    offer: [
        {
            protocol: "fuchsia.example.Foo",
            from: "#provider",
            to: [ "#client" ],
        },
    ],
}

父级组件会在 realm 中声明一组子组件,并使用 offer 关键字将功能路由到这些子组件。这样,父级既可以确定每个子项功能的范围,也可以确定其来源。这还支持拓扑中的多个组件提供相同的功能,因为组件框架依赖于显式路由来确定如何解析来自每个客户端的请求。

capability 类型

Fuchsia 组件支持多种不同类型的功能。到目前为止,本模块中的示例展示了两种不同的 capability 类型:runnerprotocol。您可能已经注意到,protocol capability 需要路由路径,但 runner capability 不需要。

某些功能由其父级显式路由到组件,而其他功能则使用环境提供给同一领域内的所有组件。借助环境,该框架可以预配那些不适合按组件明确路由的功能。默认情况下,组件会继承其父级的环境。组件还可以为其子项声明新的环境。

下表列出了可供组件使用的 capability 类型,以及这些 capability 是否必须从父级组件显式路由或由环境提供:

类型 说明 提供方
directory 其他组件提供的共享文件系统目录。 路由
event 由组件管理器生成的事件,例如组件启动或功能请求。 路由
protocol 其他组件或框架提供的 FIDL 协议。 路由
resolver 能够将网址解析为组件清单的组件。 环境
runner 用于执行特定组件的运行时。 环境
service 执行常规任务的相关 FIDL 协议的命名组。 路由
storage 每个组件专有的隔离文件系统目录。 路由

识别组件

组件通过网址进行标识。该框架会借助组件解析器将组件网址解析为组件声明。解析器本身就是组件,能够处理特定网址方案并提取组件清单、程序和资源。

大多数组件都发布在 Fuchsia 软件包中,因此组件网址是对该软件包中组件清单的引用。请参阅以下示例:

fuchsia-pkg://fuchsia.com/foo-package#meta/foo-component.cm

组件实例由拓扑路径引用(称为标识名)标识。组件的标识符以绝对路径或相对路径的形式指示其在组件实例树中的位置。例如,标识符路径 /core/system-updater 是指 core 领域中存在的 system-updater 实例。

组件生命周期

当组件实例在组件拓扑中添加和移除时,系统会创建和销毁这些实例。这可能通过以下两种方式之一发生:

  • 静态:在组件清单中将实例声明为树中其他组件的子项。只有在更新更改组件拓扑时,才会创建和销毁静态组件。
  • 动态:实例是在运行时使用 fuchsia.component.Realm 协议在组件 collection 中添加或移除的。动态组件会在系统关闭时被销毁。

组件被销毁后,框架会移除其持久性状态(例如本地存储)。

当其他组件尝试向其打开通道时,框架会启动组件实例。连接到组件公开的 capability 时,会隐式发生这种情况。连接到已启动的组件会重复使用正在运行的实例。

组件可以通过退出程序(由组件的 runner 定义)自行停止,或者框架可以在系统关闭时停止组件。

显示组件如何具有两个不同的状态(实例和执行)的示意图。这些状态共同描述了“组件生命周期”。

练习:集成组件

如需调用某个组件,该组件必须位于有效的组件拓扑中。在本练习中,您将将组件添加到 ffx-laboratory,这是一个受限集合,用于在产品的核心领域内进行开发。借助集合,您可以在运行时动态创建和销毁组件。

启动模拟器

如果您还没有运行的实例,请启动模拟器:

  1. 启动新的模拟器实例:

    ffx emu start --headless

    启动完成后,模拟器会输出以下消息并返回:

    Logging to "$HOME/.local/share/Fuchsia/ffx/emu/instances/fuchsia-emulator/emulator.log"
    Waiting for Fuchsia to start (up to 60 seconds)........
    Emulator is ready.
    
  2. 启动软件包服务器,以便模拟器加载软件包:

    fx serve

添加到组件拓扑

使用以下命令创建 echo-args 组件的新实例:

ffx component create /core/ffx-laboratory:echo-args \
    fuchsia-pkg://fuchsia.com/echo-args#meta/echo-args.cm

此命令接受两个参数:

  • /core/ffx-laboratory:echo-args:这是组件标识名,表示组件实例在组件拓扑中的路径。
  • fuchsia-pkg://fuchsia.com/echo-args#meta/echo_args.cm:这是组件网址,用于指明 Fuchsia 应如何从软件包服务器解析组件。

拓扑中现在存在一个名为 echo-args 的新组件实例。使用以下命令显示新实例的详细信息:

ffx component show echo-args

您应该会看到以下输出内容:

               Moniker: /core/ffx-laboratory:echo-args
                   URL: fuchsia-pkg://fuchsia.com/echo-args#meta/echo-args.cm
                  Type: CML dynamic component
       Component State: Unresolved
       Execution State: Stopped

请注意,实例已创建,但组件网址尚未解析。当框架尝试启动实例时,就会发生解析。

启动组件实例

使用以下命令启动新的 echo-args 组件实例:

ffx component start /core/ffx-laboratory:echo-args

此命令接受一个参数:

  • /core/ffx-laboratory:echo-args:这是组件标识名,表示组件实例在组件拓扑中的路径。

这会导致组件实例启动,向日志输出问候语,然后退出。打开一个新的终端窗口,然后过滤设备日志以查找示例中的相关消息:

ffx log --filter echo

您应该会在设备日志中看到以下输出:

[ffx-laboratory:echo-args][I] Hello, Alice, Bob, Spot!

探索实例

再次使用以下命令显示 echo-args 实例的详细信息:

ffx component show echo-args

您现在应该会看到以下输出:

               Moniker: core/ffx-laboratory:echo-args
                   URL: fuchsia-pkg://fuchsia.com/echo-args#meta/echo-args.cm
                  Type: CML dynamic component
       Component State: Resolved
 Incoming Capabilities: fuchsia.logger.LogSink
  Exposed Capabilities: diagnostics
       Execution State: Stopped

组件状态已更改为 Resolved,您可以查看有关组件功能的更多详细信息。

组件没有访问系统其他部分的环境感知功能。组件所需的每项 capability 都必须通过组件拓扑明确路由到该组件,或者由其环境提供。

echo-args 组件需要 fuchsia.logger.LogSink capability 才能写入系统日志。您之所以能够成功查看日志输出,是因为 core 领域ffx-laboratory 集合中的组件提供了此功能:

{
    collections: [
        {
            name: "ffx-laboratory",
        },
    ],
    offer: [
        {
            protocol: [ "fuchsia.logger.LogSink" ],
            from: "parent",
            to: "#ffx-laboratory",
        },
    ],
}

销毁实例

使用以下命令清理 echo-args 实例:

ffx component destroy /core/ffx-laboratory:echo-args