Mojo & Services 簡介

Mojo 概述

一個(gè)消息管道(pipe)是一對端點(diǎn)(endpoints)廊敌。每個(gè)端點(diǎn)都有一個(gè)傳入消息的隊(duì)列铜跑,在一個(gè)端點(diǎn)上寫一條消息可以有效地將該消息排隊(duì)到另一個(gè)(對等)端點(diǎn)上。因此骡澈,消息管道是雙向的锅纺。

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

給定mojom接口和消息管道护锤,可以將其中一個(gè)端點(diǎn)指定為 Remote官地,并用于發(fā)送接口描述的消息。另一個(gè)端點(diǎn)可以指定為 Receiver烙懦,用于接收接口消息驱入。

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

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

示例

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

定義接口

首先創(chuàng)建一個(gè)定義接口的文件,后綴是 .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ī)則文件中定義這個(gè)文件用于生成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時(shí)的便利叉讥,接口的 client(即 Remote 端)通常是創(chuàng)建新管道的一方。這很方便饥追,因?yàn)?Remote 可以用于立即開始發(fā)送消息图仓,而無需等待 InterfaceRequest 端點(diǎn)在任何地方被傳輸或綁定。

以下代碼編寫在 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();

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

注意: PendingReceiver 實(shí)際上什么也做不了。它是一個(gè)消息管道端點(diǎn)(endpoint)的惰性保持器幅骄。它的存在只是為了在編譯時(shí)使其端點(diǎn)更強(qiáng)類型化劫窒,示意端點(diǎn)希望由相同接口類型的 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 對象的活動(dòng)狀態(tài)主巍。畢竟,ping_responder 擁有其消息管道端點(diǎn)挪凑。如果它被銷毀了孕索,那么端點(diǎn)也被銷毀了,將沒有任何東西可以接收響應(yīng)消息躏碳。

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

發(fā)送 PendingReceiver 到瀏覽器

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

在瀏覽器中偏灿,渲染器(renderer)的 RenderFrameImpl 與其對應(yīng)的 RenderFrameHostImpl 之間始終連接的一個(gè)接口是 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 端點(diǎn)傳輸?shù)綖g覽器進(jìn)程沿猜,相應(yīng)的 BrowserInterfaceBroker 實(shí)現(xiàn)將在瀏覽器進(jìn)程中接收到它。

實(shí)現(xiàn)接口

最后碗脊,我們在瀏覽器端實(shí)現(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 的實(shí)現(xiàn)。當(dāng)此實(shí)現(xiàn)接收到 GetInterface 方法調(diào)用時(shí)衙伶,它將調(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è)置足以在渲染器幀與其瀏覽器端主機(jī)對象之間建立新的接口連接。

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

服務(wù)概述

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

一個(gè) service 是一個(gè)獨(dú)立的代碼庫椭员,它實(shí)現(xiàn)了一個(gè)或多個(gè)相關(guān)的特性或行為车海,并且與外部代碼的交互是通過 Mojo 接口連接進(jìn)行的,通常由瀏覽器進(jìn)程代理隘击。

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

示例

通常需要多個(gè)步驟才能啟動(dòng)新服務(wù)并在Chromium中運(yùn)行:

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

定義服務(wù)

通常埋同,服務(wù)定義在 services 目錄中州叠,在這個(gè)示例中,我們?yōu)?Chromium 定義一個(gè)新服務(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 的實(shí)現(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)在我們有了一個(gè)完全定義的 MathService 實(shí)現(xiàn)逆甜,可以在進(jìn)程內(nèi)或進(jìn)程外提供。

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

我們在 chrome/utility/services.cc 中注冊一個(gè)工廠函數(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,
    //...

完成此操作后交煞,瀏覽器進(jìn)程現(xiàn)在可以啟動(dòng) MathService 的新進(jìn)程外實(shí)例。

啟動(dòng)服務(wù)

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

在進(jìn)程外啟動(dòng)上面的服務(wù)實(shí)例,需要使用 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());

除非崩潰怜珍,否則啟動(dòng)的進(jìn)程將與 math_service 一直存活端蛆,可以通過銷毀(或重置)math_service 也會(huì)強(qiáng)制拆除進(jìn)程。

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

// 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)系作者
  • 序言:七十年代末虱饿,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子军援,更是在濱河造成了極大的恐慌肖揣,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,000評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件洞豁,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)情组,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,745評論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來箩祥,“玉大人院崇,你說我怎么就攤上這事∨圩妫” “怎么了底瓣?”我有些...
    開封第一講書人閱讀 168,561評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長蕉陋。 經(jīng)常有香客問我捐凭,道長,這世上最難降的妖魔是什么凳鬓? 我笑而不...
    開封第一講書人閱讀 59,782評論 1 298
  • 正文 為了忘掉前任茁肠,我火速辦了婚禮,結(jié)果婚禮上缩举,老公的妹妹穿的比我還像新娘垦梆。我一直安慰自己匹颤,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,798評論 6 397
  • 文/花漫 我一把揭開白布托猩。 她就那樣靜靜地躺著印蓖,像睡著了一般。 火紅的嫁衣襯著肌膚如雪京腥。 梳的紋絲不亂的頭發(fā)上赦肃,一...
    開封第一講書人閱讀 52,394評論 1 310
  • 那天,我揣著相機(jī)與錄音绞旅,去河邊找鬼摆尝。 笑死,一個(gè)胖子當(dāng)著我的面吹牛因悲,可吹牛的內(nèi)容都是我干的堕汞。 我是一名探鬼主播,決...
    沈念sama閱讀 40,952評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼晃琳,長吁一口氣:“原來是場噩夢啊……” “哼讯检!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起卫旱,我...
    開封第一講書人閱讀 39,852評論 0 276
  • 序言:老撾萬榮一對情侶失蹤人灼,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后顾翼,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體投放,經(jīng)...
    沈念sama閱讀 46,409評論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,483評論 3 341
  • 正文 我和宋清朗相戀三年适贸,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了灸芳。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,615評論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡拜姿,死狀恐怖烙样,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情蕊肥,我是刑警寧澤谒获,帶...
    沈念sama閱讀 36,303評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站壁却,受9級特大地震影響批狱,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜儒洛,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,979評論 3 334
  • 文/蒙蒙 一精耐、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧琅锻,春花似錦卦停、人聲如沸向胡。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,470評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽僵芹。三九已至,卻和暖如春小槐,著一層夾襖步出監(jiān)牢的瞬間拇派,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,571評論 1 272
  • 我被黑心中介騙來泰國打工凿跳, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留件豌,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,041評論 3 377
  • 正文 我出身青樓控嗜,卻偏偏與公主長得像茧彤,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子疆栏,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,630評論 2 359