OpenHarmony ACE源碼解析之JavaScript運行環(huán)境初始
OpenHarmony JS UI框架簡介
ACE全稱Ability Cross-platform Environment,是OpenHarmony標準系統(tǒng)上的UI框架。ACE結合了OpenHarmony系統(tǒng)的基礎組件Ability,開源jsframework框架,開源js引擎quickjs,開源跨平臺UI框架flutter,開源渲染引擎skia以及各種平臺能力API等共同構筑了OpenHarmony標準系統(tǒng)javascript應用開發(fā)的基礎。
ACE UI框架的整體架構如下圖所示:
JavaScript前端框架
目前OpenHarmony標準系統(tǒng)采用主流的類Web范式,對開源的Weex框架中的jsframework做定制化,采用ts開發(fā),主要包括編程模型MVVM、組件、API、頁面路由以及事件處理。
https://gitee.com/openharmony/third_party_jsframework
JavaScript引擎
目前OpenHarmony標準系統(tǒng)使用的是開源quickjs引擎,提供JS語言運行時和執(zhí)行上下文,提供js的解析和jsframework的加載。
https://gitee.com/openharmony/third_party_quickjs
中間轉換層
中間轉換層也就是JS橋接層,實現前端開發(fā)框架到UI后端引擎和JS引擎的對接。
https://gitee.com/openharmony/ace_ace_engine/tree/master/frameworks/bridge
聲明式UI后端引擎
由C++構建的UI后端引擎,包括UI組件、布局視圖、動畫事件、自繪制選軟管線。
https://gitee.com/openharmony/ace_ace_engine/tree/master/frameworks/core
渲染引擎
目前OpenHarmony標準系統(tǒng)復用了開源跨平臺UI框架flutter引擎提供基礎的圖形渲染能力。
https://gitee.com/openharmony/third_party_flutter
平臺適配層
目前OpenHarmony標準系統(tǒng)的適配層完成了ohos平臺和IDE的previewer的適配,將平臺依賴聚焦到平臺相關的畫布、通用線程以及事件處理機制等少數接口上,為跨平臺提供相應的基礎設施,實現跨平臺一致化的UI渲染。
https://gitee.com/openharmony/ace_ace_engine/tree/master/adapter
能力擴展層
為擴展ACE能力提供的插件機制,平臺其他子系統(tǒng)可以利用插件機制開發(fā)相關能力的js接口,為應用層提供相應的能力支持。通過napi提供引擎無關的插件實現機制,保證接口的ABI兼容性。
https://gitee.com/openharmony/ace_napi
JS UI框架相關代碼目錄
- /foundation/ace/ace_engine
- ├── adapter # 平臺適配目錄
- │ ├── common
- | ├── ohos # ohos平臺適配目錄
- │ └── preview # IDE preview平臺適配目錄
- ├── frameworks # 框架代碼
- │ ├── base # 基礎庫
- │ ├── bridge # 前后端對接層
- │ └── core # 聲明式UI后端引擎目錄
- /third_party/jsframework # JavaScript前端框架
- /third_party/quickjs # JavaScript引擎
- /third_party/flutter # flutter engine提供跨平臺自渲染引擎
線程模型
ACE JS應用啟動時會創(chuàng)建一系列線程,形成獨立的線程模型,以實現高性能的渲染流程。
- Platform線程:當前平臺的主線程,也就是應用的主線程,主要負責平臺層的交互、應用生命周期以及窗口環(huán)境的創(chuàng)建
- JS線程:JS前端框架的執(zhí)行線程,應用的JS邏輯以及應用UI界面的解析構建都在該線程執(zhí)行
- UI線程:引擎的核心線程,組件樹的構建以及整個渲染管線的核心邏輯都在該線程:包括渲染樹的構建、布局、繪制以及動畫調度
- GPU線程:現代的渲染引擎,為了充分發(fā)揮硬件性能,都支持GPU硬件加速,在該線程上,會通過系統(tǒng)的窗口句柄,創(chuàng)建GPU加速的OpenGL環(huán)境,負責將整個渲染樹的內容光柵化,直接將每一幀的內容渲染合成到該窗口的Surface上并送顯
- IO線程:主要為了異步的文件IO讀寫,同時該線程會創(chuàng)建一個離屏的GL環(huán)境,這個環(huán)境和 GPU線程的GL環(huán)境是同一個共享組,可以共享資源,圖片資源解碼的內容可直接在該線程上傳生成GPU紋理,實現更高效的圖片渲染
ACE框架類圖

Javascript運行環(huán)境初始化時序圖及源碼解析

1.AceAbility繼承自Ability,當應用啟動時首先應用程序框架會調用AceAbility的生命周期函數OnStart(),其中通過Ability的api獲取Hap包的路徑,通過讀取配置文件manifest.json獲取應用程序配置的前端的類型;
目前支持的前端類型包括enum class FrontendType { JSON, JS, JS_CARD, DECLARATIVE_JS },當前我們只關注JS前端;
根據前端類型調用ACE核心聚合類AceContainer的靜態(tài)方法CreateContainer,這個類是ACE框架平臺適配層的核心類,接下來的前端核心類和JS引擎的創(chuàng)建都是在其中完成的。
- //foundation\ace\ace_engine\adapter\preview\entrance\ace_ability.cpp
- void AceAbility::OnStart(const Want& want)
- {
- ...
- //獲取打包的js bundle文件,取出配置文件,獲取前端類型
- auto packagePathStr = GetBundleCodePath();
- auto moduleInfo = GetHapModuleInfo();
- if (moduleInfo != nullptr) {
- packagePathStr += "/" + moduleInfo->name + "/";
- }
- FrontendType frontendType = GetFrontendTypeFromManifest(packagePathStr);
- // create container
- //創(chuàng)建AceContainer:內部初始化時創(chuàng)建了js線程,load了js engine,創(chuàng)建了JsFrontend
- Platform::AceContainer::CreateContainer(
- abilityId_, frontendType, this,
- std::make_unique<AcePlatformEventCallback>([this]() {
- TerminateAbility();
- }));
- ...
- ...
- //運行page
- Platform::AceContainer::RunPage(
- abilityId_, Platform::AceContainer::GetContainer(abilityId_)->GeneratePageId(),
- parsedPageUrl, want.GetStringParam(START_PARAMS_KEY));
- ...
- }
2.在AceContainer::CreateContainer中,首先創(chuàng)建了AceContainer對象,在構造函數中創(chuàng)建了FlutterTaskerExecutor對象用于多線程的任務管理,此處我們主要關注JS線程的創(chuàng)建和初始化,在InitJsThread()中創(chuàng)建了JS線程并獲取保存了jsRunner_用于JS任務的派發(fā)。
- //foundation\ace\ace_engine\adapter\preview\entrance\ace_container.cpp
- void AceContainer::CreateContainer(int32_t instanceId, FrontendType type, AceAbility* aceAbility,
- std::unique_ptr<PlatformEventCallback> callback)
- {
- auto aceContainer = AceType::MakeRefPtr<AceContainer>(instanceId, type, aceAbility, std::move(callback));
- AceEngine::Get().AddContainer(instanceId, aceContainer);
- auto front = aceContainer->GetFrontend();
- if (front) {
- front->UpdateState(Frontend::State::ON_CREATE);
- front->SetJsMessageDispatcher(aceContainer);
- }
- }
- AceContainer::AceContainer(int32_t instanceId, FrontendType type, AceAbility* aceAbility,
- std::unique_ptr<PlatformEventCallback> callback) : instanceId_(instanceId), type_(type), aceAbility_(aceAbility)
- {
- ACE_DCHECK(callback);
- //創(chuàng)建和初始化FlutterTaskerExecutor用于封裝管理Flutter ACE中涉及的多個線程:platform,UI,JS,GPU,IO,統(tǒng)一post任務到各線程執(zhí)行
- auto flutterTaskExecutor = Referenced::MakeRefPtr<FlutterTaskExecutor>();
- //初始化platform線程,將flutter platform線程的TaskerRunner適配到ohos平臺主線程的EventRunner
- flutterTaskExecutor->InitPlatformThread();
- //初始化JS線程,這個線程用于解析JS,不歸flutter管理,因此是單獨在ACE里使用的
- flutterTaskExecutor->InitJsThread();
- //taskExector_封裝了所有線程任務調度的接口,因此會傳給Frontend用于JS前端解析任務和PipelineContext后端渲染UI和GPU IO相關
- taskExecutor_ = flutterTaskExecutor;
- if (type_ != FrontendType::DECLARATIVE_JS) {
- //zll:初始化前端
- InitializeFrontend();
- }
- platformEventCallback_ = std::move(callback);
- }
- void FlutterTaskExecutor::InitJsThread(bool newThread)
- {
- //創(chuàng)建并初始化JS線程,獲取保存js線程的TaskRunner
- //JS線程是ACE平臺特有,不通過flutter創(chuàng)建管理
- if (newThread) {
- jsThread_ = std::make_unique<fml::Thread>(GenJsThreadName());
- jsRunner_ = jsThread_->GetTaskRunner();
- } else {
- jsRunner_ = uiRunner_;
- }
- }
3.完成JS線程的初始化后,如果前端類型不是DECLARATIVE_JS,會調用InitializeFrontend()對前端進行初始化。
首先創(chuàng)建前端對象,Frontend::Create定義在js_frontend.cpp中,創(chuàng)建的是JsFrontend實例;
然后通過JsEngineLoader::Get()動態(tài)加載QjsEngineLoader;
再通過QjsEngineLoader創(chuàng)建QjsEngine并設置給JsFrontend;
最后對JsFrontend對象做初始化。
JsFrontend是ACE框架從后端進入前端的唯一入口,AceAbility、AceContainer和JsFrontend是一一對應的關系。
- //foundation\ace\ace_engine\adapter\preview\entrance\ace_container.cpp
- void AceContainer::InitializeFrontend()
- {
- if (type_ == FrontendType::JS) {
- //目前Frontend::Create定義在js_frontend.cpp中,創(chuàng)建的是JsFrontend實例
- frontend_ = Frontend::Create();
- auto jsFrontend = AceType::DynamicCast<JsFrontend>(frontend_);
- //創(chuàng)建并初始化js engine,此處是通過dlopen加載的qjs engine管理對象
- jsFrontend->SetJsEngine(Framework::JsEngineLoader::Get().CreateJsEngine(instanceId_));
- ...
- } else if (type_ == FrontendType::DECLARATIVE_JS) {
- ...
- } else {
- ...
- }
- ACE_DCHECK(frontend_);
- //初始化前端和js engine
- frontend_->Initialize(type_, taskExecutor_);
- }
4.接下來我們繼續(xù)分析一下JS引擎管理對象的創(chuàng)建。首先通過dlopen動態(tài)加載libace_engine_qjs.z.so通過入口函數創(chuàng)建獲取QjsEngineLoader單例對象;然后通過QjsEngineLoader::CreateJsEngine()創(chuàng)建QjsEngine。
- //foundation\ace\ace_engine\frameworks\bridge\js_frontend\engine\common\js_engine_loader.cpp
- //"libace_engine_qjs.z.so"動態(tài)庫的入口,在qjs_engine_loader.cpp中定義
- constexpr char JS_ENGINE_ENTRY[] = "OHOS_ACE_GetJsEngineLoader";
- constexpr char QUICK_JS_ENGINE_SHARED_LIB[] = "libace_engine_qjs.z.so";
- ...
- const char* GetSharedLibrary()
- {
- return QUICK_JS_ENGINE_SHARED_LIB;
- }
- JsEngineLoader& GetJsEngineLoader(const char* sharedLibrary)
- {
- void* handle = dlopen(sharedLibrary, RTLD_LAZY);
- ...
- //
- auto loader = reinterpret_cast<JsEngineLoader*>(entry());
- ...
- return *loader;
- }
- ...
- //通過加載動態(tài)鏈接庫的形式獲取qjs橋接模塊的入口函數并創(chuàng)建QjsEngineLoader
- JsEngineLoader& JsEngineLoader::Get()
- {
- static JsEngineLoader& instance = GetJsEngineLoader(GetSharedLibrary());
- return instance;
- }
- //foundation\ace\ace_engine\frameworks\bridge\js_frontend\engine\quickjs\qjs_engine_loader.cpp
- RefPtr<JsEngine> QjsEngineLoader::CreateJsEngine(int32_t instanceId) const
- {
- return AceType::MakeRefPtr<QjsEngine>(instanceId);
- }
5.在完成了QjsEngine的創(chuàng)建并設置給JsFrontend后,調用JsFrontend::Initialize(),這里主要完成了FrontendDelegateImpl對象的創(chuàng)建和初始化將對JS引擎的相關操作委派給這個對象,以及Post JS引擎初始化的任務到JS線程的TaskRunner的message queue。
- //foundation\ace\ace_engine\frameworks\bridge\js_frontend\js_frontend.cpp
- bool JsFrontend::Initialize(FrontendType type, const RefPtr<TaskExecutor>& taskExecutor)
- {
- LOGI("JsFrontend initialize begin.");
- type_ = type;
- ACE_DCHECK(type_ == FrontendType::JS);
- //創(chuàng)建并初始化FrontendDelegate對象,具體實現為FrontendDelegateImpl
- InitializeFrontendDelegate(taskExecutor);
- //在JS線程初始化js engine,真正的啟動JS引擎運行時并創(chuàng)建上下文
- taskExecutor->PostTask(
- [weakEngine = WeakPtr<Framework::JsEngine>(jsEngine_), delegate = delegate_] {
- auto jsEngine = weakEngine.Upgrade();
- if (!jsEngine) {
- return;
- }
- jsEngine->Initialize(delegate);
- },
- TaskExecutor::TaskType::JS);
- LOGI("JsFrontend initialize end.");
- return true;
- }
6.接著上面的分析,在JS線程執(zhí)行QjsEngine對象的初始化,初始化JS運行環(huán)境,主要包含兩部分初始化JS引擎運行時上下文和初始化JavaScript框架層jsframework。
- //foundation\ace\ace_engine\frameworks\bridge\js_frontend\engine\quickjs\qjs_engine.cpp
- bool QjsEngine::Initialize(const RefPtr<FrontendDelegate>& delegate)
- {
- ACE_SCOPED_TRACE("QjsEngine::Initialize");
- LOGI("Initialize");
- JSRuntime* runtime = nullptr;
- JSContext* context = nullptr;
- // put JS_NewContext as early as possible to make stack_top in context
- // closer to the top stack frame pointer of JS thread.
- runtime = JS_NewRuntime();
- if (runtime != nullptr) {
- context = JS_NewContext(runtime);
- }
- ...
- engineInstance_ = AceType::MakeRefPtr<QjsEngineInstance>(delegate, instanceId_);
- return engineInstance_->InitJsEnv(runtime, context);
- }
- bool QjsEngineInstance::InitJsEnv(JSRuntime* runtime, JSContext* context)
- {
- ...
- context_ = context;
- //1.初始化js運行時,上下文
- if (!InitJsContext(context_, MAX_STACK_SIZE, instanceId_, this)) {
- LOGE("Qjs cannot allocate JS context");
- EventReport::SendJsException(JsExcepType::JS_ENGINE_INIT_ERR);
- JS_FreeRuntime(runtime_);
- return false;
- }
- ...
- //2.加載JS Framework,初始化JS前端框架
- //加載jsframework,js_framework和js_framework_size是quickjs編譯器編譯jsframework的ts生成的c文件
- //quickjs通過JS_ReadObject讀取生成的cbytecode,并通過JS_EvalFunction(ctx, obj)執(zhí)行相應的函數
- //在這里最終調用的函數是jsframework/runtime/preparation/index.ts中的initFramework()函數
- JSValue retVal = LoadJsFramework(GetQjsContext(), js_framework, js_framework_size, instanceId_);
- bool result = JS_IsException(retVal) ? false : true;
- if (context) {
- JS_FreeValue(context, retVal);
- }
- ...
- return result;
- }
7.上面初始化JS引擎運行時上下文是在InitJsContext完成的,其中初始化了Ace模塊并將模塊導入到了JS運行時上下文中,為jsframework框架層提供了ACE功能相關的接口,jsframework可以通過調用ACE模塊的接口完成Page上Dom元素到后端聲明式UI元素節(jié)點的創(chuàng)建,同時往JS運行時上下文全局對象掛載了日志打印的函數用于對接平臺日志打印功能。
- //foundation\ace\ace_engine\frameworks\bridge\js_frontend\engine\quickjs\qjs_engine.cpp
- //ace模塊向js context暴露的函數,(js函數名,參數個數,對應的C函數)
- const JSCFunctionListEntry JS_ACE_FUNCS[] = {
- JS_CFUNC_DEF_CPP("domCreateBody", 5, JsDomCreateBody),
- JS_CFUNC_DEF_CPP("domAddElement", 9, JsDomAddElement),
- JS_CFUNC_DEF_CPP("updateElementAttrs", 3, JsUpdateElementAttrs),
- JS_CFUNC_DEF_CPP("updateElementStyles", 3, JsUpdateElementStyles),
- JS_CFUNC_DEF_CPP("onCreateFinish", 0, JsOnCreateFinish),
- JS_CFUNC_DEF_CPP("onUpdateFinish", 0, JsOnUpdateFinish),
- JS_CFUNC_DEF_CPP("removeElement", 2, JsRemoveElement),
- JS_CFUNC_DEF_CPP("callNative", 1, JsCallNative),
- JS_CFUNC_DEF_CPP("callComponent", 3, JsCallComponent),
- JS_CFUNC_DEF_CPP("loadIntl", 0, JsLoadIntl),
- JS_CFUNC_DEF_CPP("loadLocaleData", 0, JsLoadLocaleData),
- #ifdef ENABLE_JS_DEBUG
- JS_CFUNC_DEF_CPP("compileAndRunBundle", 4, JsCompileAndRunBundle),
- #endif
- };
- ...
- bool InitJsContext(JSContext* ctx, size_t maxStackSize, int32_t instanceId, const QjsEngineInstance* qjsEngineInstance)
- {
- ...
- //將ace模塊注入到上下文中,使得jsframework可以通過注冊的接口調用ace的相關功能
- // Inject ace native functions module
- InitAceModules(ctx);
- JSValue globalObj, perfUtil;
- globalObj = JS_GetGlobalObject(ctx);
- perfUtil = JS_NewObject(ctx);
- InitJsConsoleObject(ctx, globalObj);
- JS_SetPropertyStr(ctx, perfUtil, "printlog", JS_NewCFunction(ctx, JsPerfPrint, "printlog", 0));
- JS_SetPropertyStr(ctx, perfUtil, "sleep", JS_NewCFunction(ctx, JsPerfSleep, "sleep", 1));
- JS_SetPropertyStr(ctx, perfUtil, "begin", JS_NewCFunction(ctx, JsPerfBegin, "begin", 1));
- JS_SetPropertyStr(ctx, perfUtil, "end", JS_NewCFunction(ctx, JsPerfEnd, "end", 1));
- JS_SetPropertyStr(ctx, globalObj, "perfutil", perfUtil);
- ...
- JSValue hiView;
- hiView = JS_NewObject(ctx);
- JS_SetPropertyStr(ctx, hiView, "report", JS_NewCFunction(ctx, JSHiViewReport, "report", 2));
- JS_SetPropertyStr(ctx, globalObj, "hiView", hiView);
- JSValue i18nPluralRules;
- i18nPluralRules = JS_NewObject(ctx);
- JS_SetPropertyStr(ctx, i18nPluralRules, "select", JS_NewCFunction(ctx, JsPluralRulesFormat, "select", 1));
- JS_SetPropertyStr(ctx, globalObj, "i18nPluralRules", i18nPluralRules);
- //將ace模塊導入到cxt指定的js 上下文中
- const char* str = "import * as ace from 'ace';\n"
- "var global = globalThis;\n"
- "global.ace = ace;\n"
- #ifdef ENABLE_JS_DEBUG
- "global.compileAndRunBundle = ace.compileAndRunBundle;\n"
- #endif
- "global.callNative = ace.callNative;\n";
- if (JS_CALL_FAIL == CallEvalBuf(ctx, str, strlen(str), "<input>", JS_EVAL_TYPE_MODULE, instanceId)) {
- LOGW("Qjs created JS context but failed to init Ace modules!");
- }
- JS_FreeValue(ctx, globalObj);
- return true;
- }
8.完成了JS運行時上下文的初始化之后,緊接著加載初始化了jsframework,為JS應用程序提供javascript應用框架。這個地方使用了quickjs的運行bytecode的方法,在編譯時通過qjsc(quickjs編譯器)將jsframework編譯成c文件,c文件的內容就是一個bytecode的組數和數組大小,指定了jsframework的入口函數,在這里對應jsframework/runtime/preparation/index.ts 中的initFramework()完成jsframework的初始化。jsframework初始化完成的工作主要包括掛載到js運行時上下文全局對象上提供給native調用的jsframework函數,注冊到jsframework的ACE提供的模塊及其中的方法和jsframework提供的與native一一對應的組件及其方法。
- //foundation\ace\ace_engine\frameworks\bridge\js_frontend\engine\quickjs\qjs_engine.cpp
- JSValue LoadJsFramework(JSContext* ctx, const uint8_t buf[], const uint32_t bufSize, int32_t instanceId)
- {
- ACE_SCOPED_TRACE("LoadJsFramework");
- LOGI("Qjs loading JS framework");
- //等同于調用jsframework/runtime/preparation/index.ts 中的initFramework()
- JSValue ret = CallReadObject(ctx, buf, bufSize, true, instanceId);
- if (JS_IsException(ret)) {
- LOGD("Qjs loading JSFramework failed!");
- QjsUtils::JsStdDumpErrorAce(ctx, JsErrorType::LOAD_JS_FRAMEWORK_ERROR, instanceId);
- }
- return ret;
- }
- //third_party\jsframework\runtime\preparation\init.ts
- export function initFramework(): void {
- for (const serviceName in i18n) {
- service.register(serviceName, i18n[serviceName]);
- }
- for (const serviceName in dpi) {
- service.register(serviceName, dpi[serviceName]);
- }
- //jsframework提供的可供Ace native在QjsEngine對象中調用的TS方法
- const globalMethods: GlobalInterface = {
- 'createInstance': globalApi.createInstance,
- 'registerModules': globalApi.registerModules,
- 'appDestroy': globalApi.appDestroy,
- 'appError': globalApi.appError,
- 'destroyInstance': globalApi.destroyInstance,
- 'getRoot': globalApi.getRoot,
- 'callJS': globalApi.callJS
- };
- //注冊modules,這些modules是ACE module提供的,調用這些模塊的方法,會通過調用注冊到qjs_engine的ace模塊中的callNative方法
- //會最終通過ace module的callNative調用到qjs_engine中的JsCallNative-->JsHandleModule-->然后調用對應的native方法
- // registerModules and registerComponents
- ModulesInfo.forEach(modules => {
- globalMethods['registerModules'](modules);
- });
- //注冊components組件,對組件方法的調用,最終會通過ace module的callComponent調用到qjs_engine中的JsCallComponent
- ComponentsInfo.forEach((name) => {
- if (name && name.type && name.methods) {
- NativeElementClassFactory.createNativeElementClass(
- name.type,
- name.methods
- );
- }
- });
- //全局方法,這些方法可以被Qjs engine通過JS_GetPropertyStr(ctx, globalObj, "methodNamge")獲取并調用,從而實現了從native到js的調用
- for (const methodName in globalMethods) {
- global[methodName] = (...args: any) => {
- const res: any = globalMethods[methodName](...args);
- if (res instanceof Error) {
- Log.error(res.toString());
- }
- return res;
- };
- }
- }
總結
以上內容首先對OpenHarmony標準系統(tǒng)上JS UI框架ACE的邏輯架構及相關模塊進行了簡單的介紹,給出了ACE架構中相關模塊涉及的部分類的類圖,結合本次重點分析的Javascript運行環(huán)境初始化的時序圖詳細分析了Javascript運行環(huán)境初始化的整個流程。