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_responder
是 Remote
救崔,并且 receiver
是 PendingReceiver
是 Receiver
的前身,最終會(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>
對象必須保存存活今豆。