借助字典,您可以将多项功能分为 单个单元并作为单个功能一起路由。
字典的格式为键值对存储区,其中键是 capability name 字符串,并且值为 capability。通过 值本身也可能是另一个字典功能,可用于实现 类似目录的嵌套。
定义字典功能
如需定义新字典,您可以为其添加 capability
声明,如下所示:
capabilities: [
{
dictionary: "bundle",
},
],
请阅读聚合部分,了解如何添加 capability 字典。
从一开始,
字典功能和组件中的大多数其他功能类型
协议和
目录。协议功能最终会托管
由与组件相关联的程序发出;protocol
功能
声明只是让组件框架知晓
协议的存在。另一方面,dictionary
capability 始终
由组件框架运行时托管事实上,组件不需要
使用 program
来声明 dictionary
capability。
路由字典功能
由于字典是一种集合类型,因此它们支持一组更丰富的路由 操作次数。
向父实体公开字典或 向子级提供与其他功能类似。但是 字典还支持以下其他路由操作:
- Aggregation:将 capability 安装到定义的字典中 。
- 嵌套:将字典汇总到字典中,并使用 用于引用内部字典中功能的路径语法。
- 检索:从字典和路由中检索功能 也可以单独使用
- Extension:定义包含 功能
字典一经创建便无法修改。转送字典授权 只能在读取时访问它可变性解释了 字典的可变性。
有关功能路由的更多常规信息,请参阅 顶级页面。
曝光
公开字典 capability 会向组件的父项授予对它的访问权限:
{
expose: [
{
dictionary: "bundle",
from: "self",
},
],
}
与其他功能一样,您可以使用
as
个关键字:
{
expose: [
{
dictionary: "local-bundle",
from: "self",
as: "bundle",
},
],
}
成为协作者
提供字典功能可向子组件授予对它的访问权限:
{
offer: [
{
dictionary: "bundle",
from: "parent",
to: [ "#child-a", "#child-b" ],
},
],
}
与其他功能一样,您可以使用 as
更改孩子看到的姓名
关键字:
{
offer: [
{
dictionary: "local-bundle",
from: "self",
to: [ "#child-a", "#child-b" ],
as: "bundle",
},
],
}
使用
目前,该框架不支持对 dictionary
capability 执行 use
,因为
这样。但是,您可以从检索
字典并这样使用。
汇总
如需向您定义的字典添加 capability,您可以使用 offer
关键字,并在 to
中指定目标字典。此操作称为
聚合。定义字典的组件必须与
包含 offer
。
要指明您希望向目标字典添加某项功能,请使用
to
中的以下语法:
to: "self/<dictionary-name>",
其中存在针对 dictionary:
"<dictionary-name>"
的 capabilities
声明。self/
前缀反映了一个事实,即字典
是此组件的本地属性。(这属于字典路径语法
请参阅检索中的说明)。
与其他类型的 offer
一样,
可以使用 as
关键字更改字典名称。
以下是工作中的汇总示例:
capabilities: [
{
dictionary: "bundle",
},
{
directory: "fonts",
rights: [ "r*" ],
path: "/fonts",
},
],
offer: [
{
protocol: "fuchsia.examples.Echo",
from: "#echo-server",
to: "self/bundle",
},
{
directory: "fonts",
from: "self",
to: "self/bundle",
as: "custom-fonts",
},
],
嵌套
聚合的一个特殊情况是嵌套,其中功能 添加到字典中本身就是一本字典。例如:
capabilities: [
{
dictionary: "bundle",
},
],
offer: [
{
dictionary: "gfx",
from: "parent",
to: "self/bundle",
},
],
通过这种方式,可以在字典中嵌套比 。请继续阅读下一部分,了解这一点。
检索
从字典中获取一项功能以独立进行路由的行为 称为检索。
检索操作有两个输入:用于检索 功能,以及该字典中某项功能的键。CML 表示 如下:
- 字典的路径在
from
属性中提供。- 语法为:
"<source>/<path>/<to>/<dictionary>"
。 <source>
可以是路由操作 支持。例如,offer
支持self
、#<child>
或parent
, 而expose
支持self
或#<child>
。- 其余路径段用于标识(可能是嵌套的)字典
由
<source>
路由到此组件。 - 理解这一点的一个好方法是对普通的
非嵌套的
from
语法(如示例所述) 此处)。从概念上讲,您可以将<source>
作为特殊的“顶级”由框架提供的字典。对于 例如,parent
是包含全部 功能由父级提供,#<child>
是字典 包含<child>
等公开的所有功能。如果您将 路径,它只表示一个字典, 嵌套在顶层对象中更深层。
- 语法为:
- 命名 capability 的键是作为 capability 的值提供的
关键字(
protocol
、directory
等)。这是 与为未在 字典。
此语法最容易通过示例说明:
expose: [
{
protocol: "fuchsia.examples.Echo",
from: "#echo-realm/bundle",
to: "parent",
},
],
在本例中,此组件要求名为 bundle
的字典
由 #echo-realm
公开。from
包含此字典的路径:
#echo-realm/bundle
。在 bundle
字典中,
组件要检索,并且路由的是一个 protocol
,其中包含
fuchsia.examples.Echo
。最后,该协议将展示给
组件的父级为 fuchsia.examples.Echo
(作为一项单独的 capability,而不是
作为任何包含字典的一部分)。
类似语法与 use
兼容:
use: [
{
protocol: "fuchsia.examples.Echo",
from: "parent/bundle",
},
],
在本例中,组件需要父项提供字典
包含协议 fuchsia.examples.Echo
的 bundle
。没有path
所以该程序的传入命名空间中的协议路径将
为默认值:/svc/fuchsia.examples.Echo
。
请注意,通常情况下,使用声明中 from
的默认值为
"parent"
,但由于我们是从所提供的字典中检索协议的
因此必须明确指定该父项为源。
offer: [
{
dictionary: "gfx",
from: "parent/bundle",
to: "#echo-child",
},
],
在此示例中,组件假定父组件向其提供字典
bundle
,而 bundle
包含一个名为 gfx
的字典,
单独提供给#echo-child
。
from
支持任意级别的嵌套。这是
示例:
offer: [
{
protocol: "fuchsia.ui.Compositor",
from: "parent/bundle/gfx",
to: "#echo-child",
},
],
与上个示例一样,该组件假定父组件提供一个字典,
添加到名为 bundle
的应用中,而该 bundle
包含一个名为 gfx
的字典。
最后,gfx
包含一项名为 fuchsia.ui.Compositor
的协议功能,
该方案由 #echo-child
单独提供。
最后,我们甚至可以将检索与 聚合:将功能从一个字典路由到 其他:
capabilities: [
{
dictionary: "my-bundle",
},
],
offer: [
{
protocol: "fuchsia.examples.Echo",
from: "parent/bundle",
to: "self/my-bundle",
},
],
扩展程序
在某些情况下,您可能需要构建包含功能的字典。 由多个组件添加的您无法通过汇总 因为一个组件只有一个 允许向其定义的字典添加功能。
不过,您可以通过扩展程序实现类似的功能。扩展程序 操作可让您声明一个初始内容为 从其他字典(称为“源字典”)复制过来的。这样, 您可以创建一个新字典,它基于上一个字典 而无需通过它逐个路由所有功能。
通常,在扩展字典时,您会希望添加额外的 扩展字典的功能。用于额外 功能不得与源字典中的任何键冲突。如果他们 之后,当有人尝试检索 扩展字典中的功能。
您可以将一个字典声明为另一个字典的扩展,方法是添加 extends
添加到字典中用于标识来源的 capability
声明的关键字
字典。extends
的语法与讨论的 from
的语法相同
检索。
例如:
capabilities: [
{
dictionary: "my-bundle",
extends: "parent/bundle",
},
],
offer: [
{
protocol: "fuchsia.examples.Echo",
from: "#echo-server",
to: "self/my-bundle",
},
{
dictionary: "my-bundle",
from: "self",
to: "#echo-client",
as: "bundle",
},
],
与 from
一样,extends
中的路径可以引用嵌套的字典:
capabilities: [
{
dictionary: "my-gfx",
extends: "parent/bundle/gfx",
},
],
动态字典
作为扩展程序的一个特例,源字典可以是
由组件程序在运行时动态提供的字典。
这提供了一种在运行时预配的字典和
声明式 dictionary
功能。语法如下:
extends: "program/<outgoing-dir-path>",
<outgoing-dir-path>
是组件的
传出目录传递给负责提供
fuchsia.component.sandbox/Router
协议,
预计会返回 Dictionary
capability。
我们来通过一个示例来说明此功能。该示例包含 由两个部分组成
dynamic-dictionary-provider
:创建运行时 使用三个Echo
协议的Dictionary
实例。它通过Router
公开Dictionary
。 并在其 CML 中定义扩展Router
的dictionary
。dynamic-dictionary
:声明 CML 以检索三个 来自dictionary
的Echo
协议,并运行代码以使用以上各项 协议
提供商
dynamic-dictionary-provider
的组件清单如下所示。在本专精课程
请参阅 bundle
的 dictionary
定义,该定义会扩展 Router
。
capabilities: [
{
dictionary: "bundle",
extends: "program/svc/fuchsia.component.sandbox.Router",
},
],
use: [
{
protocol: [
"fuchsia.component.sandbox.CapabilityStore",
"fuchsia.component.sandbox.Factory",
],
from: "framework",
},
],
expose: [
{
dictionary: "bundle",
from: "self",
},
],
}
在初始化时,dynamic-dictionary-provider
会使用
CapabilityStore
沙盒 API,用于创建新
Dictionary
,然后向其中添加三个 Connector
。每个
Connector
表示 Echo
协议实例之一。
let store = client::connect_to_protocol::<fsandbox::CapabilityStoreMarker>().unwrap();
let id_gen = sandbox::CapabilityIdGenerator::new();
// Create a dictionary
let dict_id = id_gen.next();
store.dictionary_create(dict_id).await.unwrap().unwrap();
// Add 3 Echo servers to the dictionary
let mut receiver_tasks = fasync::TaskGroup::new();
for i in 1..=3 {
let (receiver, receiver_stream) =
endpoints::create_request_stream::<fsandbox::ReceiverMarker>().unwrap();
let connector_id = id_gen.next();
store.connector_create(connector_id, receiver).await.unwrap().unwrap();
store
.dictionary_insert(
dict_id,
&fsandbox::DictionaryItem {
key: format!("fidl.examples.routing.echo.Echo-{i}"),
value: connector_id,
},
)
.await
.unwrap()
.unwrap();
receiver_tasks.spawn(async move { handle_echo_receiver(i, receiver_stream).await });
}
每个 Connector
都绑定到一个 Receiver
,用于处理
传入的 Echo
请求。Receiver
处理程序的实现如下:
与 ServiceFs
处理程序非常相似,不同之处在于
ServiceFs
,Receiver
会绑定到 Connector
,而不是
组件的传出目录
async fn handle_echo_receiver(index: u64, mut receiver_stream: fsandbox::ReceiverRequestStream) {
let mut task_group = fasync::TaskGroup::new();
while let Some(request) = receiver_stream.try_next().await.unwrap() {
match request {
fsandbox::ReceiverRequest::Receive { channel, control_handle: _ } => {
task_group.spawn(async move {
let server_end = endpoints::ServerEnd::<EchoMarker>::new(channel.into());
run_echo_server(index, server_end.into_stream().unwrap()).await;
});
}
fsandbox::ReceiverRequest::_UnknownMethod { ordinal, .. } => {
warn!(%ordinal, "Unknown Receiver request");
}
}
}
}
async fn run_echo_server(index: u64, mut stream: EchoRequestStream) {
while let Ok(Some(event)) = stream.try_next().await {
let EchoRequest::EchoString { value, responder } = event;
let res = match value {
Some(s) => responder.send(Some(&format!("{s} {index}"))),
None => responder.send(None),
};
if let Err(err) = res {
warn!(%err, "Failed to send echo response");
}
}
}
最后,我们需要使用
Router
。
let mut fs = ServiceFs::new_local();
fs.dir("svc").add_fidl_service(IncomingRequest::Router);
fs.take_and_serve_directory_handle().unwrap();
fs.for_each_concurrent(None, move |request: IncomingRequest| {
let store = store.clone();
let id_gen = id_gen.clone();
async move {
match request {
IncomingRequest::Router(mut stream) => {
while let Ok(Some(request)) = stream.try_next().await {
match request {
fsandbox::RouterRequest::Route { payload: _, responder } => {
let dup_dict_id = id_gen.next();
store.duplicate(dict_id, dup_dict_id).await.unwrap().unwrap();
let capability = store.export(dup_dict_id).await.unwrap().unwrap();
let _ = responder.send(Ok(capability));
}
fsandbox::RouterRequest::_UnknownMethod { ordinal, .. } => {
warn!(%ordinal, "Unknown Router request");
}
}
}
}
}
}
})
.await;
Router
请求处理程序会导出字典和
返回。请注意,这会使
字典里第 CapabilityStore/Duplicate
行
第一,因为框架可能会多次调用 Router/Route
。它包含
复制字典句柄而不复制内部
内容。
fsandbox::RouterRequest::Route { payload: _, responder } => {
let dup_dict_id = id_gen.next();
store.duplicate(dict_id, dup_dict_id).await.unwrap().unwrap();
let capability = store.export(dup_dict_id).await.unwrap().unwrap();
let _ = responder.send(Ok(capability));
}
客户端
客户端很简单。首先,组件清单会检索
bundle
字典中的协议(使用常规检索)
语法。
use: [
{
protocol: [
"fidl.examples.routing.echo.Echo-1",
"fidl.examples.routing.echo.Echo-2",
"fidl.examples.routing.echo.Echo-3",
],
from: "#provider/bundle",
},
],
该程序仅依次连接到每个协议并尝试使用该协议:
for i in 1..=3 {
info!("Connecting to Echo protocol {i} of 3");
let echo = client::connect_to_protocol_at_path::<EchoMarker>(&format!(
"/svc/fidl.examples.routing.echo.Echo-{i}"
))
.unwrap();
let res = echo.echo_string(Some(&format!("hello"))).await;
assert_matches!(res, Ok(Some(s)) if s == format!("hello {i}"));
}
可变性
dictionary
功能遵循以下可变性规则:
- 只有定义字典的组件才能执行以下操作:
汇总或扩展数据。此外,这是
向
dictionary
添加 capability 的唯一方式, 可以在运行时进行修改 - 路由到字典的任何组件都可以检索 功能。
- 如果组件发出两个连续的路由请求,
从同一个
dictionary
检索,可能每个请求都会 就会返回不同的结果。例如,一个请求可能会成功,而 就会出现其他故障。这是功能的按需特性的副作用 路由。在两个请求中,某个组件可能 向上游重新解析并更改了 。