整理组件

系统中的所有组件都组合成一个已取得 root 权限的组件实例树。树中的父级组件负责将其他组件的实例声明为它们的子级,并为它们提供功能。同时,子组件可以向父组件公开功能。这些组件实例和功能关系构成了组件拓扑。

任何父组件及其所有子组件在树中形成一个组,称为“大区”。领域使父项能够控制哪些功能流入和流出其组件子树,从而创建功能边界。通过这种封装,可以在内部重新整理相应领域,而不会影响外部组件,因为它依赖于其公开的功能。

示意图,显示了如何将组件实例整理为树,以及父组件通过“功能路由”确定每个子级可用的功能。

在上图中,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 部分来声明它们实现或提供的功能。这会使组件框架知道该功能及其提供程序。请参阅以下 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 关键字通过其父组件将此组件的功能提供给其他领域,其中可能还包括此组件的子项提供的功能。在本例中,功能的来源为 self,因为此组件是提供程序。

父组件控制领域内的功能路由,创建从客户端组件到提供程序的明确路径。请参阅以下示例 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" ],
        },
    ],
}

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

功能类型

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

有些功能由其父级明确路由至组件,而其他功能则通过环境提供给同一领域内的所有组件。环境使框架能够预配没有意义按组件明确路由的功能。默认情况下,组件会继承其父级的环境。组件还可以为其子项声明新环境。

下表列出了组件可用的功能类型,以及这些功能类型是必须从父组件明确路由,还是由环境提供:

类型 说明 提供方
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 中添加或移除实例。动态组件在系统关闭时会被销毁。

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

当另一个组件尝试打开通向该组件实例的通道时,框架会启动该组件实例。在连接到该组件公开的功能时,会隐式发生。如果连接到已启动的组件,则会重复使用正在运行的实例。

组件可以通过退出程序自行停止运行(由组件的 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,您可以查看有关组件功能的更多详细信息。

组件没有 Ambient 权能访问系统的其他部分。组件所需的每项功能都必须通过组件拓扑或由其环境提供明确路由至它。

echo-args 组件需要具有 fuchsia.logger.LogSink 功能来写入系统日志。您已成功查看日志输出,因为此功能已提供给来自 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