OpenHarmony 源码解析之ACE (JavaScript运行环境初始化) 原创 精华

发布于 2021-8-23 14:13
浏览
23收藏

作者:张亮亮

1 简介

JS UI框架ACE全称Ability Cross-platform Environment,是OpenHarmony标准系统上的UI框架。

ACE: 结合了OpenHarmony系统的基础组件Ability,开源jsframework框架,开源js引擎quickjs,开源跨平台UI框架flutter,开源渲染引擎skia以及各种平台能力API等共同构筑了OpenHarmony标准系统javacript应用开发的基础。

1.1 OpenHarmony 架构图

OpenHarmony 源码解析之ACE (JavaScript运行环境初始化)-开源基础软件社区

1.2 ACE UI框架图

OpenHarmony 源码解析之ACE (JavaScript运行环境初始化)-开源基础软件社区

1.3 ACE 主要构成

  1. JavaScript前端框架

    目前OpenHarmony标准系统采用主流的类Web范式,对开源的Weex框架中的jsframework做定制化,采用ts开发,主要包括编程模型MVVM,组件,API,页面路由以及事件处理。

  2. JavaScript引擎

    目前OpenHarmony标准系统使用的是开源quickjs引擎,提供JS语言运行时和执行上下文,提供js的解析和jsframework的加载。

  3. 中间转换层

    中间转换层也就是JS桥接层,实现前端开发框架到UI后端引擎和JS引擎的对接。

  4. 声明式UI后端引擎

    由C++构建的UI后端引擎,包括UI组件、布局视图、动画事件、自绘制选软管线。

  5. 渲染引擎

    目前OpenHarmony标准系统复用了开源跨平台UI框架flutter引擎提供基础的图形渲染能力。

  6. 平台适配层

    目前OpenHarmony标准系统的适配层完成了ohos平台和IDE的previewer的适配,将平台依赖聚焦到平台相关的画布、通用线程以及事件处理机制等少数接口上,为跨平台提供相应的基础设施,实现跨平台一致化的UI渲染。

  7. 能力扩展层

    为扩展ACE能力提供的插件机制,平台其他子系统可以利用插件机制开发相关能力的js接口,为应用层提供相应的能力支持。通过napi提供引擎无关的插件实现机制,保证接口的ABI兼容性。

2 基础知识

2.1 代码结构

/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提供跨平台自渲染引擎

2.2 ACE框架类图

OpenHarmony 源码解析之ACE (JavaScript运行环境初始化)-开源基础软件社区

2.3 线程模型

ACE JS应用启动时会创建一系列线程,形成独立的线程模型,以实现高性能的渲染流程。

  • **Platform线程:**当前平台的主线程,也就是应用的主线程,主要负责平台层的交互、应用生命周期以及窗口环境的创建
  • **JS线程:**JS前端框架的执行线程,应用的JS逻辑以及应用UI界面的解析构建都在该线程执行
  • **UI线程:**引擎的核心线程,组件树的构建以及整个渲染管线的核心逻辑都在该线程:包括渲染树的构建、布局、绘制以及动画调度
  • **GPU线程:**现代的渲染引擎,为了充分发挥硬件性能,都支持GPU硬件加速,在该线程上,会通过系统的窗口句柄,创建GPU加速的OpenGL环境,负责将整个渲染树的内容光栅化,直接将每一帧的内容渲染合成到该窗口的Surface上并送显
  • **IO线程:**主要为了异步的文件IO读写,同时该线程会创建一个离屏的GL环境,这个环境和 GPU线程的GL环境是同一个共享组,可以共享资源,图片资源解码的内容可直接在该线程上传生成GPU纹理,实现更高效的图片渲染

3 Javascript运行环境初始化

3.1 时序图

OpenHarmony 源码解析之ACE (JavaScript运行环境初始化)-开源基础软件社区

3.2 源码解析

  1. OnStart()

    • AceAbility继承自Ability,当应用启动时首先应用程序框架会调用AceAbility的生命周期函数OnStart();

    • 通过Ability的api获取Hap包的路径,通过读取配置文件manifest.json获取应用程序配置的前端的类型;

    • 当前我们只关注JS前端,目前支持的前端类型包括enum class FrontendType { JSON, JS, JS_CARD, DECLARATIVE_JS }

    • 根据前端类型调用ACE核心聚合类AceContainer的静态方法CreateContainer,这个类是ACE框架平台适配层的核心类,接下来的前端核心类和JS引擎的创建都是在其中完成的。

//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
    //创建AceContainer:内部初始化时创建了js线程,load了js engine,创建了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));

    ...
}
  1. CreateContainer
    • 在AceContainer::CreateContainer中,首先创建了AceContainer对象;
    • 在构造函数中创建了FlutterTaskerExecutor对象用于多线程的任务管理;
    • 此处主要关注JS线程的创建和初始化,在InitJsThread()中创建了JS线程并获取保存了jsRunner_用于JS任务的派发;
//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);
    //创建和初始化FlutterTaskerExecutor用于封装管理Flutter ACE中涉及的多个线程:platform,UI,JS,GPU,IO,统一post任务到各线程执行
    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)
{
    //创建并初始化JS线程,获取保存js线程的TaskRunner
    //JS线程是ACE平台特有,不通过flutter创建管理
    if (newThread) {
        jsThread_ = std::make_unique<fml::Thread>(GenJsThreadName());
        jsRunner_ = jsThread_->GetTaskRunner();
    } else {
        jsRunner_ = uiRunner_;
    }
}
  1. JsFrontend

    • 完成JS线程的初始化后,如果前端类型不是DECLARATIVE_JS,会调用InitializeFrontend()对前端进行初始化。

    • 首先创建前端对象,Frontend::Create定义在js_frontend.cpp中,创建的是JsFrontend实例;

    • 然后通过JsEngineLoader::Get()动态加载QjsEngineLoader;

    • 再通过QjsEngineLoader创建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中,创建的是JsFrontend实例
        frontend_ = Frontend::Create();
        auto jsFrontend = AceType::DynamicCast<JsFrontend>(frontend_);
        //创建并初始化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_);
}
  1. QjsEngine

    • 接下来我们继续分析一下JS引擎管理对象的创建;

    • 首先通过dlopen动态加载libace_engine_qjs.z.so通过入口函数创建获取QjsEngineLoader单例对象;

    • 然后通过QjsEngineLoader::CreateJsEngine()创建QjsEngine;

//foundation\ace\ace_engine\frameworks\bridge\js_frontend\engine\common\js_engine_loader.cpp
//"libace_engine_qjs.z.so"动态库的入口,在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;
}
...
//通过加载动态链接库的形式获取qjs桥接模块的入口函数并创建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);
}
  1. JS线程

    • 在完成了QjsEngine的创建并设置给JsFrontend后,调用JsFrontend::Initialize();

    • 这里主要完成了FrontendDelegateImpl对象的创建和初始化将对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);
    //创建并初始化FrontendDelegate对象,具体实现为FrontendDelegateImpl
    InitializeFrontendDelegate(taskExecutor);
    //在JS线程初始化js engine,真正的启动JS引擎运行时并创建上下文
    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;
}
  1. jsframework

    • 在JS线程执行QjsEngine对象的初始化,初始化JS运行环境;

    • 初始化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)执行相应的函数
    //在这里最终调用的函数是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;
}
  1. ACE模块

    • 初始化JS引擎运行时上下文是在InitJsContext完成;

    • 其中初始化Ace模块并将模块导入JS运行时上下文中,为jsframework框架层提供了ACE功能相关的接口;

    • jsframework可以通过调用ACE模块的接口完成Page上Dom元素到后端声明式UI元素节点的创建;

    • 同时往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;
}
  1. 初始化完成

    • 完成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;
    };
  }
}

4 总结

以上内容首先对OpenHarmony标准系统上JS UI框架ACE的逻辑架构及相关模块进行了简单的介绍,给出了ACE架构中相关模块涉及的部分类的类图,结合本次重点分析的Javascript运行环境初始化的时序图详细分析了Javascript运行环境初始化的整个流程。

更多原创内容请关注:开鸿 HarmonyOS 学院

入门到精通、技巧到案例,系统化分享HarmonyOS开发技术,欢迎投稿和订阅,让我们一起携手前行共建鸿蒙生态。

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
已于2021-11-17 15:28:08修改
23
收藏 23
回复
举报
回复
添加资源
添加资源将有机会获得更多曝光,你也可以直接关联已上传资源 去关联
    相关推荐