/ 开发笔记

#Cocos Creator# 快速实现JavaScript/TypeScript与Java代码相互调用

问题背景

我们经常需要从JavaScript或者TypeScript代码中调用第三方库的接口,这些接口,可能是一些第三方插件,比如广告,统计,或者GooglePlay,或者其他渠道的SDK。

在安卓平台上,要调用第三方库,就需要通过JS/TS与Java代码进行交互。

解决方案

1). 使用 JavaScript Binding

最复杂也最灵活的方案是,是自己写jsb代码,但是这样会非常麻烦,不信可以自己Google一下 JavaScript Binding.

2). 使用 Cocos Creator的jsb宏
Cocos Creator 提供了一套自己的jsb架构,并提供了一些宏来抹平不同底层的区别,具体参考:
https://docs.cocos.com/creator/manual/zh/advanced-topics/jsb/JSB2.0-learning.html

这套接口很多也相对完备,不过挺复杂的,细节很多。

3). 使用 Cocos Creator的jsb接口

另外,Cocos Creator已经基于自己的jsb实现,提供了一些快速的接口供我们是用。

官方文档 《如何在 Android 平台上使用 JavaScript 直接调用 Java 方法》
https://docs.cocos.com/creator/manual/zh/advanced-topics/java-reflection.html

使用Cocos Creator的jsb与Java交互的步骤

1. JavaScript 调用 Java接口

使用 jsb.reflection.callStaticMethod 可以访问指定Java类中的静态方法

var o = jsb.reflection.callStaticMethod(className, methodName, methodSignature, parameters...)

1) 参数

  • className
    Java的完整类名,与Java中引用不一样的地方是,需要.替换成/,例如:
    org/cocos2dx/javascript/tpcl/admobHelper

  • methodName
    Java类中的静态方法名

  • methodSignature
    要调用的方法的参数和返回值描述
    签名的格式:(参数1参数2参数3)返回值
    参数列表放在()之间,并且参数之间不要有空格和任何符号
    返回值跟在()后边。

签名举例
()V 表示没有参数,没有返回值的方法,即 void foo()
(I)V 表示参数为一个int,没有返回值的方法 void foo(int a)
(I)I 表示参数为一个int,返回值为int的方法 int foo(int a)
(IF)Z 表示参数为一个int和一个float,返回值为boolean的方法 boolean foo(int a, float b)

  • parameters
    传入的参数值

2) 返回值

只要是支持的类型,可以直接使用。

3) 注意事项

支持类型
目前 Cocos Creator 中支持的 Java 类型签名有下面 4 种:
CocosCreator_JSB_Java_Parameters_Signatures

也就是说,要使用Cocos Creator的jsb方法,只能用最基础的int/float/bool/string类型,不支持其他复杂类型,比如自定义对象,回调方法等。

UI线程和GL线程

Android的App中是区分UI线程和GL线程的,不同的接口需要在不同的线程中调用。
其中,cocos 引擎的渲染和 JS 的逻辑是在 GL 线程中进行的,而 Android 本身的 UI 更新是在 App 的 UI 线程进行的,所以如果我们在 JS 中调用的 Java 方法有任何刷新 UI 的操作,都需要在 UI 线程进行。

从JavaScript中直接调用Java方法时,默认是在GL线程。

如果不知道哪些方法不适合在GL线程中调用,可以使用默认调用方式,在调试的时候,Android Studio会提示你,这时候可以切换到UI线程中调用:

通过调用 Cocos2dxActivity.runOnUiThread 方法可以在切换到UI线程。

以Admob为例,Admob中大部分方法都需要在UI线程中调用。

public static void showBanner(){
    System.out.println("[admob.Banner.show]");
    _app.runOnUiThread(new Runnable() {
        @Override
        public void run() {
            _banner.setVisibility(View.VISIBLE);
            updateBannerPosition();
            _banner.resume();
        }
    });
}

其中 _app是在初始化时传入的Cocos2dxActivity对象

public static void setContext(Cocos2dxActivity app, RelativeLayout layout){
    _app = app;
    _rootLayout = layout;
}

4) 示例

Java类

public class admobHelper {
    public static void init(){
        Log.d("admob", "init");
        System.out.println("[admob.init]");
        MobileAds.initialize(_app, APP_ID);
    }
}

JavaScript/TypeScript

export default class AdmobHelper{
  public static init(){
    if(cc.sys.platform == cc.sys.IPHONE || cc.sys.platform == cc.sys.IPAD){
      jsb.reflection.callStaticMethod("TpclHelper", "initAdmob");
    }else if(cc.sys.platform == cc.sys.ANDROID){       jsb.reflection.callStaticMethod("org/cocos2dx/javascript/tpcl/admobHelper", "init", "()V");
    }        
 }  

2. Java 调用 JavaScript 接口

在少数情况下,我们还需要从Java中调用JavaScript接口。

使用 Cocos2dxJavascriptJavaBridge.evalString 可以执行一段JavaScript代码。

var o = jsb.reflection.callStaticMethod(className, methodName, methodSignature, parameters...)

值得注意的是,这个方法只有一个参数,就是要执行的JavaScript代码。它的实现,类似于在Chrome开发者工具中的Console中直接执行代码。

如果要调用Cocos引擎的方法,由于这些方法都在cc对象中,而cc已经提前由引擎暴露到了全局环境里,所以,可以直接用cc.xxx来调用。

Cocos2dxJavascriptJavaBridge.evalString("cc.log(\"Javascript Java bridge!\")");

而如果是自己的变量呢?同样的,你需要提前把你的对象暴露到全局环境里,可以使用window变量

假设,JavaScript接口

let foo = {
	sayHello : function(){
	    console.info("hello");
	}
}
window["foo"]=foo;

Java代码

app.runOnGLThread(new Runnable() {
    @Override
    public void run() {
Cocos2dxJavascriptJavaBridge.evalString("foo.sayHello()");
    }
});

1). 参数与返回值

  1. 怎么传参数
    可以直接拼到evalString的参数里即可。

  2. 怎么返回值
    貌似不支持返回值。

2). 注意事项

有些方法需要在GL线程中才能使用,所以需要用到Cocos2dxActivity.runOnGLThread

iOS平台怎么办?

iOS平台上,我们也可以利用Cocos Creator的jsb接口,来实现JavaScript与Object C的交互。

方法也是非常类似的,参考官方文档《如何在 iOS 平台上使用 Javascript 直接调用 Objective-C 方法》
https://docs.cocos.com/creator/manual/zh/advanced-topics/oc-reflection.html

这个很详细了,没有什么坑。

方法

使用 jsb.reflection.callStaticMethod 就可以调用指定类的静态方法了。

参数
分别是 className, methodName, paramter1, paramter2, ...

签名
OC的jsb不需要提供签名,但是需要在方法名中包含完整的定义。

举例

Object C定义

@interface NativeOcClass : NSObject
+(BOOL)callNativeUIWithTitle:(NSString *) title andContent:(NSString *)content;
@end

JavaScript

var ret = jsb.reflection.callStaticMethod(
  "NativeOcClass", // 类名
  "callNativeUIWithTitle:andContent:", // 方法名
  "cocos2d-js", // 参数1 title
  "Yes! you call a Native UI from Reflection" // 参数2 Content
);

注意事项

值得注意的是:
可能是由于苹果不支持动态执行脚本的缘故,Cocos Creator的jsb接口并不支持使用evalString方法,无法直接从Object C执行JavaScript脚本。