Mojo Services

一个 Service 通过提供一个或多个 Mojo 接口来暴露一套服务,一个服务可以通过 Connector 来调用其他的服务,但并不是所有的服务之间都可以随意调用,而是通过 Service Manager 来管理多个 Service 间的依赖关系,只有明确表示有依赖关系的服务才能够被调用,而依赖关系则是通过 Manifest 来定义的,所有的这些 Manifest 组合在一起称为一个 Catalog。 这套机制的实现使用 Mojom 定义接口,其中最重要的是 service_manager, service, connector。

下图展示了 Service A 和 Service B 通过定义 Manifest 依赖,使用 Connector 相互调用的示意图:

当 Service A 要调用 Serivce B 提供的接口的时候,通过自己的 Connector 对象向 ServiceManager 发起接口 Binding 请求,Service Manager 会收到这个请求,然后根据已经注册的 Manifest 依赖关系,验证 Service A 是否声明了对 Service B 的依赖,如果是,则 Service Manager 会检查 Service B 是否已经存在,如果已经存在,则使用 Serivce B,否则启动一个新的 Service B 实例,这个实例可能运行在一个独立的进程中,这样 Service A 就获得了由 Service B 提供的一个 Mojo 接口。

需要注意的是,Services 是多进程架构,以上的 Service A,Service B 以及 Service Mananger 都可以运行在独立的进程中。Service Manager 所在的进程称为 Borker 进程。为了方便用户使用该多进程架构,Services 模块提供了一套创建新的可执行程序的框架,目前 Chromium 的启动就使用了该框架,该框架内部会进行一些常用功能的初始化,比如日志,崩溃时的 handler,CommandLine,mojo,Tracker/Trace,LANG 等,其中最重要的是 process_type,默认情况下进程是 Embedder 类型的,在 Chromium 中只使用了这个类型。详细信息可以查看 services/service_manager/embedder/main.cc。

service_manager::Main()

service_manager::Main() 可以帮助我们初始化很多有用的模块,使用方法如下:

#include "base/at_exit.h"

// For services
#include "services/service_manager/embedder/main.h"
#include "services/service_manager/embedder/main_delegate.h"

// 需要先实现一个MainDelegate
class DemoServerManagerMainDelegate : public service_manager::MainDelegate {
 public:
  DemoServerManagerMainDelegate() {}
  // 默认情况下ServiceManager最终会调到这里,将控制权交给用户自己的代码
  int RunEmbedderProcess() override {
    // 自定义进程的行为
    ...
  }
};

int main(int argc, const char** argv) {
  // process_type 为 Service 的进程不会初始化这个
  // 应该是Chromium的bug,因为Chromium中没有使用Service类型的进程
  base::AtExitManager at_exit;
  DemoServerManagerMainDelegate delegate;
  service_manager::MainParams main_params(&delegate);
  main_params.argc = argc;
  main_params.argv = argv;
  return service_manager::Main(main_params);
}

注意: 要是用 Services,并不是一定非要使用以上的启动逻辑,只是以上启动逻辑会帮忙初始化很多有用的模块,用起来比较简单罢了。

创建一个 Service

Service 一般都要提供一些能力 Capacity 给其他 Service 使用,这些 Capacity 是以 Mojo 接口的形式提供的,因此,要创建 Service,需要先定义 Mojo 接口:

定义一个 TestInterface 接口:

module demo.mojom;

interface TestInterface {
    Hello(string who);
};

实现该接口:

// 在新版本中这些类被重命名,这里模拟新版本
template <class T>
using Remote = mojo::InterfacePtr<T>;
template <class T>
using PendingRemote = mojo::InterfacePtrInfo<T>;
template <class T>
using Receiver = mojo::Binding<T>;
template <class T>
using PendingReceiver = mojo::InterfaceRequest<T>;

class TestInterfaceImpl : public demo::mojom::TestInterface {
 public:
  TestInterfaceImpl(PendingReceiver<TestInterface> receiver)
      : receiver_(this, std::move(receiver)) {}

  void Hello(const std::string& who) {
    LOG(INFO) << "TestInterfaceImpl run: Hello " << who;
  }

 private:
  Receiver<demo::mojom::TestInterface> receiver_;
};

实现一个 Service:

// 这里故意把Service的实现和接口的实现分开,方便演示
class TestService : public service_manager::Service {
 public:
  void OnStart() override {
    LOG(INFO) << "TestService Start.";
    // 演示从一个服务内请求其它服务提供的接口
    Remote<RootInterface> root;
    context()->connector()->BindInterface("consumer_service",
      mojo::MakeRequest(&root));
    root->Hi("TestService");
  }
  // 当其他服务调用connector->Connect()时会触发这里
  void OnBindInterface(const service_manager::BindSourceInfo& source,
                       const std::string& interface_name,
                       mojo::ScopedMessagePipeHandle interface_pipe) override {
    LOG(INFO) << "OnBindInterface: " << interface_name;
    // 这里可以使用BinderRegistry来简化
    if (interface_name == TestInterface::Name_) {
      test_interface_.reset(new TestInterfaceImpl(
          PendingReceiver<TestInterface>(std::move(interface_pipe))));
    }
  }

 private:
  std::unique_ptr<TestInterfaceImpl> test_interface_;
};

Servcie 要定义自己的 Manifest 来描述自己提供的能力以及以来的其他 Servcies,Manifest 由一个json 文件描述:

// mojom/test_servcie_manifest.json
{
    "name": "test_service",
    "display_name": "Test Service",
    "interface_provider_specs": {
        "service_manager:connector": {
            "provides": {
                "test": ["demo.mojom.TestInterface"]
            },
            "requires": {
                "consumer_service":["root"]
            }
        }
    }
}

这里可以看到,我们的服务名字为 test_servcie,它通过 demo.mojom.TestInterface 提供了 test 能力,并且依赖 consumer_service 服务的 root 能力。

接下来需要在 BUILD.gn 文件中,将该 json 定义的 Manifest 生成代码可访问的 Catalog,在 BUILD.gn 中添加以下内容:

import("//services/catalog/public/tools/catalog.gni")
import("//services/service_manager/public/cpp/service.gni")
import("//services/service_manager/public/service_manifest.gni")

service_manifest("test_service_manifest") {
  name = "test_service"
  source = "mojom/test_service_manifest.json"
}

service_manifest("consumer_service_manifest") {
  name = "consumer_service"
  source = "mojom/consumer_service_manifest.json"
}

catalog("test_service_catalog") {
  embedded_services = [ 
    ":test_service_manifest",
    ":consumer_service_manifest"
  ]
}

# 这会生成一个名为 demo::services::CreateTestServiceCatalog() 的全局方法
catalog_cpp_source("test_service_catalog_source") {
  catalog = ":test_service_catalog"
  generated_function_name = "demo::services::CreateTestServiceCatalog"
}

现在万事具备,可以创建 ServiceManager 了,注意这里调用了自动生成的 demo::services::CreateTestServiceCatalog() 方法,相当于在 ServiceManager 中注册了这种服务:

service_manager::BackgroundServiceManager service_manager(
    &service_process_launcher_delegate,
    demo::services::CreateTestServiceCatalog());

有了 ServiceManager 就可以启动服务了:

// 可以使用以下方式手动启动一个Service
service_manager.StartService(service_manager::Identity("test_service"));

这样启动后,service_manager 检测到 test_service 不存在,所以就在新进程中启动一个 test_service 服务,在新进程中 test_service 的 OnStart 方法就会被调用,test_service 在 OnStart 方法中就可以使用 Connector 去请求 consumer_service 提供的 root 能力了。

class TestService : public service_manager::Service {
  ...
  void OnStart() override {
    LOG(INFO) << "TestService Start.";
    // 演示从一个服务内请求其它服务提供的接口
    Remote<RootInterface> root;
    context()->connector()->BindInterface("consumer_service",mojo::MakeRequest(&root));
    root->Hi("TestService");
  }
  ...
};

至此,Service 的整条流程也走完了,这里省略了一些逻辑,还有一些其他用法也不赘述了,,具体请查看 demo_services.cc。

Services 的代码位于 Chromium 中的 //services 目录下,需要说明的是,services 并不是完全独立的,它是和 Chromium 紧密相关的,内部存放了大量的 Chromium 相关的 service,比如 device,audio 等,因此如果想像 Mojo 那样在其他和 Chromium 无关的项目中使用可能会比较麻烦。