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_responder 是 Remote,并且 receiver 是 PendingReceiver 是 Receiver 的前身,最終會變成一個 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>對象必須保存存活。