Lynx 做爲一個基於 JavaScript 語言(後續簡稱 JS )的跨平臺開發框架,與 JS 的通訊是"與生俱來"的,框架和 JS 引擎打交道是必不可少的能力。JS 引擎提供了 Extension 功能,提供接入方間接和 JS 通訊的橋樑,Lynx 的 JS Binding 正是基於這個能力進行了封裝,構建一套基礎的 JS API,將能力開放給前端開發者。前端
當前主流瀏覽器基本都擁有本身的 JS 引擎,在當前移動端最爲流行的 JS 引擎屬 V8 和 JavascriptCore (別名 Nitro,後續簡稱 JSC),Lynx 框架圍繞這兩個引擎打造高效的 JS Binding。c++
JS Binding 最主要的目的是利用 JS 引擎和 JS 通訊,開放底層框架能力,也能夠稱它爲 JS Bridge,它決定了 JS 和框架層通訊的速度。Lynx 早期版本爲了快速實現高效通訊,依賴 V8 引擎(後續簡稱 V8),使用的是「純粹」的 Extension 方式,即依照 V8 的拓展方式實現了 JS Binding,創建一套 JS API ,在 Android 系統上首先實現了渲染層的平臺拓展。git
Lynx 以這種方式在早期快速實現可靠高效的通訊能力。但當 Lynx 把平臺拓展到 iOS 時,因爲 V8 沒法在 iOS 平臺使用,JS Binding 必須把 V8 切換成 JSC ,全部關於 V8 的類和函數定義以及初始化流程,均要替換成 JSC 的標準。第一個 JSC 版本的 JS Binding 是基於 JSC iOS 標準實現的。而第二個 JSC 版本的 JS Binding 是基於純 JSC 標準實現的,此次改動的目的是但願能統一 Android 和 iOS 的底層 JS 引擎。github
本文主要介紹主流 JS 引擎使用姿式及 Lynx 中 JS Binding 的內存管理方式,做爲 JS Binding 技術演進分析的鋪墊。瀏覽器
在瞭解 Lynx 中 JS Binding 技術的作法前,先了解一下 V8 和 JSC 在初始化和 Extension 方面的標準實現,從中發現兩個引擎的異同,當掌握了基礎的用法以後,能更好的理解 Lynx 中 JS Binding 的發展路線。下面 Extension 拓展以 example 對象爲例。安全
example.h 頭文件,這個類定義了即將暴露給 JS 端的 example 對象所具備的接口,包括 TestStatic 和 TestDynamic 方法及變量 num 的設置和獲取。框架
class Example {
public:
Example();
virtual ~Example();
void TestStatic();
void TestDynamic();
int num();
void set_num(int num);
private:
void Initialize();
};
複製代碼
在具體實現代碼中,主要功能是建立 JS 上下文,建立 example 的 JS 對象,靜態註冊 testStatic 方法和 num 變量,動態註冊 testDynamic 並暴露到上下文中。完成後能夠經過在 JS 端使用以下代碼訪問到 example c++ 對象的接口。函數
example.testStatic();
example.testDynamic();
example.num = 99;
console.log(example.num);
複製代碼
接下來將分析兩個引擎中的實現代碼,包括環境初始化和 Extension 方式,代碼主要關注如下點性能
靜態註冊指的是對 JS 的原型 prototype 設置屬性、方法及鉤子函數,從持有該原型的構造函數建立的對象均有設置的方法和屬性及鉤子函數。ui
動態註冊指直接對 JS 對象設置方法的鉤子函數,僅有被設置過的對象才擁有被設置的方法。動態註冊屬性鉤子函數的方式在 JS 引擎中暫時沒有提供直接的方式.
V8 初始化和 Extension 方式
example_v8.cc 文件,如下爲 V8 Extension 示例工程部分代碼,完整代碼請看附錄 。總體流程總結以下:
初始化 V8 的環境
V8::InitializeICUDefaultLocation(argv[0]);
V8::InitializeExternalStartupData(argv[0]);
v8::Platform* platform = v8::platform::CreateDefaultPlatform();
V8::InitializePlatform(platform);
v8::V8::Initialize();
複製代碼
建立 global 對象模板,據此建立 JS 運行上下文 context,從 context 中獲取 global 對象
// 建立isolate
v8::Isolate* isolate = v8::Isolate::New(create_params);
v8::Isolate::Scope isolate_scope(isolate);
v8::HandleScope handle_scope(isolate);
// 建立global 對象模板
v8::Local <v8::ObjectTemplate> global_template = v8::ObjectTemplate::New(isolate);
// 建立 JS 運行上下文 context
v8::Local <v8::Context> context = v8::Context::New(isolate, nullptr, global_template);
v8::Context::Scope context_scope(context);
// context 中獲取 global 對象
v8::Local<v8::Object> global = context->Global();
複製代碼
建立 example 對象的構造函數模板,在構造函數模板中獲取原型模板,並設置靜態方法和變量的鉤子
// 建立 example 的構造函數模板, 使用該 c++ 類的初始化函數做爲參數(函數鉤子),初始化構造器函數模
// 板。即當調用構造函數建立對象時,會調用該鉤子函數作構造處理
v8::Local<v8::FunctionTemplate> example_tpl = v8::FunctionTemplate::New(isolate);
// 設置構造函數模板的類名
example_tpl->SetClassName(V8Helper::ConvertToV8String(isolate, "Example"));
// 設置內部關聯 c++ 對象的數量爲 1
example_tpl->InstanceTemplate()->SetInternalFieldCount(1);
// 設置構造函數模板中的原型模板的對應函數名的鉤子
example_tpl->PrototypeTemplate()->Set(V8Helper::ConvertToV8String(isolate, "testStatic"), v8::FunctionTemplate::New(isolate, TestStaticCallback));
// 設置構造函數模板中的原型模板的屬性的 Get 和 Set 鉤子方法
example_tpl->PrototypeTemplate()->SetAccessor(V8Helper::ConvertToV8String(isolate, "num"), GetNumCallback, SetNumCallback);
複製代碼
用於靜態註冊的函數鉤子,包括 testStatic 方法鉤子和 num 的 get / set 鉤子
// example.testStatic() 調用時對應的 c++ 函數鉤子
static void TestStaticCallback(const v8::FunctionCallbackInfo <v8::Value> &args) {
Example* example = static_cast<Example*>(args.Holder()->GetAlignedPointerFromInternalField(0));
example->TestStatic();
}
// console.log(example.num) 調用時對應觸發的 c++ 鉤子函數
static void GetNumCallback(v8::Local<v8::String> property, const PropertyCallbackInfo<Value>& info) {
Example* example = static_cast<Example*>(args.Holder()->GetAlignedPointerFromInternalField(0));
int num = example->num();
info.GetReturnValue().Set(v8::Number::New(isolate, num));
}
// example.num = 99 時會觸發該的 c++ 函數鉤子
static void SetNumCallback(v8::Local<v8::String> property, v8::Local<v8::Value> value, const PropertyCallbackInfo<void>& info) {
if (value->IsInt32()) {
Example* example = static_cast<Example*>(args.Holder()->GetAlignedPointerFromInternalField(0));
example->set_num(value->ToInt32())
}
}
複製代碼
根據函數模板建立 example 對象,關聯對應 c++ 對象,動態註冊方法鉤子
// 在函數模板中獲取可調用的函數
v8::Local<v8::Function> example_constructor = example_tpl->GetFunction(context).ToLocalChecked();
// 調用函數的建立對象的方法,建立 JS 引擎的 example 對象
v8::Local<v8::Object> example =
example_constructor->NewInstance(context, 0, nullptr).ToLocalChecked();
// 關聯 JS 引擎對象和 c++ 對象
handle->SetAlignedPointerInInternalField(0, this);
// 動態註冊函數鉤子
v8::Local<v8::Function> dynamic_test_func = v8::FunctionTemplate::New(TestDynamicCallback, args.Data())->GetFunction();
v8::Local<v8::String> dynamic_test_name = v8::String::NewFromUtf8(isolate, "testDynamic", v8::NewStringType::kNormal).ToLocalChecked();
dynamic_test_func->SetName(dynamic_test_name);
example->Set(dynamic_test_name, dynamic_test_func);
複製代碼
用於於動態註冊的 testDynamic 的函數鉤子
// example.testDynamic() 調用時對應的 c++ 函數鉤子
static void TestDynamicCallback(const v8::FunctionCallbackInfo <v8::Value> &args) {
Example* example = static_cast<Example*>(args.Holder()->GetAlignedPointerFromInternalField(0));
example->TestDynamic();
}
複製代碼
將 example 對象做爲變量添加到 global 的屬性中
v8::Local<v8::String> example_v8_str = v8::String::NewFromUtf8(isolate, "example", v8::NewStringType::kNormal).ToLocalChecked()
global->Set(context, example_v8_str, example).FromJust();
複製代碼
如何銷燬虛擬機 對於普通的銷燬步驟來講,v8引擎對於虛擬機的銷燬分爲銷燬 Context 和銷燬 Isolate ,通常v8的 context 會使用 v8::Persistent<v8::Context> 持有,在調用 v8::Persistent 的 Reset 方法以後,當前 context 中使用擴展方式註冊的對象可能不會被徹底回收,所以須要本身手動進行回收
JSC 初始化和 Extension 方式
example_jsc.cc 文件,如下爲 JSC Extension 示例工程部分代碼,完整代碼請看附錄。總體流程總結以下:
初始化 JSC 的環境
JSContextGroupRef context_group = JSContextGroupCreate();
複製代碼
建立 global 類定義,據此建立 global 類,根據 global 類建立 JS 運行上下文 context,從 context 中獲取 global 對象
JSClassDefinition global_class_definition = kJSClassDefinitionEmpty;
JSClassRef global_class = JSClassCreate(&global_definition);
JSContextRef context = JSGlobalContextCreateInGroup(context_group, global_class);
JSObjectRef global = JSContextGetGlobalObject(context);
複製代碼
建立 example 類定義,向類定義設置靜態方法和變量的鉤子
// 定義將要 Extension 的靜態方法,其中包含函數鉤子
static JSStaticFunction s_examplle_function_[] = {
{"testStatic", TestStaticCallback, kJSClassAttributeNone},
{ 0, 0, 0 }
};
// 定義將要 Extension 的變量,其中包含 get 和 set 函數鉤子
static JSStaticValue s_example_values_[] = {
{"num", GetNumCallback, SetNumCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
{ 0, 0, 0, 0 }
};
// 建立 example 類的定義
JSClassDefinition example_class_definition = kJSClassDefinitionEmpty;
// 設置類的對應函數名和參數名的鉤子
example_class_definition.staticFunctions = s_console_function_;
example_class_definition.staticValues = s_console_values_;
// 設置類的名稱
example_class_definition.className = "Example";
複製代碼
用於靜態註冊的函數鉤子,包括 testStatic 方法鉤子和 num 的 get / set 鉤子
// example.test() 調用時對應的 c++ 函數鉤子
static JSValueRef TestStaticCallback(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) {
// 獲取 JS 引擎對象中持有的 c++ 對象
Example* example = static_cast<Example*>(JSObjectGetPrivate(object));
example->TestStatic();
}
// console.log(example.num) 調用時對應觸發的 c++ 鉤子函數
static JSValueRef GetNumCallback(JSContextRef ctx, JSObjectRef object, JSStringRef propertyName, JSValueRef* exception) {
Example* example = static_cast<Example*>(JSObjectGetPrivate(object));
int num = obj->num();
return JSValueMakeNumber(ctx, num);
}
// example.num = 99 時會觸發該的 c++ 函數鉤子
static bool SetNumCallback(JSContextRef ctx, JSObjectRef object, JSStringRef propertyName, JSValueRef value, JSValueRef* exception) {
Example* example = static_cast<Example*>(JSObjectGetPrivate(object));
obj->set_num(JSValueToNumber(ctx, value, NULL));
}
複製代碼
根據類定義建立類,根據類建立 example 對象,關聯對應 c++ 對象,動態註冊方法鉤子
// 建立 JS 引擎的類
JSClassRef example_class_ref = JSClassCreate(&example_class_definition);
JSObjectRef example = JSObjectMake(context, example_class_ref, NULL);
// 關聯 c++ 對象和 JS 引擎對象
JSObjectSetPrivate(context, example, this)
JSClassRelease(example_class_ref);
// 動態註冊函數鉤子
JSStringRef dynamic_test_func_name = JSStringCreateWithUTF8CString("testDynamic");
JSObjectRef dynamic_test_func = JSObjectMakeFunctionWithCallback(context, dynamic_test_func_name, TestDynamicCallback);
JSObjectSetProperty(context, example, dynamic_test_func_name, dynamic_test_func, kJSPropertyAttributeDontDelete, NULL);
JSStringRelease(dynamic_test_func_name);
複製代碼
用於於動態註冊的 testDynamic 的函數鉤子
// example.testDynamic() 調用時對應的 c++ 函數鉤子
static JSValueRef TestDynamicCallback(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) {
Example* example = static_cast<Example*>(JSObjectGetPrivate(object));
example->TestDynamic();
}
複製代碼
將 exmaple 對象做爲變量添加到 global 的屬性中
JSStringRef example_str_ref = JSStringCreateWithUTF8CString("example");
JSObjectSetProperty(context, global, example_str_ref, example, kJSPropertyAttributeDontDelete, NULL);
JSStringRelease(example_str_ref);
複製代碼
如何銷燬虛擬機 JSC引擎中對於虛擬機的銷燬相對比較簡單,只須要經過調用 JSGlobalContextRelease 和 JSContextGroupRelease 來分別對 Context 和 Context Group 便可,內存中使用擴展方式註冊的對象都會在銷燬過程當中調用 Finalize 回調
關於線程安全
JS的 context 並不是是線程安全的,所以一個 context 不能在多個線程之間共享,以免碰見未知錯誤。對於應用來講可能須要使用多個 JS Context,而使用多個 Context 的方式有兩種,獨佔式和共享式。
獨佔式,所謂獨佔式是指一個 Context 使用一個線程進行處理 共享式,所謂共享式是指多個 Context 使用一個線程進行處理,以v8爲例,在切換 Context 的時候須要設置v8::Isolate::Scope 和 v8::Context::Scope ,能夠仿造下面示例經過宏定義來處理
#define MakeCurrent(_isolate, _context) \ v8::Isolate* isolate = _isolate == NULL ? v8::Isolate::GetCurrent() : _isolate; \ v8::Isolate::Scope isolate_scope(isolate); \ v8::HandleScope handleScope(isolate); \ v8::Context::Scope context_scope(v8::Local<v8::Context>::New(isolate, _context));
複製代碼
小結
在上述的幾個示例中,能夠看出在建立對象和註冊靜態方法和變量上,V8 和 JSC 具備各自的 API 和變量命名特色,方法中的參數類型也是徹底不一致的,V8 和 JSC 在這一層面上具備極大的差別。這也致使了前述在替換 Lynx 的 JS 引擎時所耗費的成本。
然而兩個引擎的總體流程是一致的,同時 API 和參數類型在概念上也是一致的,其緣由是 V8 和 JSC 都遵循 JS 的規範。相同點例如:
當對兩個引擎的 API 具備必定熟練度,同時對於 JS 已經有必定的掌握,對於兩個引擎在使用上的異同,能有更好的理解。
JS 引擎具備內存管理機制,在創建 JS Binding 時,不可避免的要創建本地對象(c++ 對象或者平臺對象)與 JS 引擎對象的關係,恰當的對象關係能保證程序在內存上的穩定,這須要使用到 JS 引擎的內存管理相關知識以及其提供能夠控制對象在 GC 機制上的行爲的接口。
一切由 JS 引擎提供或建立的對象的生命週期管理須要由其內部的 GC 機制把控,JS 引擎提供了兩個接口管理 JS 對象在 GC 機制上的行爲,一個是持久化操做幫助 JS 對象脫離 GC 管理,一個是使 JS 對象迴歸 GC 管理。JS 對象在其生命週期內所出現的行爲都可以監聽(加鉤子),例如 JS 對象的初始化和析構監聽。V8 引擎和 JSC 引擎涉及的知識和接口均不相同,可是在概念上是一致的,下面看一下兩個平臺上的區別。
V8 引擎監聽和管理對象生命週期的方法
V8 引擎在使用上會常常出現 Handle(句柄)的用法,這是引擎對於其對象訪問和管理的方式,Handle 提供在堆上的 JS 對象位置的引用。當一個 JS 對象在 JS 上已經沒法訪問,同時沒有任何的句柄指向他,則被認爲可被 GC 回收,這是引擎提供句柄的意義。
在 V8 引擎中,在 GC 運行階段,垃圾收集器會常常移動堆上的對象,同時會更新句柄內容使其指向 JS 對象在堆上更新後的位置。
Local handle(局部句柄)和 Persistent handle(持久句柄)是常用到的其中兩種句柄類型。
Local handle 被存放在棧上面,它的生命週期僅存在於一個句柄域中(handle scope),當程序跳出函數塊,句柄域析構,局部句柄也隨之被釋放,指向 JS 對象的句柄數量隨之減小。
Handle scope 比如一個容器(棧),當初始化以後,它會收集在這期間建立的局部句柄,當被析構以後,全部的局部句柄將被移除,觸發局部句柄的析構。
Persistent handle(持久句柄)和局部句柄同樣提供了一個引用指向堆上分配的 JS 對象,但對於其引用的生命週期管理與局部句柄不同。持久句柄存放在全局區,所以生命週期不受局部區域塊的影響,可以在其的生命週期內在多個函數中屢次使用,既 JS 對象脫離 GC 管理。持久句柄能夠經過 Persistent::New
方法由局部句柄生成,也能夠經過 Local::New
方法生成局部句柄。能夠經過 Persistent::SetWeak
方法進行弱持久化,同時也能夠經過 Persistent::Reset
方法去持久化。
弱持久化,設置了弱持久化則 Persistent handle 的 JS 對象會當僅剩下該弱持久句柄指向 JS 對象,GC 收集器將會回收並觸發被設置的監聽函數。
去持久化,釋放持久句柄對於堆上的對象的引用
Local handle 和 Persistent handle 的轉化方式
void TestHandle() {
v8::Isolate* isolate = v8::Isolate::GetCurrent();
// 下面的代碼會建立局部句柄,須要使用一個 handle scope 來管理
HandleScope handle_scope(isolate);
// 建立一個 JS Array 對象,返回的是一個局部句柄
v8::Local<v8::Array> local_array = v8::Array::New(isolate, 3);
// 將局部句柄轉爲持久句柄
v8::Persistent<v8::Array> persistent_array = v8::Persistent<v8::Array>::New(isolate, local_array);
// 將持久句柄轉爲局部句柄
local_array = v8::Local<v8::Object>::New(persistent_array);
// 將持久句柄去持久化
persistent_array.Reset();
// 當函數塊結束後,局部句柄將被析構, JS Array 對象也在將來某個事件被 GC
}
複製代碼
將持久句柄進行弱持久化的方式以下,在弱持久化的 API 中提供了 JS 對象被 GC 時的監聽回調的設置。
static void WeakCallback(const v8::WeakCallbackInfo<ObjectWrap>& data) {
v8::Isolate *isolate = data.GetIsolate();
v8::HandleScope scope(isolate);
// data.GetParameter() 是上述 MakeWeak 時傳進來的參數 this
}
void MakeWake() {
persistent_array_.SetWeak(this, WeakCallback, v8::WeakCallbackType::kParameter);
}
複製代碼
JSC 引擎管理對象 GC 行爲的接口
JSC 引擎上經常使用於和 JS 打交道的對象包括 JSContextGroupRef / JSContextRef / JSValueRef / JSObjectRef / JSStringRef / JSClassRef 等,對於這些對象的內存管理的方式和 V8 引擎上的方式有所不一樣。在 JSC 引擎上直接接觸到的分爲包裝和指針,而指針部分由引擎 GC 管理,須要開發者手動管理。
JSStringRef、 JSContextRef、JSContextGroupRef、JSClassRef 就是典型的不受管理的指針,當開發者建立了它們以後,必須在不須要使用的時候釋放它們的內存,不然會出現內存泄露。請看下面的示例。
JSContextGroupRef context_group = JSContextGroupCreate();
JSClassRef global_class = JSClassCreate(&kJSClassDefinitionEmpty);
JSContextRef context = JSGlobalContextCreateInGroup(context_group, global_class);
JSStringRef str = JSStringCreateWithUTF8CString("try");
// 手動回收內存
JSClassRelease(golbal_class);
JSStringRelease(str);
JSGlobalContextRelease(context);
JSContextGroupRelease(context_group);
複製代碼
JSValueRef 表明着 JS 對象( JSObjectRef 也是一個 JSValueRef ) 是由 JSC 引擎中的 GC 機制管理的,當 JSValueRef 建立出來後,當須要超出當前函數塊在全局區域或者堆上繼續操做,則須要經過 JSValueProtect
對 JSValueRef 進行持久化的操做,JS 對象將脫離 GC 的管理。JSValueUnprotect
是 JSC 引擎提供的對於 JSValueRef 去持久化的 API。持久化和去持久化必須成對出現,不然會出現內存泄露。
// 假設已經初始化上下文
JSContextRef context;
JSObjectRef object = JSObjectMake(context, NULL, NULL);
// 持久化操做
JSValueProtect(context, object);
// 去持久化操做
JSValueUnprotect(context, object);
複製代碼
JSC 中沒有弱持久化的概念,經過類定義建立出來的對象均可以監聽其被 GC 的事件,這一點和 V8 不一樣。對於普通或是去持久化的 JS 對象, 監聽其析構函數的方式是在建立 JS 對象的類定義時候須要加入析構函數的鉤子。
static void FinalizeCallback(JSObjectRef object) {
// do sth
}
void CreateObject(JSContextRef context) {
JSClassDefinition definition = kJSClassDefinitionEmpty;
definition.finalize = FinalizeCallback;
JSClassRef class_ref = JSClassMake(definition);
JSObjectRef object = JSObjectMake(context, class_ref, NULL);
JSClassRelease(class_ref);
}
複製代碼
瞭解了基本的 JS 引擎初始化、 Extension 和內存管理知識,對於後續 Lynx 上各個 JS Binding 的做用以及框架設計會有更好的理解。
Lynx 是一個由前端驅動的框架,須要創建本地對象和 JS 對象的關係,本地對象跟隨 JS 對象的生命週期。
Lynx 早期的快速實現首先在 Android 平臺上進行,依賴 V8 引擎,利用 Java 搭建了核心鏈路。因爲 Android 並無直接和 V8 溝通的接口,經過引入 V8 的動態 so,Lynx 結合 JNI 實現 JS Binding,創建和 V8 的橋樑。總體設計以下圖。JS Binding 是做爲 Android 和 JS 的通訊使者,JS Binding 包括了 JS 引擎初始化模塊 JSContext 和一系列拓展模塊,如 JS 上下文中 document、element 對象等。
在前期版本中只有簡單的設計,主要目的是知足功能。這裏主要介紹基本類及其做用和總體流程。
基礎類介紹
關鍵基礎類 UML 圖
JSContext 用於初始化 V8 環境,向 V8 中註冊基礎對象,搭建中間層 JS Binding。經過 JS Binding 能夠實現 Android 層面的對象和 V8 的間接調用。JS Binding 中包括 DocumentObject、WindowObject、ElementObject 等。
ElementObject 屬於 JS Binding 中的成員,做爲兩端 Element 通訊的使者。其父類是 JavaObjectWrap。用於向 V8 中拓展 element 對象及註冊相關函數和變量鉤子,在 JS 上下文提供基礎 API。同時初始化 JNI 函數,準備和 Java Element 通訊基礎。與 ElementObject 做用相似的還有 DocumentObject、WindowObject 等。
JavaObjectWrap 做爲通訊使者的基類,經過 V8 引擎和 JNI 提供的持久化接口是創建 Android 端對象和 V8 端對象的關係,使 Android 對象、c++對象和 V8 對象具備一致的生命週期。在 JavaObjectWrap 中持有 Java 持久化對象和 JS 持久化對象。繼承 Observer 類。
Observer 做用是確保頁面退出時,在 JSBinding 上沒有泄露的內存。在頁面退出時,防止在頁面退出後由於 JS 引擎的內存管理影響,致使 c++ 和 Android 的內存泄露,清理在 JS Binding 中的未被 GC 的對象。
總體流程
JS Binding 擔任了兩個不一樣平臺間交流的使者,是整個 Lynx 框架的基石。JS Binding 總體流程主要包括 JS Binding 初始化流程、JS 調用拓展 API 流程和 JS Binding 結束流程。
在頁面啓動時,JS Binding 會進行初始化操做,JS 上下文中就能具有了使用 Lynx 框架能力的功能,初始化流程以下
當 JS 中經過 document.createElement('view') 方法時,會簡潔調用到 Java 層接口並返回具體結果,具體流程以下
Lynx 頁面退出表明着 JS Binding 的結束,Android 層經過 JNI 釋放 JSContext 內存,進而釋放 JS 對象、c++ 對象和 Java 對象所佔用的內存。觸發析構函數將完成如下操做
這篇文章主要介紹 JS Binding 入門知識和 Lynx 框架在早期版本 JS Binding 的簡單實現,爲後續的 Lynx 框架的 JS Binding 的演進拋磚引玉。下一篇文章將分析 Lynx 框架中 JS Binding 演進的技術,並介紹其優缺點。
請持續關注 Lynx,一個高性能跨平臺開發框架。
example_v8.cc 初始化流程具體代碼
// example.testStatic() 調用時對應的 c++ 函數鉤子
static void TestStaticCallback(const v8::FunctionCallbackInfo <v8::Value> &args) {
Example* example = static_cast<Example*>(args.Holder()->GetAlignedPointerFromInternalField(0));
example->TestStatic();
}
// example.testDynamic() 調用時對應的 c++ 函數鉤子
static void TestDynamicCallback(const v8::FunctionCallbackInfo <v8::Value> &args) {
Example* example = static_cast<Example*>(args.Holder()->GetAlignedPointerFromInternalField(0));
example->TestDynamic();
}
// console.log(example.num) 調用時對應觸發的 c++ 鉤子函數
static void GetNumCallback(v8::Local<v8::String> property, const PropertyCallbackInfo<Value>& info) {
Example* example = static_cast<Example*>(args.Holder()->GetAlignedPointerFromInternalField(0));
int num = example->num();
info.GetReturnValue().Set(v8::Number::New(isolate, num));
}
// example.num = 99 時會觸發該的 c++ 函數鉤子
static void SetNumCallback(v8::Local<v8::String> property, v8::Local<v8::Value> value, const PropertyCallbackInfo<void>& info) {
if (value->IsInt32()) {
Example* example = static_cast<Example*>(args.Holder()->GetAlignedPointerFromInternalField(0));
example->set_num(value->ToInt32())
}
}
void Example::Initialize() {
// 初始化 V8 引擎
V8::InitializeICUDefaultLocation(argv[0]);
V8::InitializeExternalStartupData(argv[0]);
v8::Platform* platform = v8::platform::CreateDefaultPlatform();
V8::InitializePlatform(platform);
v8::V8::Initialize();
v8::Isolate* isolate = v8::Isolate::New(create_params);
v8::Isolate::Scope isolate_scope(isolate);
v8::HandleScope handle_scope(isolate);
// 建立全局 global 對象模板,根據模板建立 JS 運行上下文,在上下文 context 中獲取 global 對象
v8::Local <v8::ObjectTemplate> global_template = v8::ObjectTemplate::New(isolate);
v8::Local <v8::Context> context = v8::Context::New(isolate, nullptr, global_template);
v8::Context::Scope context_scope(context);
v8::Local<v8::Object> global = context->Global();
// 建立 example 的構造函數模板, 使用該 c++ 類的初始化函數做爲參數(函數鉤子),初始化構造器函數模
// 板。即當調用構造函數建立對象時,會調用該鉤子函數作構造處理
v8::Local<v8::FunctionTemplate> example_tpl = v8::FunctionTemplate::New(isolate);
// 設置構造函數模板的類名
example_tpl->SetClassName(V8Helper::ConvertToV8String(isolate, "Example"));
// 設置內部關聯 c++ 對象的數量爲 1
example_tpl->InstanceTemplate()->SetInternalFieldCount(1);
// 設置構造函數模板中的原型模板的對應函數名的鉤子
example_tpl->PrototypeTemplate()->Set(V8Helper::ConvertToV8String(isolate, "testStatic"),
v8::FunctionTemplate::New(isolate, TestStaticCallback));
// 設置構造函數模板中的原型模板的屬性的 Get 和 Set 鉤子方法
example_tpl->PrototypeTemplate()->SetAccessor(V8Helper::ConvertToV8String(isolate, "num"), GetNumCallback, SetNumCallback);
// 在函數模板中獲取可調用的函數
v8::Local<v8::Function> example_constructor = example_tpl->GetFunction(context).ToLocalChecked();
// 調用函數的建立對象的方法,建立 JS 引擎的 example 對象
v8::Local<v8::Object> example =
example_constructor->NewInstance(context, 0, nullptr).ToLocalChecked();
// 關聯 JS 引擎對象和 c++ 對象
handle->SetAlignedPointerInInternalField(0, this);
// 動態註冊函數鉤子
v8::Local<v8::Function> dynamic_test_func = v8::FunctionTemplate::New(TestDynamicCallback, args.Data())->GetFunction();
v8::Local<v8::String> dynamic_test_name = v8::String::NewFromUtf8(isolate, "testDynamic", v8::NewStringType::kNormal).ToLocalChecked();
dynamic_test_func->SetName(dynamic_test_name);
example->Set(dynamic_test_name, dynamic_test_func);
// 向 global 對象中設置 example 屬性
v8::Local<v8::String> example_v8_str = v8::String::NewFromUtf8(isolate, "example", v8::NewStringType::kNormal).ToLocalChecked()
global->Set(context, example_v8_str, example).FromJust();
}
複製代碼
初始化流程具體代碼
// example.test() 調用時對應的 c++ 函數鉤子
static JSValueRef TestStaticCallback(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) {
// 獲取 JS 引擎對象中持有的 c++ 對象
Example* example = static_cast<Example*>(JSObjectGetPrivate(object));
example->TestStatic();
}
// example.testDynamic() 調用時對應的 c++ 函數鉤子
static JSValueRef TestDynamicCallback(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) {
Example* example = static_cast<Example*>(JSObjectGetPrivate(object));
example->TestDynamic();
}
// console.log(example.num) 調用時對應觸發的 c++ 鉤子函數
static JSValueRef GetNumCallback(JSContextRef ctx, JSObjectRef object, JSStringRef propertyName, JSValueRef* exception) {
Example* example = static_cast<Example*>(JSObjectGetPrivate(object));
int num = obj->num();
return JSValueMakeNumber(ctx, num);
}
// example.num = 99 時會觸發該的 c++ 函數鉤子
static bool SetNumCallback(JSContextRef ctx, JSObjectRef object, JSStringRef propertyName, JSValueRef value, JSValueRef* exception) {
Example* example = static_cast<Example*>(JSObjectGetPrivate(object));
obj->set_num(JSValueToNumber(ctx, value, NULL));
}
// 定義將要 Extension 的靜態方法,其中包含函數鉤子
static JSStaticFunction s_examplle_function_[] = {
{"testStatic", TestStaticCallback, kJSClassAttributeNone},
{ 0, 0, 0 }
};
// 定義將要 Extension 的變量,其中包含 get 和 set 函數鉤子
static JSStaticValue s_example_values_[] = {
{"num", GetNumCallback, SetNumCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
{ 0, 0, 0, 0 }
};
void Example::Initialize(JSVM* vm, Runtime* runtime) {
// 初始化 JSC 引擎
JSContextGroupRef context_group = JSContextGroupCreate();
// 建立全局 global 類的定義
JSClassDefinition global_class_definition = kJSClassDefinitionEmpty;
// 建立 global 對象的類
JSClassRef global_class = JSClassCreate(&global_definition);
// 根據 global 類建立上下文,從上下文獲取 global 對象
JSContextRef context = JSGlobalContextCreateInGroup(context_group, global_class);
JSObjectRef global = JSContextGetGlobalObject(context);
// 建立 example 類的定義
JSClassDefinition example_class_definition = kJSClassDefinitionEmpty;
// 設置類的對應函數名和參數名的鉤子
example_class_definition.staticFunctions = s_console_function_;
example_class_definition.staticValues = s_console_values_;
// 設置類的名稱
example_class_definition.className = "Example";
// 建立 JS 引擎的類
JSClassRef example_class_ref = JSClassCreate(&example_class_definition);
JSObjectRef example = JSObjectMake(context, example_class_ref, NULL);
// 關聯 c++ 對象和 JS 引擎對象
JSObjectSetPrivate(context, example, this)
JSClassRelease(example_class_ref);
// 動態註冊函數鉤子
JSStringRef dynamic_test_func_name = JSStringCreateWithUTF8CString("testDynamic");
JSObjectRef dynamic_test_func = JSObjectMakeFunctionWithCallback(context, dynamic_test_func_name, TestDynamicCallback);
JSObjectSetProperty(context, example, dynamic_test_func_name, dynamic_test_func, kJSPropertyAttributeDontDelete, NULL);
JSStringRelease(dynamic_test_func_name);
// 向 global 對象中設置 example 屬性
JSStringRef example_str_ref = JSStringCreateWithUTF8CString("example");
JSObjectSetProperty(context, global, example_str_ref, example, kJSPropertyAttributeDontDelete, NULL);
JSStringRelease(example_str_ref);
}
複製代碼