cocoscreator,在原生环境调用js的建议

cocos提供的 evalString 方法,可以在原生环境中来回调js

但这个方式有很多弊端

  1. 其方式几乎等同于js的eval方法,即直接执行一段js语句,有很多安全性问题。

  2. 在cocos jsb上,evalString本质上就是让v8引擎执行一次js语句的编译、运行等操作,过程耗费比太过昂贵。

  3. evalString只接受一个js语句参数,在执行稍复杂的js语句时要注意js语句合法性,如大段的json格式字符串内容,就得注意单引号双引号可能导致的js语句解析失败。

介于evalString的问题,我们工程中自己封装了一个简单的原生回调方法,基本可以规避上面上个问题

其调用方式就是必须指定在具体的全局对象,执行固定的方法并传递参数

使用方式

  1. 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 &param);

}

#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 &param){

        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 &param){

        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

  1. 为了方便java层调用,在Cocos2dxJavascriptJavaBridge.java中声明一个native 静态方法,该native方法已在上面的CocosJSBridge中实现

    public static native void excuteJS(String globalName,String funcName,String param);

  2. 初始化

    在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方法废除,即不友好也易犯错。

12赞

感谢分享 :+1:

挺好的,就是跟unity一样,只能传一个参数,比较不方便

:cow::beer: plus

1赞

感谢分享 :+1:

感谢分享 :+1: