什么是 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. 重定向 stdout、stderr 至 logcat
由于 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::Scope 与 v8::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 JNICALLJava_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。