Mojo & Services 簡介

Mojo 概述

一個消息管道(pipe)是一對端點(endpoints)。每個端點都有一個傳入消息的隊列,在一個端點上寫一條消息可以有效地將該消息排隊到另一個(對等)端點上。因此,消息管道是雙向的。

mojom 文件描述了接口,接口是消息的強類型集合。對于熟悉Google protobufs的開發(fā)人員來說,每個接口消息大致類似于單個proto消息。

給定mojom接口和消息管道,可以將其中一個端點指定為 Remote,并用于發(fā)送接口描述的消息。另一個端點可以指定為 Receiver,用于接收接口消息。

上面的概括有點過于簡單化了。請記住,消息管道仍然是雙向的,mojom 消息可能需要回復(fù)。回復(fù)從 Receiver 端發(fā)送,并由 Remote 端點接收。

為了處理接收到的消息,Receiver 端點必須與其 mojom 接口的實現(xiàn)相關(guān)聯(lián)(即 bound)。

示例

假設(shè)我們要在瀏覽器進程中將 “Ping” 消息從呈現(xiàn)幀(render frame)發(fā)送到其對應(yīng)的 RenderFrameHostImpl 實例。為此,我們需要定義一個 mojom 接口,創(chuàng)建一個使用該接口的管道,然后將管道的一端 放置到正確的位置,以便在那里接收和處理發(fā)送的消息。

定義接口

首先創(chuàng)建一個定義接口的文件,后綴是 .mojom

// src/example/public/mojom/ping_responder.mojom
module example.mojom;

interface PingResponder {
  // Receives a "Ping" and responds with a random integer.
  Ping() => (int32 random);
};

然后,需要在規(guī)則文件中定義這個文件用于生成c++代碼

# src/example/public/mojom/BUILD.gn
import("http://mojo/public/tools/bindings/mojom.gni")
mojom("mojom") {
  sources = [ "ping_responder.mojom" ]
}

創(chuàng)建管道

作為一般規(guī)則和使用Mojo時的便利,接口的 client(即 Remote 端)通常是創(chuàng)建新管道的一方。這很方便,因為 Remote 可以用于立即開始發(fā)送消息,而無需等待 InterfaceRequest 端點在任何地方被傳輸或綁定。

以下代碼編寫在 render 中的某塊

// src/third_party/blink/example/public/ping_responder.h
mojo::Remote<example::mojom::PingResponder> ping_responder;
mojo::PendingReceiver<example::mojom::PingResponder> receiver =
    ping_responder.BindNewPipeAndPassReceiver();

在這個例子中,ping_responderRemote,并且 receiverPendingReceiverReceiver 的前身,最終會變成一個 Receiver。BindNewPipeAndPassReceiver 是創(chuàng)建消息管道的最常用方法:它將 PendingReceiver 作為返回值。

注意: PendingReceiver 實際上什么也做不了。它是一個消息管道端點(endpoint)的惰性保持器。它的存在只是為了在編譯時使其端點更強類型化,示意端點希望由相同接口類型的 Receiver 綁定。

發(fā)送消息

在我們的 Remote 中調(diào)用 Ping() 方法發(fā)送消息

// src/third_party/blink/example/public/ping_responder.h
ping_responder->Ping(base::BindOnce(&OnPong));

重要:如果我們想要接收響應(yīng),我們必須在調(diào)用 OnPong 之前保持 ping_responder 對象的活動狀態(tài)。畢竟,ping_responder 擁有其消息管道端點。如果它被銷毀了,那么端點也被銷毀了,將沒有任何東西可以接收響應(yīng)消息。

我們已經(jīng)解決了將消息從 renderer 進程發(fā)送到瀏覽器進程的難題。我們只需要從上面獲取 receiver 對象,然后以某種方式將其傳遞到瀏覽器進程,在瀏覽器進程中,可以將其轉(zhuǎn)換為發(fā)送接收到的消息的 Receiver

發(fā)送 PendingReceiver 到瀏覽器

PendingReceiver(以及一般的消息管道端點)只是另一種可以通過 mojom 消息自由發(fā)送的對象類型。獲取 PendingReceiver 的最常見方法是將其作為方法參數(shù)傳遞到其他已連接的接口上。

在瀏覽器中,渲染器(renderer)的 RenderFrameImpl 與其對應(yīng)的 RenderFrameHostImpl 之間始終連接的一個接口是 BrowserInterfaceBroker。此接口是獲取其他接口的工廠。它的 GetInterface 方法使用 GenericPendingReceiver,它允許傳遞任意接口接收器(receiver)。

interface BrowserInterfaceBroker {
  GetInterface(mojo_base.mojom.GenericPendingReceiver receiver);
}

由于 GenericPendingReceiver 可以從任何特定的 PendingReceiver 隱式構(gòu)造,因此它可以使用它先前通過 BindNewPipeAndPassReceiver 創(chuàng)建的receiver 對象調(diào)用此方法:

RenderFrame* my_frame = GetMyFrame();
my_frame->GetBrowserInterfaceBroker().GetInterface(std::move(receiver));

這將把 PendingReceiver 端點傳輸?shù)綖g覽器進程,相應(yīng)的 BrowserInterfaceBroker 實現(xiàn)將在瀏覽器進程中接收到它。

實現(xiàn)接口

最后,我們在瀏覽器端實現(xiàn) PingResponder 接口。

#include "example/public/mojom/ping_responder.mojom.h"

class PingResponderImpl : example::mojom::PingResponder {
 public:
  explicit PingResponderImpl(mojo::PendingReceiver<example::mojom::PingResponder> receiver)
      : receiver_(this, std::move(receiver)) {}

  // example::mojom::PingResponder:
  void Ping(PingCallback callback) override {
    // Respond with a random 4, chosen by fair dice roll.
    std::move(callback).Run(4);
  }

 private:
  mojo::Receiver<example::mojom::PingResponder> receiver_;

  DISALLOW_COPY_AND_ASSIGN(PingResponderImpl);
};

RenderFrameHostImpl 擁有 BrowserInterfaceBroker 的實現(xiàn)。當(dāng)此實現(xiàn)接收到 GetInterface 方法調(diào)用時,它將調(diào)用以前為此特定接口注冊的處理程序。

// render_frame_host_impl.h
class RenderFrameHostImpl
  ...
  void GetPingResponder(mojo::PendingReceiver<example::mojom::PingResponder> receiver);
  ...
 private:
  ...
  std::unique_ptr<PingResponderImpl> ping_responder_;
  ...
};

// render_frame_host_impl.cc
void RenderFrameHostImpl::GetPingResponder(
    mojo::PendingReceiver<example::mojom::PingResponder> receiver) {
  ping_responder_ = std::make_unique<PingResponderImpl>(std::move(receiver));
}

// browser_interface_binders.cc
void PopulateFrameBinders(RenderFrameHostImpl* host,
                          mojo::BinderMap* map) {
...
  // Register the handler for PingResponder.
  map->Add<example::mojom::PingResponder>(base::BindRepeating(
    &RenderFrameHostImpl::GetPingResponder, base::Unretained(host)));
}

此設(shè)置足以在渲染器幀與其瀏覽器端主機對象之間建立新的接口連接。

假設(shè)我們讓 ping_responder 對象在呈現(xiàn)器(renderer)中存活足夠長的時間,我們最終會看到它的 OnPong 回調(diào)被調(diào)用,其完全隨機值為4,在上面的瀏覽器端實現(xiàn)所定義。

服務(wù)概述

上面只描述了 Mojo IPC 在 Chromium 中的應(yīng)用。雖然 renderer-to-browser 的消息傳遞很簡單,而且可能是最流行的用法,我們正在逐步地將代碼庫拆解為一組服務(wù)(service),比傳統(tǒng)的 Content browser/renderer/gpu/utility process 拆分方式粒度稍大。

一個 service 是一個獨立的代碼庫,它實現(xiàn)了一個或多個相關(guān)的特性或行為,并且與外部代碼的交互是通過 Mojo 接口連接進行的,通常由瀏覽器進程代理。

每個服務(wù)定義并實現(xiàn)一個主 Mojo 接口,瀏覽器可以使用該接口來管理服務(wù)的實例。

示例

通常需要多個步驟才能啟動新服務(wù)并在Chromium中運行:

  • 定義主服務(wù)接口并實現(xiàn)
  • 在進程外的代碼中連接實現(xiàn)
  • 編寫一些瀏覽器邏輯以啟動服務(wù)進程

定義服務(wù)

通常,服務(wù)定義在 services 目錄中,在這個示例中,我們?yōu)?Chromium 定義一個新服務(wù),定義在 chrome/services 目錄中。

創(chuàng)建 mojom 文件:

// src/chrome/services/math/public/mojom/math_service.mojom
module math.mojom;

interface MathService {
  Divide(int32 dividend, int32 divisor) => (int32 quotient);
};
# src/chrome/services/math/public/mojom/BUILD.gn
import("http://mojo/public/tools/bindings/mojom.gni")

mojom("mojom") {
  sources = [
    "math_service.mojom",
  ]
}

MathService 的實現(xiàn):

// src/chrome/services/math/math_service.h
#include "base/macros.h"
#include "chrome/services/math/public/mojom/math_service.mojom.h"

namespace math {

class MathService : public mojom::MathService {
 public:
  explicit MathService(mojo::PendingReceiver<mojom::MathService> receiver);
  ~MathService() override;

 private:
  // mojom::MathService:
  void Divide(int32_t dividend,
              int32_t divisor,
              DivideCallback callback) override;

  mojo::Receiver<mojom::MathService> receiver_;

  DISALLOW_COPY_AND_ASSIGN(MathService);
};

}  // namespace math
// src/chrome/services/math/math_service.cc
#include "chrome/services/math/math_service.h"

namespace math {

MathService::MathService(mojo::PendingReceiver<mojom::MathService> receiver)
    : receiver_(this, std::move(receiver)) {}

MathService::~MathService() = default;

void MathService::Divide(int32_t dividend,
                         int32_t divisor,
                         DivideCallback callback) {
  // Respond with the quotient!
  std::move(callback).Run(dividend / divisor);
}

}  // namespace math
# src/chrome/services/math/BUILD.gn

source_set("math") {
  sources = [
    "math.cc",
    "math.h",
  ]

  deps = [
    "http://base",
    "http://chrome/services/math/public/mojom",
  ]
}

現(xiàn)在我們有了一個完全定義的 MathService 實現(xiàn),可以在進程內(nèi)或進程外提供。

連接服務(wù)實現(xiàn)

我們在 chrome/utility/services.cc 中注冊一個工廠函數(shù)。

auto RunMathService(mojo::PendingReceiver<math::mojom::MathService> receiver) {
  return std::make_unique<math::MathService>(std::move(receiver));
}

mojo::ServiceFactory* GetMainThreadServiceFactory() {
  // Existing factories...
  static base::NoDestructor<mojo::ServiceFactory> factory {
    RunFilePatcher,
    RunUnzipper,

    // We add our own factory to this list
    RunMathService,
    //...

完成此操作后,瀏覽器進程現(xiàn)在可以啟動 MathService 的新進程外實例。

啟動服務(wù)

如果你在進程中運行服務(wù),就沒有什么有趣的事情要做了。你可以像其他任何對象一樣實例化服務(wù)實現(xiàn),但你也可以通過 Mojo Remote 程序與它進行對話,就好像它已經(jīng)脫離流程一樣。

在進程外啟動上面的服務(wù)實例,需要使用 ServiceProcessHost API:

mojo::Remote<math::mojom::MathService> math_service =
    content::ServiceProcessHost::Launch<math::mojom::MathService>(
        content::ServiceProcessHost::LaunchOptions()
            .WithSandboxType(content::SandboxType::kUtility)
            .WithDisplayName("Math!")
            .Pass());

除非崩潰,否則啟動的進程將與 math_service 一直存活,可以通過銷毀(或重置)math_service 也會強制拆除進程。

我們現(xiàn)在可以執(zhí)行進程外分配:

// NOTE: As a client, we do not have to wait for any acknowledgement or
// confirmation of a connection. We can start queueing messages immediately and
// they will be delivered as soon as the service is up and running.
math_service->Divide(
    42, 6, base::BindOnce([](int32_t quotient) { LOG(INFO) << quotient; }));

注意:為了確保響應(yīng)回調(diào)的執(zhí)行,mojo::Remote<math::mojom::MathService> 對象必須保存存活。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容