本文转载自 OEDx 腾讯在线教育部技术博客。
文章作者:晋中望(xepherjin)
背景
一直以来,ABCmouse 项目中的整体 JS/Native 通信调用结构都是基于callStaticMethod <-> evalString
的方式。通过callStaticMethod
方法我们可以通过反射机制直接在 JavaScript 中调用 Java / Objective-C 的静态方法。而通过evalString
方式,则可以执行 JS 代码,这样便可以进行双端通信。
![591521a25b542aa32b826ad25216f8c7.png](https://img-blog.csdnimg.cn/img_convert/591521a25b542aa32b826ad25216f8c7.png)
虽然基于这个方式上层封装接口后,新增业务逻辑会比较方便。但是过度依赖evalString
,往往也会带来一些隐患。举个 Android 侧的例子:
Cocos2dxJavascriptJavaBridge.evalString("window.sample.testEval('" + param + "',JSON.stringify(" + jsonObj + "))");
对于常见的参数结构,这样运行是没有问题的,然而基于实际场景的种种情况,我们会发现针对引号的控制格外重要。如代码所示,为了保证 JS 代码能够被正确执行,我们在拼接字符串时必须明确 '
与 "
的使用,稍有不慎就会出现 evalString
失败的情况。在 Cocos 的官方论坛上,从大量的反馈中我们也能了解这里的确是一个十分容易踩坑的地方。而另一方面,对于我们项目本身而言,过度依赖 evalString
所产生的种种不确定因素也往往很难掌控,我们又不能一味地通过 try/catch
去解决。所幸的是,经过全局业务排查,目前项目中在绝大多数因此,在查阅官方文档后,我们决定绕过 evalString
,直接基于 JSB 绑定的方式进行通信。
这里以下载器的接入为例。在我们的项目中,下载器是在 Android 与 iOS 侧分别各自实现。在改造之前的版本中,下载器的调用与回调基于 callStaticMethod <-> evalString
的方式。
每次调用下载都需要这样执行:
if(cc.sys.isNative && cc.sys.os == cc.sys.OS_IOS) {
jsb.reflection.callStaticMethod('ABCFileDownloader', 'downloadFileWithUrl:cookie:savePath:', url, cookies, savePath);
} else if(cc.sys.isNative && cc.sys.os == cc.sys.OS_ANDROID) {
jsb.reflection.callStaticMethod("com/tencent/abcmouse/downloader/ABCFileDownloader", "downloadFileWithUrl", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V", url, cookies, savePath);
}
下载成功抑或是失败都需要通过拼接出类似如下的语句执行 JS:
StringBuilder sb = new StringBuilder(JS_STRING_ON_DOWNLOAD_FINISH + "(");
sb.append("'" + success + "',");
sb.append("'" + url + "',");
sb.append("'" + savePath + "',");
sb.append("'" + msg + "',");
sb.append("'" +code + "')");
Cocos2dxJavascriptJavaBridge.evalString(sb.toString());
无论是调用抑或是回调都拼接繁琐又容易出错,全部数据不得不转化为字符串(emmmm也不美观),而且还要考虑到evalString的执行效率问题。如果只是仅有的少数业务场景在使用尚勉强接受,但是当业务日趋复杂庞大,如果都要这样写,同时又没有详细的文档去规范约束,其后期维护成本可想而知。
而当使用 JSB 改造后,我们调用只需如下寥寥几行代码且无需区分平台,更不必担心上述拼接隐患,相比之下逻辑要清晰许多:
jsb.fileDownloader.requestDownload(url, savePath, cookies, options, (success, url, savePath, msg, code) => {
// do whatever you want
});
那么接下来就以一个最简单的下载器的绑定流程为例,我来带大家学习下 JSB 手动绑定的大致流程。
(虽然 Cocos 很人性化提供了自动绑定的配置文件,可以通过一些配置直接生成目标文件,减少了很多工作量。但是亲手来完成一次手动绑定的流程会帮助更为全面地了解整个绑定的实现流程,有助于加深理解。另一方面,当存在特殊需要自动绑定无法满足时,手动绑定也往往会更为灵活)
前置
在开始之前,我们需要需要知道有关 ScriptEngine 抽象层、相关 API 等相关知识,这部分内容如果已从 Cocos 文档了解可跳过直接进行 实践 部分。
抽象层
![6d1e9a731e0107efe4213f2aaa1dc058.png](https://img-blog.csdnimg.cn/img_convert/6d1e9a731e0107efe4213f2aaa1dc058.png)
首先先来看一下上图 Cocos 官方提供的一张抽象层架构,在1.7版本中,抽象层被设计为一个与引擎没有关系的独立模块,对 JS 引擎的管理从 ScriptingCore 被移动到了 se::ScriptEngine 类中,ScriptingCore 被保留下来是希望通过它把引擎的一些事件传递给封装层,充当适配器的角色。在这个抽象层提供了对 JavaScriptCore、SpiderMonkey、V8、ChakraCore 等多种可选的 JS 执