本文基于 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 文件中的。
写在最后
如果你觉得这篇内容对你还蛮有帮助,我想邀请你帮我三个小忙:
- 点赞,转发,有你们的 『点赞和评论』,才是我创造的动力;
- 关注小编,同时可以期待后续文章ing