974 字
5 分钟
Node.js C++ / Android 嵌入器 API 及其使用

什么是 Node.js?#

Node.js 是一个免费、开源、跨平台的 JavaScript 运行时环境,它让开发人员能够创建服务器、 Web 应用、命令行工具和脚本。

Node.js 官网:Node.js

Node.js 技术架构#

作为一个异步事件驱动的 JavaScript 运行库,Node.js 旨在构建可扩展的网络应用程序。Node.js 可以同时处理许多连接。每次连接时,回调函数被触发,但如果没有工作要做,Node.js 将进入睡眠状态。

Node.js 基于 V8 JavaScript 引擎。同时,Node.js 使用 libuv 进行事件调度。V8 和 libuv 均由 C/C++ 编写 ,性能高。libuv 提供了异步 I/O 的事件调度方式,作为 Node.js 底层的事件循环框架。

下面就开始使用 Node.js C++ 嵌入器 API(Node.js C++ Embedding API):

使用 Node.js C++ 嵌入器#

1. 重定向 stdoutstderrlogcat#

由于 Node.js 默认是标准输出,所以在 Android 嵌入项目时就看不到日志,因此第一步就是将输出流重定向到 LogCat(这样 console.log 结果也能看了)。

int pipe_stdout[2];
int pipe_stderr[2];
pthread_t thread_stdout;
pthread_t thread_stderr;
const char *ADBTAG = "NodeJS"; // 可以换成你自己的 TAG
void *thread_stderr_func(void *) {
ssize_t redirect_size;
char buf[2048];
while ((redirect_size = read(pipe_stderr[0], buf, sizeof buf - 1)) > 0) {
if (buf[redirect_size - 1] == '\n')
--redirect_size;
buf[redirect_size] = 0;
__android_log_write(ANDROID_LOG_ERROR, ADBTAG, buf);
}
return 0;
}
void *thread_stdout_func(void *) {
ssize_t redirect_size;
char buf[2048];
while ((redirect_size = read(pipe_stdout[0], buf, sizeof buf - 1)) > 0) {
if (buf[redirect_size - 1] == '\n')
--redirect_size;
buf[redirect_size] = 0;
__android_log_write(ANDROID_LOG_VERBOSE, ADBTAG, buf);
}
return 0;
}
int start_redirecting_stdout_stderr() {
setvbuf(stdout, 0, _IONBF, 0);
pipe(pipe_stdout);
dup2(pipe_stdout[1], STDOUT_FILENO);
setvbuf(stderr, 0, _IONBF, 0);
pipe(pipe_stderr);
dup2(pipe_stderr[1], STDERR_FILENO);
if (pthread_create(&thread_stdout, 0, thread_stdout_func, 0) == -1)
return -1;
pthread_detach(thread_stdout);
if (pthread_create(&thread_stderr, 0, thread_stderr_func, 0) == -1)
return -1;
pthread_detach(thread_stderr);
return 0;
}

2. 初始化 V8 和 Node.js#

初始化代码略微复杂,我们在 JNI_OnLoad 里面去注册。 首先先是重定向输出了,随后由于 Node.js 初始化需要参数,我们就给他一个。

jint JNI_OnLoad(JavaVM *vm, void *unused) {
Main::vm = vm;
JNIEnv *env;
if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {
return JNI_ERR;
}
start_redirecting_stdout_stderr();
std::vector<std::string> args = {"node"};
Main::initializationResult =
node::InitializeOncePerProcess(args, {
node::ProcessInitializationFlags::kNoInitializeV8,
node::ProcessInitializationFlags::kNoInitializeNodeV8Platform
}).release();
for (const std::string &error: Main::initializationResult->errors())
LOGE("%s: %s\n", args[0].c_str(), error.c_str());
if (Main::initializationResult->early_return() != 0) {
return Main::initializationResult->exit_code();
}
Main::platform =
node::MultiIsolatePlatform::Create(4).release();
v8::V8::InitializePlatform(Main::platform);
v8::V8::Initialize();
return JNI_VERSION_1_6;
}

通过 node::MultiIsolatePlatform::Create 函数来创建一个 node::MultiIsolatePlatform(这个 Platform 是支持运行 Worker Thread 的)。

3. 创建 Isolate 和 Context#

v8::Isolate,也就是隔离。是 v8 虚拟机的实例,它负责为 JavaScript 源码创建执行环境,管理堆栈、编译、执行、context 等所有组件。 由于我们是在 Node.js 环境下,我们可以使用 node::CreateIsolate 来创建一个基于 Node.js 标准的 v8::Isolate 实例。

Isolate::Isolate() {
allocator = node::CreateArrayBufferAllocator();
loop = uv_loop_new();
self = node::NewIsolate(
allocator,
loop,
Main::platform
);
isolateData = node::CreateIsolateData(
self,
loop,
Main::platform,
allocator
);
}

node::IsolateData 实例包含的信息可以由使用相同 v8::Isolate 的多个 node::Environment 共享。 然后是创建 Context。

Context::Context(Isolate *isolate) {
this->isolate = isolate;
v8::Isolate::Scope isolateScope(isolate->self);
v8::HandleScope handleScope(isolate->self);
self.Reset(isolate->self, node::NewContext(isolate->self));
environment = node::CreateEnvironment(
isolate->isolateData,
self.Get(isolate->self),
Main::initializationResult->args(),
Main::initializationResult->exec_args(),
node::EnvironmentFlags::kOwnsProcessState
);
}

请注意,创建 Context 的时候必须使用 v8::Isolate::Scopev8::HandleScope 限定 v8::Isolate 的范围,否则程序将会崩溃。 随后这里通过 node::NewContext 来创建一个基于 Node.js 标准的 v8::Local<v8::Context> 实例。 随后是创建 node::Environment,这里传入 node::EnvironmentFlags::kOwnsProcessState 这个 Flag 是允许实例更改进程状态(比如使 process.setuid 等等可用,而不是禁用),另外是允许创建多个 node::Environment 实例。

4. 编译 & 运行 JavaScript 代码#

通过 v8::Script::Compile 编译 JavaScript 代码,再通过 v8::Script::Run 运行它:

extern "C"
JNIEXPORT void JNICALL
Java_com_mucheng_nodejava_core_Context_nativeEvaluateScript(JNIEnv *env, jobject thiz,
jstring script) {
Context *context = Context::From(thiz);
Isolate *isolate = context->isolate;
v8::Isolate::Scope isolateScope(isolate->self);
v8::HandleScope handleScope(isolate->self);
v8::Context::Scope contextScope(context->self.Get(isolate->self));
v8::TryCatch tryCatch(isolate->self);
SetProcessExitHandler(context->environment, [](node::Environment *environment, int exitCode) {
Stop(environment, node::StopFlags::kDoNotTerminateIsolate);
LOGE("Process ExitCode: %d", exitCode);
});
v8::MaybeLocal<v8::Script> compiling = v8::Script::Compile(context->self.Get(isolate->self),
v8::String::NewFromUtf8(
isolate->self,
Util::JavaStr2CStr(
script)).ToLocalChecked());
if (compiling.IsEmpty()) {
if (tryCatch.HasCaught()) {
v8::MaybeLocal<v8::Value> stackTrace = v8::TryCatch::StackTrace(
context->self.Get(isolate->self), tryCatch.Exception());
if (stackTrace.IsEmpty()) {
Util::ThrowScriptCompilingException(
*v8::String::Utf8Value(isolate->self, tryCatch.Exception()));
} else {
Util::ThrowScriptCompilingException(
*v8::String::Utf8Value(isolate->self, stackTrace.ToLocalChecked()));
}
}
return;
}
v8::MaybeLocal<v8::Value> running = compiling.ToLocalChecked()->Run(
context->self.Get(isolate->self));
if (running.IsEmpty()) {
if (tryCatch.HasCaught()) {
v8::MaybeLocal<v8::Value> stackTrace = v8::TryCatch::StackTrace(
context->self.Get(isolate->self), tryCatch.Exception());
if (stackTrace.IsEmpty()) {
Util::ThrowScriptRuntimeException(
*v8::String::Utf8Value(isolate->self, tryCatch.Exception()));
} else {
Util::ThrowScriptRuntimeException(
*v8::String::Utf8Value(isolate->self, stackTrace.ToLocalChecked()));
}
}
return;
}
}

注意,请使用 v8::Context::Scope 限定 v8::Local<v8::Context> 的范围,否则程序将会崩溃。 使用 node::SetProcessExitHandler 可以自定义 process.exit 事件,这里是停止当前的 node::Environment。 对于 JavaScript 的异常,你可以使用 v8::TryCatch 进行捕捉与获取 StackTrace。

Node.js C++ / Android 嵌入器 API 及其使用
https://mucute-qwq.github.io/posts/node-android/
作者
一剪沐橙
发布于
2026-03-27
许可协议
CC BY-NC-SA 4.0