基于反射机制实现 JavaScript 与 Android 系统原生通信

JavaScript 调用 Java 静态方法

使用 Cocos Creator 打包的安卓原生应用中,我们可以通过反射机制直接在 JavaScript 中调用 Java 的静态方法。它的定义如下:

  1. import { native } from 'cc';
  2. var o = native.reflection.callStaticMethod(className, methodName, methodSignature, parameters...)
  • className:类名
  • methodName:方法名
  • methodSignature:方法签名
  • parameters:参数列表

接下来,我们以 com.cocos.game 包下面的 Test 类为例,来具体说明。

  1. // package "com.cocos.game";
  2. public class Test {
  3. public static void hello (String msg) {
  4. System.out.println (msg);
  5. }
  6. public static int sum (int a, int b) {
  7. return a + b;
  8. }
  9. public static int sum (int a) {
  10. return a + 2;
  11. }
  12. }

className

className 需要包含包名信息,如果要调用上面的 Test 类中的静态方法,className 应该为 “com/cocos/game/Test”。

  1. > **注意**:这里必须是斜线 `/`,而不是在 Java 代码中的 `.`

methodName

methodName 就是方法本来的名字,例如要调用 sum 方法的话,methodName 传入的就是 “sum”。

methodSignature

由于 Java 支持函数重载功能,方法签名用于告诉反射系统对应的参数类型和返回值类型,以确定唯一的方法。

它的格式为:(参数类型)返回值类型

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

Java 类型签名
intI
floatF
booleanZ
StringLjava/lang/String;

注意:String 类型的签名为 Ljava/lang/String;,不要漏掉了最后的 ;

下面是一些案例

  • ()V 表示没有参数,没有返回值
  • (I)V 表示参数为一个 int,没有返回值的方法
  • (I)I 表示参数为一个 int,返回值为 int 的方法
  • (IF)Z 表示参数为一个 int 和一个 float,返回值为 boolean 的方法
  • (ILjava/lang/String;F)Ljava/lang/String; 表示参数类型为一个 int,一个 String 和一个 float,返回值类型为 String 的方法

parameters

传递的参数与签名匹配即可,支持 number、bool 和 string。

使用示例

接下来我们看几个 Test 类中的静态方法的调用示例:

  1. if(sys.os == sys.OS.ANDROID && sys.isNative){
  2. // 调用 hello 方法
  3. native.reflection.callStaticMethod("com/cocos/game/Test", "hello", "(Ljava/lang/String;)V", "this is a message from JavaScript");
  4. // 调用第一个 sum 方法
  5. var result = native.reflection.callStaticMethod("com/cocos/game/Test", "sum", "(II)I", 3, 7);
  6. log(result); // 10
  7. // 调用第二个 sum 方法
  8. var result = native.reflection.callStaticMethod("com/cocos/game/Test", "sum", "(I)I", 3);
  9. log(result); // 5
  10. }

sys.isNative 用于判断是否为原生平台,sys.os 用于判断当前运行系统。由于各平台通信机制不同,建议先判断再处理。

运行后,可以在 控制台 中看到相应的输出结果。

Java 调用 JavaScript

除了 JavaScript 调用 Java,引擎也提供了 Java 调用 JavaScript 的机制。

通过引擎提供的 CocosJavascriptJavaBridge.evalString 方法可以执行 JavaScript 代码。需要注意的是,由于 JavaScript 相关代码会在 GL 线程中执行,我们需要利用 CocosHelper.runOnGameThread 来确保线程是正确的。

接下来,我们给刚才的 Alert 对话框增加一个按钮,并在它的响应函数中执行一段 JavaScript 代码。

  1. alertDialog.setButton("OK", new DialogInterface.OnClickListener() {
  2. public void onClick(DialogInterface dialog, int which) {
  3. // 一定要在 GL 线程中执行
  4. CocosHelper.runOnGameThread(new Runnable() {
  5. @Override
  6. public void run() {
  7. CocosJavascriptJavaBridge.evalString("cc.log(\"Javascript Java bridge!\")");
  8. }
  9. });
  10. }
  11. });

调用全局函数

我们可以在脚本中通过如下代码新增一个全局函数:

  1. window.callByNative = function(){
  2. //to do
  3. }

window 是 Cocos 引擎脚本环境中的全局对象,如果要让一个变量、函数、对象或者类全局可见,需要将它作为 window 的属性。可以使用 window.变量名 或者 变量名 进行访问。

然后像下面这样调用:

  1. CocosHelper.runOnGameThread(new Runnable() {
  2. @Override
  3. public void run() {
  4. CocosJavascriptJavaBridge.evalString("window.callByNative()");
  5. }
  6. });

或者:

  1. CocosHelper.runOnGameThread(new Runnable() {
  2. @Override
  3. public void run() {
  4. CocosJavascriptJavaBridge.evalString("callByNative()");
  5. }
  6. });

调用类的静态函数

假如在 TypeScript 脚本中有一个类具有如下静态函数:

  1. export class NativeAPI{
  2. public static callByNative(){
  3. //to do
  4. }
  5. }
  6. //将 NativeAPI 注册为全局类,否则无法在 Java 中被调用
  7. window.NativeAPI = NativeAPI;

我们可以像这样调用:

  1. CocosHelper.runOnGameThread(new Runnable() {
  2. @Override
  3. public void run() {
  4. CocosJavascriptJavaBridge.evalString("NativeAPI.callByNative()");
  5. }
  6. });

调用单例函数

如果脚本代码中,有实现可以全局访问的单例对象

  1. export class NativeAPIMgr{
  2. private static _inst:NativeAPIMgr;
  3. public static get inst():NativeAPIMgr{
  4. if(!this._inst){
  5. this._inst = new NativeAPIMgr();
  6. }
  7. return this._inst;
  8. }
  9. public static callByNative(){
  10. //to do
  11. }
  12. }
  13. //将 NativeAPIMgr 注册为全局类,否则无法在 Java 中被调用
  14. window.NativeAPIMgr = NativeAPIMgr;

我们可以像下面这样调用:

  1. CocosHelper.runOnGameThread(new Runnable() {
  2. @Override
  3. public void run() {
  4. CocosJavascriptJavaBridge.evalString("NativeAPIMgr.inst.callByNative()");
  5. }
  6. });

参数传递

以上几种 Java 调用 JS 的方式,均支持参数传递,但参数只支持 string, number 和 bool 三种基础类型。

我们以全局函数为例:

  1. window.callByNative = function(a:string, b:number, c:bool){
  2. //to do
  3. }

可像这样调用:

  1. CocosHelper.runOnGameThread(new Runnable() {
  2. @Override
  3. public void run() {
  4. CocosJavascriptJavaBridge.evalString("window.callByNative('test',1,true)");
  5. }
  6. });

在 C++ 代码中调用 JavaScript

如果要在 C++ 中调用 evalString,我们可以参考下面的方式,确保 evalString 在 JavaScript 引擎所在的线程被执行:

  1. CC_CURRENT_ENGINE()->getScheduler()->performFunctionInCocosThread([=]() {
  2. se::ScriptEngine::getInstance()->evalString(script.c_str());
  3. });

线程安全

可以看到,上面的代码中,使用了 CocosHelper.runOnGameThreadCC_CURRENT_ENGINE()->getScheduler()->performFunctionInCocosThread。这是为了代码在执行时处于正确的线程,详情请参考:线程安全