JavaScript and Android Communication with Reflection

Calling Java Static Methods from JavaScript

In an Android native application created with Cocos Creator, we can directly call Java static methods from JavaScript using the Java reflection mechanism. The method is defined as follows.

  1. import { native } from 'cc';
  2. var o = native.reflection.callStaticMethod(className, methodName, methodSignature, parameters...)
  • className: the name of the Java class.
  • methodName: the name of the static method.
  • methodSignature: the signature of the method, to indicate a unique method.
  • parameters: the list of parameters.

Now, let’s take the Test class under the com.cocos.game package as an example.

  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

The className should include the package path. If we want to call a static method in the Test class mentioned above, the className should be “com/cocos/game/Test”.

Note: Here we use a / instead of a . in Java package name.

methodName

The methodName is simply the name of the method. For example, if we want to call the sum method, the methodName parameter should be “sum”.

methodSignature

Since Java supports method overloading, the method signature is used to specify the parameter types and return type, ensuring a unique method is invoked.

The format of the method signature is: (parameter types)return type.

Cocos Creator currently supports the following 4 Java type signatures:

Java TypeSignature
intI
floatF
booleanZ
StringLjava/lang/String;

Note: the signature of String type is Ljava/lang/String;, don’t miss the ; in the end.

Here are some examples:

  • ()V represents a method with no parameters and no return value.
  • (I)V represents a method with one parameter of type int and no return value.
  • (I)I represents a method with one parameter of type int and a return value of type int.
  • (IF)Z represents a method with two parameters, one of type int and one of type float, and a return value of type boolean.
  • (ILjava/lang/String;F)Ljava/lang/String; represents a method with three parameters, one of type int, one of type String, and one of type float, and a return value of type String.

parameters

The parameters passed should match the method signature. It supports number, boolean, and string types.

Examples

Here are some examples of calling static methods in the Test class:

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

The sys.isNative is used to check if it’s running on a native platform, and the sys.os is used to determine the current operating system. Since the communication mechanisms vary across different platforms, it is recommended to perform the check before call native.reflection.callStaticMethod.

After running the code, you can see the corresponding output.

Calling JavaScript from Java

In addition to JavaScript calling Java, the engine also provides a mechanism for Java to call JavaScript.

By using the CocosJavascriptJavaBridge.evalString method provided by the engine, you can execute JavaScript code. It’s important to note that since JavaScript code in Cocos Engine is executed on the GL thread, we need to use CocosHelper.runOnGameThread to ensure that the thread is correct.

Next, we will add a button to the Alert dialog and execute a piece of JavaScript code in its response function.

  1. alertDialog.setButton("OK", new DialogInterface.OnClickListener() {
  2. public void onClick(DialogInterface dialog, int which) {
  3. // Must execute in GL thread
  4. CocosHelper.runOnGameThread(new Runnable() {
  5. @Override
  6. public void run() {
  7. CocosJavascriptJavaBridge.evalString("cc.log(\"Javascript Java bridge!\")");
  8. }
  9. });
  10. }
  11. });

Next, let’s take a look at how to call JavaScript code in different situations.

Calling Global Function

We can add a new global function in the script using the following code:

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

window is the global object in the Cocos Engine script environment. If you want a variable, function, object, or class to be globally accessible, you need to add it as a property of window. You can access it using window.variableName or variableName directly.

Then, you can call it like this:

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

Or:

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

Calling Static Function of an Class

Suppose there is an object in the TypeScript script with the following static function:

  1. export class NativeAPI{
  2. public static callByNative(){
  3. //to do
  4. }
  5. }
  6. //Register NativeAPI as a global class, otherwise it cannot be called in Objective-C.
  7. window.NativeAPI = NativeAPI;

Then you can call it like this:

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

Calling Singleton Function

If the script code implements a singleton object that can be globally accessed:

  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. //Register NativeAPIMgr as a global class, otherwise it cannot be called in Objective-C.
  14. window.NativeAPIMgr = NativeAPIMgr;

You can call it like this:

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

Calling with Parameters

The above mentioned ways of calling JS from Java all support parameter passing. However, the parameters only support the three basic types: string, number, and boolean.

Taking the global function as an example:

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

You can call it like this:

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

Calling JavaScript from C++

If you want to call evalString in C++, you can refer to the following approach to ensure that evalString is executed in the GL thread of the engine:

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

Thread Safety

As you can see in the code above, CocosHelper.runOnGameThread and CC_CURRENT_ENGINE()->getScheduler()->performFunctionInCocosThread are used. This is to ensure that the code is executed in the correct thread. For more details, please refer to the Thread Safety documentation.