Chrome-mojo The Service Manager & Services

https://chromium.googlesource.com/chromium/src/+/312b6bf/services/service_manager/README.md

Chromium Mojo & IPC | 柯幽

概述

Service Manager是一个组件,像 Chromium 这样的大型应用程序可以使用它来支持跨平台、多进程、面向服务、连字符形容词负载的体系结构。

本文档介绍了如何将Service Manager嵌入到应用程序中,以及如何定义和注册服务以供其管理。如果您只想阅读有关定义服务和使用公共服务 API 的内容,请跳至主要服务部分。

Embedding the Service Manager

要嵌入Service Manager,应用程序应链接到//services/service_manager/embedderservice_manager::MainDelegate这为大多数平台定义了一个主要入口点,并为应用程序实现提供了一个相对较小的接口。特别是,应用程序至少应实现GetServiceManifests提供有关包含应用程序的全套服务的元数据。

请注意,Chromium 当前未实现GetServiceManifests服务管理器的生产用途。这是因为一堆流程启动和管理逻辑仍然存在于内容层。随着更多此类代码进入 Service Manager 内部,Chromium 将开始看起来更像任何其他 Service Manager 嵌入器。

TODO:在此处改进嵌入程序文档,并在 MainDelegate 支持后包括对进程内服务启动的支持。

Services

此上下文中的服务可以定义为满足以下所有约束任何独立的应用程序逻辑主体:

  • 它定义了接收由服务管理器代理的接口请求的单一实现Service,并使用ServiceBinding.
  • 它的 API 表面进出其他服务仅限于Mojo接口和构建在这些 Mojo 接口上的自包含客户端库。这意味着服务实现的内部堆或全局状态不会在链接时或运行时暴露。
  • 它定义了一个服务清单来声明服务管理器应该如何识别和管理服务实例,以及向系统中的其他服务公开或需要哪些接口。

Service Manager负责管理各个Services实例的创建和互连,无论它们是嵌入在现有进程中还是每个都隔离在专用进程中。托管服务进程可以使用各种受支持的沙箱配置中的任何一种进行沙箱化。

本节将介绍整个服务开发过程中的重要概念和 API,并在此过程中构建一个小型工作示例服务。

关于服务粒度的简要说明

许多开发人员担心一个服务或一组服务的正确“大小”或粒度是多少。这是有道理的,在选择更简单且可能更高效的整体实现与选择更模块化但通常更复杂的实现之间总会存在一些设计张力。

这种矛盾的一个典型例子是 Chromium 的device service。该服务托管了许多独立的设备接口子系统,例如 USB、蓝牙、HID、电池状态等。您可以很容易地想象为这些功能中的每一个提供单独的服务,但最终决定将它们合并为一个服务与硬件设备能力相关。影响这一决定的一些因素:

  • 将功能彼此隔离并没有明显的安全优势。
  • 保持这些功能彼此隔离并没有明显的代码大小优势——支持任何一种设备功能的环境很可能会支持其他几种功能,因此无论如何都可能包括所有或大部分较小的服务
  • 服务中的不同功能之间实际上没有任何耦合,因此构建单独的服务对代码健康几乎没有好处。

考虑到上述所有条件,选择数量较少的服务似乎是正确的决定。

自己做出此类决定时,请运用您的最佳判断。如有疑问,请在services-dev@chromium.org上启动一个 bike-shedding centithread 。

Implementation

任何服务实现中的中心固定装置就是它的Service实现。这是一个很小的接口,实际上只有三个实际感兴趣的虚方法,都可以选择实现:

class Service {
 public:
  virtual void OnStart();
  virtual void OnBindInterface(const BindSourceInfo& source,
                               const std::string& interface_name,
                               mojo::ScopedMessagePipeHandle interface_pipe);
  virtual void OnDisconnected();
};

        每一个服务都实现这样的service接口以成为service的一个子类,以便service manager可以使用生命周期事件和来自其他服务的接口请求调用该服务。

注意:正如下面的实例共享中所讨论的,您的服务配置可能允许服务管理器管理您的服务的许多并发实例。无论这些实例是在同一个共享进程中运行还是在单独的进程中运行,每个实例都恰好由您的实际子类的一个专用实例组成Service

通过本文档的其余部分,我们将构建一个基本的工作服务实现,完成一个清单和简单的测试。我们称它为storage service

我们将此示例限制为两个 mojom 文件。通常将重要常量定义在单独的constants.mojom文件中:
// src/services/storage/public/mojom/constants.mojom
module storage.mojom;

// This string will identify our service to the Service Manager. It will be used
// in our manifest when registering the service, and clients can use it when
// sending interface requests to the Service Manager if they want to reach our
// service.
const string kServiceName = "storage";

// We'll use this later, in service manifest definitions.
const string kAllocationCapability = "allocation";

以及一些有用的接口定义:

// src/services/storage/public/mojom/block.mojom
module storage.mojom;

interface BlockAllocator {
  // Allocates a new block of persistent storage for the client. If allocation
  // fails, |receiver| is discarded.
  Allocate(uint64 num_bytes, pending_receiver<Block> receiver);
};

interface Block {
  // Reads and returns a small range of bytes from the block.
  Read(uint64 byte_offset, uint16 num_bytes) => (array<uint8> bytes);

  // Writes a small range of bytes to the block.
  Write(uint64 byte_offset, array<uint8> bytes);
};

最后我们将定义我们的基本Service子类:

// src/services/storage/storage_service.h

#include "base/macros.h"
#include "services/service_manager/public/cpp/service.h"
#include "services/service_manager/public/cpp/service_binding.h"
#include "services/storage/public/mojom/block.mojom.h"

namespace storage {

class StorageService : public service_manager::Service,
                       public mojom::BlockAllocator {
 public:
  explicit StorageService(service_manager::mojom::ServiceRequest request)
      : service_binding_(this, std::move(request)) {}
  ~StorageService() override = default;

 private:
  // service_manager::Service:
  void OnBindInterface(const service_manager::BindSourceInfo& source,
                       const std::string& interface_name,
                       mojo::ScopedMessagePipeHandle interface_pipe) override {
    if (interface_name == mojom::BlockAllocator::Name_) {
      // If the Service Manager sends us a request with BlockAllocator's
      // interface name, we should treat |interface_pipe| as a
      // PendingReceiver<BlockAllocator> that we can bind.
      allocator_receivers_.Add(
          this, mojo::PendingReceiver<mojom::BlockAllocator>(std::move(interface_pipe)));
    }
  }

  // mojom::BlockAllocator:
  void Allocate(uint64_t num_bytes, mojo::PendingReceiver<mojom::Block> receiver) override {
    // This space intentionally left blank.
  }

  service_manager::ServiceBinding service_binding_;
  mojo::ReceiverSet<mojom::BlockAllocator> allocator_receivers_;

  DISALLOW_COPY_AND_ASSIGN(StorageService);
};

}  // namespace storage

这是一个基本的服务实现:

首先,请注意StorageService构造函数接service_manager::mojom::ServiceRequest并立即将其传递给service_binding_构造函数。这是服务实现中几乎通用的惯例,您的服务可能也会这样做。是ServiceRequest服务管理器用来驱动您的服务的接口管道,是ServiceBinding一个帮助程序类,它将来自服务管理器的消息转换为Service您已实现的类的更简单的接口方法。

StorageService还实现了OnBindInterface,这是服务管理器ServiceBinding在决定将另一个服务的接口请求路由到您的服务实例时调用的(通过您的)。请注意,因为这是旨在支持任意接口的通用 API,所以请求以接口名称和原始消息管道句柄的形式出现。检查名称并决定如何(甚至是否)绑定管道是服务的责任。在这里我们只识别传入的BlockAllocator请求并丢弃其他任何东西。

注意:因为接口接收器只是强类型消息管道端点包装器,所以您可以在原始消息管道句柄上自由构造任何类型的接口接收器。如果您打算传递端点,最好尽早执行此操作(即,一旦您知道预期的接口类型),以便从编译器的类型检查中受益并避免必须传递名称和管道。

我们需要放下的最后一项服务是它的清单。

Manifests

服务的清单是一个简单的静态数据结构,在其初始化过程的早期提供给服务管理器。服务管理器将它拥有的所有清单数据组合在一起,以形成它正在协调的系统的完整画面。它使用所有这些信息来做出如下决定:

  • 当服务 X 向服务 Y 请求接口 Q 时,应该允许吗?
  • X 的请求中指定的所有约束是否有效,是否允许 X 如此指定它们?
  • 我是否需要生成一个新的 Y 实例来满足这个请求,或者我是否可以重新使用现有的实例(假设有)?
  • 如果我必须为新的 Y 实例生成一个新进程,我应该如何配置它的沙箱(如果有的话)?

所有这些元数据都包含在Manifest类的不同实例中。

A Basic Manifest

定义服务清单的最常见方法是将其放在服务的 C++ 客户端库中自己的源目标中。为了将内联一次性初始化的便利与避免静态初始化器结合起来,通常这意味着使用函数局部静态和base::NoDestructor如下service_manager::ManifestBuilder所示。首先是标题:

// src/services/storage/public/cpp/manifest.h

#include "services/service_manager/public/cpp/manifest.h"

namespace storage {

const service_manager::Manifest& GetManifest();

}  // namespace storage

对于实际实施:

// src/services/storage/public/cpp/manifest.cc

#include "services/storage/public/cpp/manifest.h"

#include "base/no_destructor.h"
#include "services/storage/public/mojom/constants.mojom.h"
#include "services/service_manager/public/cpp/manifest_builder.h"

namespace storage {

const service_manager::Manifest& GetManifest() {
  static base::NoDestructor<service_manager::Manifest> manifest{
      service_manager::ManifestBuilder()
          .WithServiceName(mojom::kServiceName)
          .Build()};
  return *manifest;
};

}  // namespace storage

这里我们只指定了服务名称,匹配中定义的常量,constants.mojom以便其他服务无需硬编码字符串即可轻松找到我们。

有了这个清单定义,我们的服务就无法到达其他服务,其他服务也无法到达我们;这是因为我们既不公开也不要求任何功能,因此服务管理器将始终阻止来自我们或针对我们的任何接口请求。

Exposing Interfaces

让我们公开一个授予绑定管道权限的“分配器”功能BlockAllocator。我们可以如下扩充上面的清单定义:

...
#include "services/storage/public/mojom/block.mojom.h"
...

...
          .WithServiceName(mojom::kServiceName)
          .ExposeCapability(
              mojom::kAllocatorCapability,
              service_manager::Manifest::InterfaceList<mojom::BlockAllocator>())
          .Build()
...

这声明了我们的服务公开的能力的存在"allocator",并指定授予客户端此能力意味着授予它发送我们的服务storage.mojom.BlockAllocator接口请求的特权。

您可以为每个公开的功能列出任意数量的接口,并且多个功能可以列出相同的接口。

注意:如果您希望其他服务能够通过服务管理器(请参阅连接器)请求它,则只需要通过功能公开接口——也就是说,如果您在实现中处理对它的请求Service::OnBindInterface

将此与如上所述的可传递获取的接口进行对比Block。服务管理器不调解现有接口连接的行为,因此一旦客户端拥有一个服务管理器,BlockAllocator他们就可以使用它们BlockAllocator.Allocate发送任意数量的Block请求。此类请求直接转到BlockAllocator管道绑定到的服务端实现,因此清单内容与其行为无关。

Getting Access to Interfaces

我们不需要向我们的storage manifest,添加任何其他内容,但如果其他服务想要访问,他们需要在他们的清单中声明他们需要我们的"allocation"能力。为了便于维护,他们会利用我们公开定义的常量来执行此操作。这很简单:

// src/services/some_other_pretty_cool_service/public/cpp/manifest.cc

...       // Somewhere along the chain of ManifestBuilder calls...
          .RequireCapability(storage::mojom::kServiceName,
                             storage::mojom::kAllocationCapability)
...

现在some_other_pretty_cool_service可以使用它的连接器 Connector连接器Service Manager请求BlockAllocator我们的服务,如下所示:

mojo::Remote<storage::mojom::BlockAllocator> allocator;
connector->Connect(storage::mojom::kServiceName,
                         allocator.BindNewPipeAndPassReceiver());

mojo::Remote<storage::mojom::Block> block;
allocator->Allocate(42, block.BindNewPipeAndPassReceiver());

// etc..

Other Manifest Elements

结构中还有一些其他可选元素Manifest可以影响您的服务在运行时的行为方式。请参阅当前Manifest定义和注释以及ManifestBuilder最完整和最新的信息,但清单指定的一些更常见的属性是:

  • Display Name - 这是Service Manager将用来命名为运行您的服务而创建的任何新进程的字符串。例如,该字符串将出现在 Windows 任务管理器中以标识服务进程。
  • Options- 一些杂项选项被填充到一个ManifestOptions字段中。其中包括沙盒类型(请参阅沙盒配置)、实例共享策略以及用于控制一些特殊功能的各种行为标志。
  • Preloaded Files - 在 Android 和 Linux 平台上,服务管理器可以代表服务打开指定文件,并在启动时将相应的打开文件描述符传递给每个新服务进程。
  • Packaged Services——一个服务可以声明它通过包含另一个服务自己的清单的副本来打包另一个服务。有关详细信息,请参阅包装

Running the Service

连接服务使其可以在生产环境中运行实际上目前不在本文档的范围内,只是因为它仍然在很大程度上取决于嵌入服务管理器的环境。现在,如果你想让你的小服务连接到 Chromium 中,你应该查看以 Chromium 为中心的Mojo 和服务简介和/或Servicifying Chromium 功能文档中的相关部分。

出于本文档的目的,我们将重点关注在进程内和进程外服务的测试环境中运行服务。

测试

测试服务时使用三种主要方法,以不同的组合应用:

标准单元测试

这非常适合涵盖服务内部组件的详细信息并确保它们按预期运行。关于服务,这里没有什么特别之处。代码就是代码,您可以对其进行单元测试。

进程外端到端测试

这些有利于尽可能接近地模拟生产环境,将您的服务实现与测试(客户端)代码隔离在一个单独的进程中。

这种方法的主要缺点是它限制了您的测试查看或观察内部服务状态的能力,这有时在测试环境中很有用(例如,以可预测的方式伪造某些行为)。通常,支持此类控件意味着向您的服务添加仅测试接口。

帮助程序TestServiceManagerservice_executableGN 目标类型使这很容易完成。您只需为您的服务定义一个新的入口点:

// src/services/storage/service_main.cc

#include "base/message_loop.h"
#include "services/service_manager/public/cpp/service_executable/main.h"
#include "services/storage/storage_service.h"

void ServiceMain(service_manager::ServiceRequest request) {
  base::SingleThreadTaskExecutor main_task_executor;
  storage::StorageService(std::move(request)).RunUntilTermination();
}

和一个 GN 目标:

import "services/service_manager/public/cpp/service_executable.gni"

service_executable("storage") {
  sources = [
    "service_main.cc",
  ]

  deps = [
    # The ":impl" target would be the target that defines our StorageService
    # implementation.
    ":impl",
    "//base",
    "//services/service_manager/public/cpp",
  ]
}

test("whatever_unittests") {
  ...

  # Include the executable target as data_deps for your test target
  data_deps = [ ":storage" ]
}

最后在您的测试代码中,用于TestServiceManager在您的测试环境中创建一个真正的 Service Manager 实例,配置为了解您的storage服务。

TestServiceManager允许您注入人工服务实例以将您的测试套件视为实际服务实例。您可以为您的测试提供一个清单,以模拟需要(或不需要)各种功能,并获得一个Connector用于访问您的被测服务的清单。这看起来像:

#include "services/service_manager/public/cpp/manifest_builder.h"
#include "services/service_manager/public/cpp/test/test_service.h"
#include "services/service_manager/public/cpp/test/test_service_manager.h"
#include "services/storage/public/cpp/manifest.h"
#include "services/storage/public/mojom/constants.mojom.h"
#include "services/storage/public/mojom/block.mojom.h"
...

TEST(StorageServiceTest, AllocateBlock) {
  const char kTestServiceName[] = "my_inconsequentially_named_test_service";
  service_manager::TestServiceManager service_manager(
      // Make sure the Service Manager knows about the storage service.
      {storage::GetManifest,

       // Also make sure it has a manifest for our test service, which this
       // test will effectively act as an instance of.
       service_manager::ManifestBuilder()
           .WithServiceName(kTestServiceName)
           .RequireCapability(storage::mojom::kServiceName,
                              storage::mojom::kAllocationCapability)
           .Build()});
  service_manager::TestService test_service(
      service_manager.RegisterTestInstance(kTestServiceName));

  mojo::Remote<storage::mojom::BlockAllocator> allocator;

  // This Connector belongs to the test service instance and can reach the
  // storage service through the Service Manager by virtue of the required
  // capability above.
  test_service.connector()->Connect(storage::mojom::kServiceName,
                                          allocator.BindNewPipeAndPassReceiver());

  // Verify that we can request a small block of storage.
  mojo::Remote<storage::mojom::Block> block;
  allocator->Allocate(64, block.BindNewPipeAndPassReceiver());

  // Do some stuff with the block, etc...
}

进程内服务 API 测试

有时您希望主要通过其客户端 API 访问您的服务,但您也希望能够——无论是为了方便还是出于必要——在测试代码中观察或操作其内部状态。在这种情况下,在进程内运行服务是理想的,在这种情况下,涉及服务管理器或处理清单没有多大用处。

相反,您可以使用 aTestConnectorFactory为自己提供一个工作Connector对象,该对象将接口请求直接路由到您直接连接的特定服务实例。举个简单的例子,假设我们有一些客户端库辅助函数,用于在给定 a 时分配一个存储块Connector

// src/services/storage/public/cpp/allocate_block.h

namespace storage {

// This helper function can be used by any service which is granted the
// |kAllocationCapability| capability.
mojo::Remote<mojom::Block> AllocateBlock(service_manager::Connector* connector,
                              uint64_t size) {
  mojo::Remote<mojom::BlockAllocator> allocator;
  connector->Connect(mojom::kServiceName, allocator.BindNewPipeAndPassReceiver());

  mojo::Remote<mojom::Block> block;
  allocator->Allocate(size, block.BindNewPipeAndPassReceiver());
  return block;
}

}  // namespace storage

我们的测试可能类似于:

EST(StorageTest, AllocateBlock) {
  service_manager::TestConnectorFactory test_connector_factory;
  storage::StorageService service(
      test_connector_factory.RegisterInstance(storage::mojom::kServiceName));

  constexpr uint64_t kTestBlockSize = 64;
  mojo::Remote<storage::mojom::Block> block = storage::AllocateBlock(
      test_connector_factory.GetDefaultConnector(), kTestBlockSize);
  block.FlushForTesting();

  // Verify that we have the expected number of bytes allocated within the
  // service implementation.
  EXPECT_EQ(kTestBlockSize, service.GetTotalAllocationSizeForTesting());
}

Connectors连接器

services实例用Connector来向Service manager发送请求

Sending Interface Receivers 发送接口接收器

到目前为止,最常见和最有用的方法ConnectorConnect,它允许您的服务将接口接收器发送到系统中的另一个服务,配置允许。

假设该storage服务实际上依赖于更低级别的存储服务来访问其磁盘,您可以想象其块分配代码执行如下操作:

 mojo::Remote<real_storage::mojom::ReallyRealStorage> storage;
  service_binding_.GetConnector()->Connect(
      real_storage::mojom::kServiceName, storage.BindNewPipeAndPassReceiver());
  storage->AllocateBytes(...);

请注意,这个特定重载的第一个参数Connect是一个字符串,但更通用的形式Connect是一个ServiceFilter在有关服务过滤器的部分中查看有关这些的更多信息。

Registering Service Instances 注册服务实例

可以授予的超能力服务之一是强制将新服务实例注入服务管理器世界的能力。这是通过完成的Connector::ServiceInstance,并且仍然被 Chromium 的浏览器进程大量使用。大多数服务不需要接触此 API。

在多线程环境中的使用

连接器不是线程安全的,但它们支持克隆。有两种有用的方法可以将新连接器与不同线程上的现有连接器相关联。

您可以在自己的Clone线程Connector上,然后将克隆传递给另一个线程:

std::unique_ptr<service_manager::Connector> new_connector = connector->Clone();
base::PostTask(...[elsewhere]...,
               base::BindOnce(..., std::move(new_connector)));

Connector或者你可以从你站着的地方创建一个全新的,然后异步地将它与另一个线程上的一个相关联:

mojo::PendingReceiver<service_manager::mojom::Connector> receiver;
std::unique_ptr<service_manager::Connector> new_connector =
    service_manager::Connector::Create(&receiver);

// |new_connector| can be used to start issuing calls immediately, despite not
// yet being associated with the establshed Connector. The calls will queue as
// long as necessary.

base::PostTask(
    ...[over to the correct thread]...,
    base::BindOnce([](
      mojo::PendingReceiver<service_manager::Connector> receiver) {
      service_manager::Connector* connector = GetMyConnectorForThisThread();
      connector->BindConnectorReceiver(std::move(receiver));
    }));

Identity身份

服务管理器启动的每个服务实例都被分配了一个全局唯一的(跨空间时间)标识,由Identity类型封装。该值被传递给服务,并在调用之前立即保留和公开。ServiceBindingService::OnStart

四个组成部分Identity

  • Service name
  • Instance ID
  • Instance group ID
  • Globally unique ID

您已经非常熟悉service name:这是服务在其清单中声明的​​任何内容,例如"storage"

Instance ID 实例编号

Instance ID是一个base::Token限定符,如果出于任意原因需要多个实例,它仅用于区分服务的多个实例。默认情况下,实例在启动时获得的实例 ID 为零,除非连接的客户端明确请求特定的实例 ID。这样做需要Additional Capabilities涵盖的特殊清单声明功能。

实例 ID 如何发挥作用的一个很好的例子:"unzip"Chrome 中的服务用于安全地解压不受信任的 Chrome 扩展程序 (CRX) 存档,但我们不希望同一过程解压多个扩展程序。base::Token为了支持这一点,Chrome为其连接到服务时使用的实例 ID生成一个随机数"unzip",这会在每个此类连接的新隔离进程中创建一个新服务实例。有关如何完成此操作的信息,请参阅服务过滤器。

Instance ID 实例组 ID

所有创建的服务实例都隐含地属于一个实例组,该实例组也由 标识base::Token。除非被Additional Capabilities特殊授权,或者目标服务是单例或跨组共享,否则发出接口请求的服务只能到达同一实例组中的其他服务实例。有关详细信息,请参阅实例组。

Globally Unique ID全球唯一 ID

最后,全球唯一 ID是一个加密安全的、不可猜测的随机base::Token值,可以被认为在所有时间和空间都是唯一的。这永远无法由实例甚至高特权服务控制,其唯一目的是确保Identity其自身在时间和空间上都被视为唯一的。请参阅服务过滤器观察服务实例,了解为什么这种唯一性属性很有用,有时甚至是必要的。

Instance Sharing 实例共享

假设服务管理器由于足够的能力要求而决定允许接口请求,它必须考虑许多因素来决定将请求路由到哪里。第一个因素是目标服务的实例共享策略,在其清单中声明。支持的策略有以下三种:

  • 无共享- 这意味着目标实例的精确身份取决于请求的实例 IDServiceFilter以及由ServiceFilter源实例组提供或继承的实例组。
  • 跨组共享——这意味着目标实例的精确标识仍然取决于请求的实例 ID ServiceFilter,但实例组ServiceFilter和源实例都被完全忽略。
  • 单例——这意味着无论如何,一次只能有一个服务实例。连接到服务时始终忽略实例 ID 和组。

基于上述策略之一,服务管理器确定现有服务实例是否与给定指定的参数ServiceFilter以及源实例自己的身份相匹配。如果是这样,该服务管理器将通过 将接口请求转发到该实例Service::OnBindInterface。否则,它将生成一个与约束充分匹配的新实例,并将请求转发到该新实例。

Instance Groups实例组

服务实例被组织成实例组。这些是实例的任意分区,主机应用程序可以使用它们来施加各种安全边界。

系统中的大多数服务都没有在传递给时指定它们要连接到的实例组的特权ServiceFilterConnector::Connect请参阅其他功能)。因此,大多数调用隐式继承调用者的组 ID,并且仅在针对在其清单中采用单例或跨组共享策略Connect的服务时跨出调用者的实例组。

单例和跨组共享服务本身总是在它们自己的隔离组中运行。

Service Filters服务过滤器

最常见的Connect调用形式传递一个简单的字符串作为第一个参数。这实际上是在告诉service manager ,调用者不关心有关目标实例身份的任何细节——它只关心与指定服务的某个实例交谈。

当客户确实关心其他细节时,他们可以显式构造并传递一个ServiceFilter对象,该对象实质上提供所需目标实例的总和的某个子集Identity

在 a 中指定实例组或实例 IDServiceFilter需要服务在其清单选项中声明其他功能。

AServiceFilter还可以包装一个完整的Identity值,包括全局唯一 ID。此过滤器始终匹配在空间和时间上唯一的特定实例。因此,如果已识别的实例已经死亡并被具有相同服务名称、相同实例 ID 和相同实例组的新实例替换,请求仍然会失败,因为全局唯一 ID 组件永远不会匹配这个或任何未来实例

以特定为目标的一个有用属性Identity是客户端可以连接而不会引发新的目标实例创建的任何风险:目标存在并且可以路由请求,或者目标不存在并且请求将被丢弃。

Additional Capabilities附加功能

服务清单可用于ManifestOptionsBuilder设置一些额外的布尔选项来控制其服务管理器权限:

  • CanRegisterOtherServiceInstances- 如果这是true服务可以调用RegisterServiceInstance它来Connector强制将新的服务实例引入到环境中。
  • CanConnectToInstancesWithAnyId- 如果这是true服务可以在ServiceFilter它传递给的任何实例中指定一个实例 ID Connect
  • CanConnectToInstancesInAnyGroup- 如果这是true该服务可以在ServiceFilter它传递给的任何对象中指定一个实例组 ID Connect

Packaging包装

一项服务可以通过将另一项服务的清单嵌套在自己的清单中来声明它打包了另一项服务。

这向 Service Manager发出信号,表明它在需要打包服务的新实例时应该遵从打包服务。例如,如果我们提供清单:

  service_manager::ManifestBuilder()
        .WithServiceName("fruit_vendor")
        ...
        .PackageService(service_manager::ManifestBuilder()
                            .WithServiceName("banana_stand")
                            .Build())
        .Build()

如果有人想连接到服务的一个新实例"banana_stand"(,服务管理器会请求一个合适的"fruit_vendor"实例代表它做这件事。

注意:如果一个适当的实例"fruit_vendor"尚未运行——正如上面实例共享中描述的规则所确定的——一个将首先由服务管理器生成。

为了支持此操作,必须fruit_vendor公开一个准确命名的功能"service_manager:service_factory",其中包括"service_manager.mojom.ServiceFactory"接口。service_manager.mojom.ServiceFactory然后它必须在其实现中处理对接口的请求Service::OnBindInterfaceServiceFactory服务提供的实现必须处理CreateService将由服务管理器发送的。此调用将包括服务的名称和ServiceRequest需要绑定的新服务实例。

注意:由于历史原因,它是如此复杂。期待它很快就会变得不那么复杂。

服务可以使用它,例如,如果在某些运行时环境中,他们想要与另一个服务共享他们的进程。

小花絮:这实际上是 Chromium 今天管理所有服务的方式,因为内容层仍然拥有大部分生产就绪流程启动逻辑。我们有一个单例content_packaged_services服务,它打包了系统中几乎所有其他已注册的服务,因此服务管理器将ServiceFactory几乎所有服务实例创建操作(通过)推迟到内容。

沙箱配置

服务清单支持为在进程外运行时启动的服务指定固定的沙箱配置。目前这些值是字符串,必须与此处定义的常量之一匹配。

最常见的默认值是"utility",这是一个限制性的沙箱配置,通常是一个安全的选择。对于必须在非沙盒环境下运行的服务,请使用值"none". 其他沙箱配置的使用应在 Chrome 安全审查员的建议下进行。

观察服务实例

需要"service_manager:service_manager服务能力的服务"service_manager"可以连接到"service_manager"服务以请求ServiceManager接口。这又可以用于注册一个新的ServiceManagerListener以观察与服务管理器托管的所有服务实例相关的生命周期事件。

整个树中有几个这样的例子。

额外支持

如果本文档在某些方面没有帮助,请向您友好的services-dev@chromium.org邮件列表发送消息。

也不要忘记查看树中的其他Mojo 和服务文档。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值