我们用 React-Native 做混合开发的时候肯定好奇过 JS 侧和 Native 侧是如何通信的,下面来用简单的代码来模拟这个过程。
React Native 用 JavaScriptCore 作为 JS 的解析引擎,但我对它不是很了解,所以本文使用较为熟悉的轻量 JS 引擎 QuickJs 作为替代,关于 QuickJs 的编译与使用可以参考官方站点 (bellard.org/quickjs/),中文站点 (github.com/quickjs-zh/)
JS 传递消息给 C 侧
- 创建 JS 上下文
- QuickJs 引擎没有提供类似 NodeJs 或 JavaScriptCore 中的 global 对象,为了尽量保持与 RN 一致,特意添加一个 global 全局对象
- 添加 Console 等对象来帮助调试
JSContext *JS_GlobalContextCreate()
{
JSRuntime *rt = JS_NewRuntime();
JSContext *ctx = JS_NewContext(rt);
JSValue global = JS_GetGlobalObject(ctx);
JS_SetPropertyStr(ctx, global, "global", global);
js_std_add_helpers(ctx, 0, NULL);
return ctx;
}
- 在 JS 全局对象上添加 bridge 方法,JS 可调用该方法来向 C 侧传递信息
首先看 C 侧函数 nativeFlushQueueImmediate,RN 侧是通过 folly 库来进行反序列化,本文只模拟到将 JS 侧传来的 JSValue 转换为 JSON 字符串
JSValue nativeFlushQueueImmediate(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
JSValue quene = JS_JSONStringify(ctx, argv[0], JS_UNDEFINED, JS_UNDEFINED);
char *chQuene = JS_ToCStringLen2(ctx, NULL, quene, 0);
}
- 然后将它封装为 JS 侧 built-in 函数,并挂到 JS 侧全局对象上
void installNativeHook(JSContext *ctx, JSValueConst global)
{
const char *funcname = "nativeFlushQueueImmediate";
JSValue cfn1 = JS_NewCFunction(ctx, nativeFlushQueueImmediate, funcname, 1);
JS_DefinePropertyValue(
ctx, global, JS_NewAtom(ctx, funcname),
JS_DupValue(ctx, cfn1),
JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE);
}
- JS 侧调用该方法即可将消息传给 C 侧
let quene = [[1, 2], [2, 2]]
globa.nativeFlushQueueImmediate(quene)
C 侧向 JS 侧发送消息
- 首先在 JS 侧全局对象上定义好供 C 侧调用的方法, 约定 C 侧传来的参数是调用的 callbackId 与 args
global.invokeCallback = function (args) {
let callbackId = args[0]
args = args.slice(1)
batchBridge.callbacks[callbackId](args)
}
- 然后定义 C 侧 invokeJSFunction 函数,会从 JS 侧全局对象上取出 invokeCallback 方法,调用传参即可
void invokeJSFunction(JSContext *ctx, int callbackId, const char* arguments) {
JSValue global = JS_GetGlobalObject(ctx);
JSValue callFunction = JS_GetPropertyInternal(ctx, global, JS_NewAtom(ctx, "invokeCallback"), JS_UNDEFINED, 0);
JSValue arg = JS_ParseJSON(ctx, arguments, strlen(arguments), NULL);
JS_Call(ctx, callFunction, JS_NULL, 1, &arg);
}
- JS 侧完整代码如下:
let batchBridge = {
callbacks: []
}
function callNative(arg) {
global.nativeFlushQueueImmediate([arg])
}
function registerCallback(callback) {
batchBridge.callbacks.push(callback)
return batchBridge.callbacks.indexOf(callback)
}
global.invokeCallback = function(args) {
let callbackId = args[0]
args = args.slice(1)
batchBridge.callbacks[callbackId](args)
}
Promise.resolve().then(() => {
let callback = function (args) {
console.log("args is: ", args)
console.log('this is call back!!')
}
callNative([1, 2, registerCallback(callback)])
})
- C 侧完整代码如下,本文只是为了简单模拟,在 nativeFlushQueueImmediate 函数的末尾直接执行了 invokeJSFunction 来执行 JS 侧方法
#include "quickjs-libc.h"
#include <stdio.h>
#include <string.h>
JSContext *JS_GlobalContextCreate()
{
JSRuntime *rt = JS_NewRuntime();
JSContext *ctx = JS_NewContext(rt);
return ctx;
}
void js_install_global_object(JSContext *ctx)
{
JSValue global = JS_GetGlobalObject(ctx);
// JS_DefineProperty(ctx, global, JS_NewAtom(ctx, "global"), global, JS_UNDEFINED, JS_UNDEFINED, JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE | JS_PROP_HAS_VALUE | JS_PROP_HAS_CONFIGURABLE | JS_PROP_HAS_WRITABLE | JS_PROP_HAS_ENUMERABLE);
JS_SetPropertyStr(ctx, global, "global", global);
}
JSValue evaluateScript(JSContext *ctx, const char *filename)
{
uint8_t *buf;
size_t buf_len;
JSValue obj;
int eval_flags;
buf = js_load_file(ctx, &buf_len, filename);
eval_flags = 0;
obj = JS_Eval(ctx, (const char *)buf, buf_len, filename, eval_flags);
js_std_loop(ctx);
return obj;
}
JSValue nativeFlushQueueImmediate(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
JSValue quene = JS_JSONStringify(ctx, argv[0], JS_UNDEFINED, JS_UNDEFINED);
char *chQuene = JS_ToCStringLen2(ctx, NULL, quene, 0);
printf("\n quene is: %s \n", chQuene);
invokeJSFunction(ctx, 0, "[0, 2, \"somearg\"]");
}
void installNativeHook(JSContext *ctx, JSValueConst global)
{
const char *funcname = "nativeFlushQueueImmediate";
JSValue cfn1 = JS_NewCFunction(ctx, nativeFlushQueueImmediate, funcname, 1);
JS_DefinePropertyValue(
ctx, global, JS_NewAtom(ctx, funcname),
JS_DupValue(ctx, cfn1),
JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE);
}
void invokeJSFunction(JSContext *ctx, int callbackId, const char *arguments)
{
JSValue global = JS_GetGlobalObject(ctx);
JSValue callFunction = JS_GetPropertyInternal(ctx, global, JS_NewAtom(ctx, "invokeCallback"), JS_UNDEFINED, 0);
JSValue arg = JS_ParseJSON(ctx, arguments, strlen(arguments), NULL);
JS_Call(ctx, callFunction, JS_NULL, 1, &arg);
}
int main()
{
JSContext *ctx = JS_GlobalContextCreate();
js_install_global_object(ctx);
js_std_add_helpers(ctx, 0, NULL);
installNativeHook(ctx, JS_GetGlobalObject(ctx));
const char *filename = "./test.js";
evaluateScript(ctx, filename);
}