OpenHarmony应用启动过程

2024-08-09 16:14:32 浏览数 (5)

本文基于 OpenHarmony 源码梳理应用的启动过程,介绍 appspawn/ability_runtime/ace_engine/ets_runtime 等重要模块的初始化流程,以及它们之间的相互关系。

不同形态的 hap 应用在具体细节上会有一些差异,但整体的流程上是一致的。本文基于 OpenHarmoney 3.2 标准系统 FA 模式的 ets 应用进行阐述。

应用启动整体流程

查看各个进程的父子关系可知,OpenHarmony 的系统应用和用户应用进程,都是由应用孵化器(appspawn)拉起的。

应用启动的整理流程如下图所示:

说明:应用启动时,appspawn 进程会 fork 出一个应用子进程,创建 AceAbility 实现类和 AceContainer。

AceContainer 初始化过程中会在 JS 线程中创建 JS 运行环境,包括 JsEngine、NativeEngin、ArkJSRuntime、JSThread、EcmaVM 等重要组件。

启动流程详解

appspawn 创建应用进程:

应用日志:

代码语言:shell复制
08-05 17:58:11.955 255-255/appspawn I C02c11/APPSPAWN: [appspawn_service.c:408]child process com.example.myapplication success pid 2345

关键代码流程:

代码语言:shell复制
// basestartupappspawnstandardappspawn_service.cAPPSPAWN_STATIC void OnReceiveRequest(const TaskHandle taskHandle, const uint8_t *buffer, uint32_t buffLen)    AppSpawnProcessMsg(sandboxArg, &appProperty->pid);    // base/startup/appspawn/common/appspawn_server.c    int AppSpawnProcessMsg(AppSandboxArg *sandbox, pid_t *childPid)        if (client->cloneFlags & CLONE_NEWPID) {            pid = clone(AppSpawnChild, childStack   SANDBOX_STACK_SIZE, client->cloneFlags | SIGCHLD, (void *)sandbox);        pid = fork();  // fork出应用进程        *childPid = pid;        if (pid == 0) { // 子进程流程执行            AppSpawnChild((void *)sandbox);            int AppSpawnChild(void *arg)                struct AppSpawnContent_ *content = sandbox->content;                DoStartApp(content, client, content->longProcName, content->longProcNameLen);                    // notify success to father process and start app process                    NotifyResToParent(content, client, 0);                content->runChildProcessor(content, client); // 进入应用主线程 (ability_runtime 的 MainThread)        }

应用主线程初始化 Ability:

应用的整体状态流转是由 Ability 实例对象来控制完成的。因此应用进程拉起时,会先创建出 Ability。

不同的应用模型在这里会创建不同的实例类型:

代码语言:shell复制
// foundationabilityability_runtimeframeworksnativeabilitynativeability_impl_factory.cpp// AbilityImplFactory::MakeAbilityImplObject() 方法:switch (info->type) {        case AppExecFwk::AbilityType::PAGE:            if (info->isStageBasedModel) {                abilityImpl = std::make_shared<NewAbilityImpl>();            } else {                abilityImpl = std::make_shared<PageAbilityImpl>();            }            break;        case AppExecFwk::AbilityType::SERVICE:            abilityImpl = std::make_shared<ServiceAbilityImpl>();            break;        case AppExecFwk::AbilityType::DATA:            abilityImpl = std::make_shared<DataAbilityImpl>();            break;

AbilityImpl 实例创建后,应用开始进入 Start 状态,触发 AceAbility::OnStart() 回调。在该回调中,会创建 JS 运行环境。

③AceContainer 初始化

AceContainer 初始化可分为两个阶段:第一个阶段创建 JS 运行时环境(js_engine, native_engine, ets_runtime);第二个阶段调度 js_engine 开始读取 js 字节码文件(xxx.abc)。

阶段一:创建 JS 运行时环境

这里的代码流程比较长… 具体调用过程见上图说明。讲几个主要的点:

AceContianer 初始化时会创建一个任务执行线程 FlutterTaskExecutor,这就是后续 js 代码的执行线程。应用主线程把需要在js线程中执行的代码包装成 task,放到 FlutterTaskExecutor 中去执行。

创建 Js 引擎时可以选择不同的引擎类型,这是在源码编译阶段由宏开关控制的。

代码语言:shell复制
foundationarkuiace_engineframeworksbridgedeclarative_frontendenginedeclarative_engine_loader.cpp

代码语言:shell复制
RefPtr<JsEngine> DeclarativeEngineLoader::CreateJsEngine(int32_t instanceId) const{#ifdef USE_V8_ENGINE    return AceType::MakeRefPtr<V8DeclarativeEngine>(instanceId);#endif#ifdef USE_QUICKJS_ENGINE    return AceType::MakeRefPtr<QJSDeclarativeEngine>(instanceId);#endif#ifdef USE_ARK_ENGINE    return AceType::MakeRefPtr<JsiDeclarativeEngine>(instanceId);#endif}

宏开关在如下配置文件中定义:

代码语言:shell复制
foundation/arkui/ace_engine/adapter/ohos/build/config.gni

代码语言:shell复制
engine_defines = [ "USE_ARK_ENGINE" ]

③ArkNativeEngine 初始化时创建了 NAPI 层的各个重要组件(moduleManager, scopeManager, referenceManager, loop…)

ArkNativeEngine 向 js 运行环境中注册了一个"requireNapi()"方法,该方法是 js 应用 import 各种 NAPI 库的入口。

js 代码中的"import xxxx"在 hap 包编译阶段会改写为“requireNapi(xxx)”。

当这行代码被 js 引擎解释执行时,即会调用到 ArkNativeEngine 中注册的 requireNapi c 实现代码,通过 NAPI 的 ModuleManager 模块完成 xxxNAPI 模块 lib 库的加载。

阶段二:读取并执行 js 字节码文件

在 AceContainer::RunPage() 流程中,会依次创建两个 js 线程的 task,分别读取 app.abc 和 index.abc 文件。

细节 1:JsiDeclarativeEngine::LoadJs() 方法中是根据传入的 .js 文件名去读取对应的 .abc。

代码语言:shell复制
// foundationarkuiace_engineframeworksbridgedeclarative_frontendenginejsijsi_declarative_engine.cppvoid JsiDeclarativeEngine::LoadJs(const std::string& url, const RefPtr<JsAcePage>& page, bool isMainPage)    ...    const char js_ext[] = ".js";    const char bin_ext[] = ".abc";    auto pos = url.rfind(js_ext);    std::string urlName = url.substr(0, pos)   bin_ext; // 将文件名 xxx.js 替换成 xxx.abc

细节 2:EcmaVM::InvokeEcmaEntrypoint() 方法中会执行 index.abc 中的入口函数 func_main_0,该函数在原始的 index.js 文件中并没有,是 hap 包编译后生成在 index.abc 文件中的。

写在最后

如果你觉得这篇内容对你还蛮有帮助,我想邀请你帮我三个小忙