cocos提供的 evalString 方法,可以在原生环境中来回调js
但这个方式有很多弊端
-
其方式几乎等同于js的eval方法,即直接执行一段js语句,有很多安全性问题。
-
在cocos jsb上,evalString本质上就是让v8引擎执行一次js语句的编译、运行等操作,过程耗费比太过昂贵。
-
evalString只接受一个js语句参数,在执行稍复杂的js语句时要注意js语句合法性,如大段的json格式字符串内容,就得注意单引号双引号可能导致的js语句解析失败。
介于evalString的问题,我们工程中自己封装了一个简单的原生回调方法,基本可以规避上面上个问题
其调用方式就是必须指定在具体的全局对象,执行固定的方法并传递参数
使用方式
- CocosJSBridge.h和 CocosJSBridge.cpp可以放在Classes下面, 在Xcode、Android.mk 引入
CocosJSBridge.h
#ifndef PROJ_ANDROID_STUDIO_COCOSJSBRIDGE_H
#define PROJ_ANDROID_STUDIO_COCOSJSBRIDGE_H
#include <iostream>
#include <string.h>
namespace ccjsbridge{
// 引擎初始化调用一次
void init();
// 调用js环境的方法
void excuteJS(const std::string &globalObj,const std::string &funcName,const std::string ¶m);
}
#endif //PROJ_ANDROID_STUDIO_COCOSJSBRIDGE_H
CocosJSBridge.cpp
#include <thread>
#include <functional>
#include "CocosJSBridge.h"
#include "cocos/scripting/js-bindings/jswrapper/SeApi.h"
#include "cocos/platform/CCApplication.h"
#include "cocos/base/CCScheduler.h"
#include "cocos2d.h"
#define NS_CCC cocos2d
#define TRACE CCLOG
namespace ccjsbridge {
// GL线程ID
static std::thread::id glThreadID;
void _sendMessageToJS(const std::string &globalName,const std::string &funcName,const std::string ¶m){
auto seinstance = se::ScriptEngine::getInstance();
if ( !seinstance->isValid()){
TRACE(" [ccjsbridge] se::ScriptEngine isInValid!");
return;
}
se::AutoHandleScope hs; // fix v8
auto globalObject = seinstance->getGlobalObject();
se::Value gobj;
globalObject->getProperty( globalName.c_str() , &gobj);
if ( gobj.isObject() && !gobj.isNull() ) {
se::Value funcHandler;
gobj.toObject()->getProperty( funcName.c_str() , &funcHandler);
if ( funcHandler.isObject() && funcHandler.toObject()->isFunction() ){
se::ValueArray args;
args.push_back(se::Value(param));
// TRACE(" [ccjsbridge] sendMessageToJS globalName:%s -- funcName:%s -- param: %s ",globalName.c_str(),funcName.c_str(), param.c_str() );
funcHandler.toObject()->call(args, globalObject);
}
}
}
void init(){
glThreadID = std::this_thread::get_id();
}
void excuteJS(const std::string &globalName,const std::string &funcName,const std::string ¶m){
if ( std::this_thread::get_id() == glThreadID){
_sendMessageToJS(globalName,funcName,param);
return;
}
auto app = NS_CCC::Application::getInstance();
if (app != nullptr){
std::weak_ptr<NS_CCC::Scheduler> g_scheduler = app->getScheduler();
if(auto sche = g_scheduler.lock()) {
sche->performFunctionInCocosThread([globalName,funcName,param](){
_sendMessageToJS(globalName,funcName,param);
});
}
}
}
}
#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID)
#include "cocos/platform/android/jni/JniHelper.h"
extern "C"
JNIEXPORT void JNICALL Java_org_cocos2dx_lib_Cocos2dxJavascriptJavaBridge_excuteJS(JNIEnv *env, jclass clazz,
jstring globalObj, jstring funcName, jstring param) {
std::string _globalObj = NS_CCC::JniHelper::jstring2string(globalObj);
if (_globalObj.empty()){
TRACE("[ccjsbridge] Java_org_cocos2dx_lib_Cocos2dxJavascriptJavaBridge_excuteJS globalObj is empty!");
return;
}
std::string _funcName = NS_CCC::JniHelper::jstring2string(funcName);
if (_funcName.empty()){
TRACE("[ccjsbridge] Java_org_cocos2dx_lib_Cocos2dxJavascriptJavaBridge_excuteJS funcName is empty!");
return;
}
std::string _param = NS_CCC::JniHelper::jstring2string(param);
ccjsbridge::excuteJS(_globalObj,_funcName,_param);
}
#endif
-
为了方便java层调用,在Cocos2dxJavascriptJavaBridge.java中声明一个native 静态方法,该native方法已在上面的CocosJSBridge中实现
public static native void excuteJS(String globalName,String funcName,String param);
-
初始化
在AppDelegate.cpp 中引入
#include "CocosJSBridge.h"并在applicationDidFinishLaunching中执行
ccjsbridge::init();
以上完成后就可以在Java中通过以下方式测试一下
Cocos2dxJavascriptJavaBridge.excuteJS("console","log","这是一个java测试 console.log");
在ios中,OC或cpp中引入
`#include "CocosJSBridge.h"`
调用 ccjsbridge::excuteJS("console","log","这是一个java测试 console.log");
本质上 Cocos2dxJavascriptJavaBridge.excuteJS也是调用ccjsbridge::excuteJS
补充说明
第3步 ccjsbridge::init()是可选操作
理解这个需要了解一下cocos 、v8 js引擎、App主线程之间的调用栈区别,以及为什么要做线程切换。
调用init只是为了CocosJSBridge可以获取当前cocos引擎的GL线程ID,获取GL线程ID可以实现这样一个目的,
比如在原生oc/java的GL线程想回调js层,由于在和引擎在同一个线程,理论上这个时候完全可以在当前线程中直接调用js内的方法,
在引擎启动时调用 ccjsbridge::init(),该方法将记录当前的线程gl_id,后续有ccjsbridge::excuteJS执行,将比较当前执行栈线程id是否和gl_id一致,如果一致则无需线程切换
完成上述接入后,可以在Android做下面这个尝试,查看输出,对比一下大概就能了解
new Handler().postDelayed(new Runnable(){
public void run(){
// 非GL线程执行
Log.d("AppActivity","AAA 执行前");
Cocos2dxJavascriptJavaBridge.excuteJS("console","log","AAA 这是一个java测试 console.log");
Log.d("AppActivity","AAA 执行后");
runOnGLThread(new Runnable() {
@Override
public void run() {
// GL线程执行
Log.d("AppActivity","BBB 执行前");
Cocos2dxJavascriptJavaBridge.excuteJS("console","log","BBB 这是一个java测试, 在GL线程调用 console.log");
Log.d("AppActivity","BBB 执行后");
}
});
}
}, 5000);
最后,对引擎而言,还是建议将 Cocos2dxJavascriptJavaBridge的evalString方法废除,即不友好也易犯错。


plus