最近使用cocos2d-x与Android交互时的一些记录,使用的是cocos2d-x提供的JniHelper,基于cocos2d-x v3.15,具体的一些代码也放在了这里。
cocos2d-x调用Java
使用JniHelper
JniHelper是cocos2d-x对JNI的一系列接口的封装,cocos2d-x的C++代码和原生Android的java代码的交互可以通过JniHelper来完成。当需要在cocos2d-x中使用JniHelper时,包含头文件即可,注意头文件的位置:
1 2 3
| #if(CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID) #include "platform/android/jni/JniHelper.h" #endif
|
在JniHelper中,有一个最重要的结构体JniMethodInfo
,用于定位Java方法的信息,定义如下:
1 2 3 4 5 6
| typedef struct JniMethodInfo_ { JNIEnv * env; jclass classID; jmethodID methodID; } JniMethodInfo;
|
其中包含三项内容。其中JNIEnv
是与线程相关的结构体,由每个线程专属,不能跨线程访问。JNIEnv
可以访问存储有JNI函数指针的数组。而jclass
和jmethodID
分别对应调用的类和方法ID。在函数调用时需要用到JniMethodInfo
结构体,先看函数调用的步骤:
函数调用
在C++中调用Java函数通常分为两步,首先获取函数信息,然后再调用。
首先获取函数信息,JniHelper中提供了两个静态函数,分别用来获取Java静态函数和非静态函数(实例方法)的信息:
1 2 3 4 5 6 7 8
| static bool getStaticMethodInfo(JniMethodInfo &methodinfo, const char *className, const char *methodName, const char *paramCode); static bool getMethodInfo(JniMethodInfo &methodinfo, const char *className, const char *methodName, const char *paramCode);
|
两个函数比较相似,都是需要传入一个JniMethodInfo
的引用,以及类名、方法名、函数签名,返回一个bool值,表示对应的Java方法是否可调用。参数中的三个字符串,类名需要是包含包名的完整类名,方法即Java中被调用方法的名,最后一个函数签名,是根据函数返回值类型和函数参数类型得到的一个独特的字符串。返回值和参数类型对应的字符串标示见下表,
标示类型 |
Java类型 |
Z |
boolean |
B |
byte |
C |
char |
S |
short |
I |
int |
J |
long |
F |
float |
D |
double |
Ljava/lang/String; |
String |
[I |
int[] |
[Ljava/lang/object; |
Object[] |
数组类型一般在前边加[
,数组之外的引用类型需要在后边加;
,以下是三个示例:
函数签名 |
Java函数 |
()Ljava/lang/String; |
string f() |
(ILjava/lang/Class;)J |
long f(int i,Class c) |
([B)V |
void f(byte[] bytes) |
除了根据类型拼接函数签名,还可以取Java编译后的class文件,执行:
则会输出包含的函数签名信息。
接着刚才,在获取函数信息之后,如果判断此函数可调用,则可以执行下一步,调用:
1 2 3 4 5
| JniMethodInfo t;
t.env->Call...Method(...);
|
此时根据调用的Java函数签名不同,所使用的JNIEnv的函数也不一样,如调用一个静态无参返回值为空的Java函数,需要使用CallStaticVoidMethod(...)
。OK,现在看一个完整的例子,首先是Java代码:
1 2 3 4 5 6 7 8 9 10
| package com.aillieo.cocos2d_x_jni_test; import org.cocos2dx.cpp.AppActivity;
public class SceneTest { public static void testJni() { System.out.println("testJni() is called!"); } }
|
接下来是C++代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
|
#include "SceneTest.hpp"
#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID) #include <jni.h> #include "platform/android/jni/JniHelper.h" #define CLASS_NAME "com/aillieo/cocos2d_x_jni_test/SceneTest" #endif
#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID) void SceneTest::CallJavaMethod() { JniMethodInfo t; cocos2d::log("will try call method"); if(JniHelper::getStaticMethodInfo(t, CLASS_NAME, "testJni", "()V")) { cocos2d::log("has this static method"); t.env->CallStaticVoidMethod(t.classID, t.methodID); } } #endif
|
这是一个最简单的例子了,没有参数传入,也没有返回值。
Java类型映射
在C++中调用Java代码时,很多情况下需要传入参数或者接收返回值。Java中的类型在C++中(Native)的映射如下两表所示,分别是Java中的值类型和引用类型。
Java |
Native类型 |
描述 |
boolean |
jboolean |
无符号8位整型(unsigned char) |
byte |
jbyte |
带符号8位整型(char) |
char |
jchar |
无符号16位整型(unsigned short) |
short |
jshort |
带符号16位整型(short) |
int |
jint |
带符号32位整型(int) |
long |
jlong |
带符号64位整型(long) |
float |
jfloat |
32位浮点型(float) |
double |
jdouble |
64位浮点型(double) |
基本数据类型可以在C++代码中直接使用。
Java引用类型 |
Native类型 |
All objects |
jobject |
char[] |
jcharArray |
java.lang.Class实例 |
jclass |
short[] |
jshortArray |
java.lang.String实例 |
jstring |
int[] |
jintArray |
Object[] |
jobjectArray |
long[] |
jlongArray |
boolean[] |
jbooleanArray |
float[] |
floatArray |
byte[] |
jbyteArray |
double[] |
jdoubleArray |
java.lang.Throwable实例 |
jthrowable |
Java引用类型不能在C++中直接使用,必须通过封装在JniMethodInfo
中的JNIEnv
来操作。
带参数的方法
调用带参数的Java静态方法示例代码如下:
Java方法:
1 2 3 4 5 6 7 8 9 10 11
| package com.aillieo.cocos2d_x_jni_test; import org.cocos2dx.cpp.AppActivity;
public class SceneTest { public static void sendParam(final int index, final String text) {
} }
|
C++调用代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
|
#include "SceneTest.hpp"
#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID) #include <jni.h> #include "platform/android/jni/JniHelper.h" #define CLASS_NAME "com/aillieo/cocos2d_x_jni_test/SceneTest" #endif
#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID) void SceneTest::sendParam(int index, std::string str) { JniMethodInfo t; cocos2d::log("will try call method"); if(JniHelper::getStaticMethodInfo(t, CLASS_NAME, "sendParam", "(ILjava/lang/String;)V")) { cocos2d::log("has this static method");
jint jIndex = index; jstring jText = t.env->NewStringUTF(str.c_str()); t.env->CallStaticVoidMethod(t.classID, t.methodID, jIndex, jText); t.env->DeleteLocalRef(jText);
} } #endif
|
返回值处理
处理返回值,即从Java代码中获取信息。
Java方法:
1 2 3 4 5 6 7 8 9 10 11
| package com.aillieo.cocos2d_x_jni_test; import org.cocos2dx.cpp.AppActivity;
public class SceneTest {
public static String getText() { return "Text from Android"; } }
|
C++调用代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
|
#include "SceneTest.hpp"
#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID) #include <jni.h> #include "platform/android/jni/JniHelper.h" #define CLASS_NAME "com/aillieo/cocos2d_x_jni_test/SceneTest" #endif
#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID) std::string SceneTest::getText() { JniMethodInfo t; cocos2d::log("will try call method"); if(JniHelper::getStaticMethodInfo(t, CLASS_NAME, "getText", "()Ljava/lang/String;")) { cocos2d::log("has this static method"); jstring outStr = (jstring)t.env->CallStaticObjectMethod(t.classID, t.methodID); const char* c_str= t.env->GetStringUTFChars(outStr, NULL); std::string ret(c_str); t.env->ReleaseStringUTFChars(outStr, c_str); return ret; } } #endif
|
调用实例方法
之前讨论的都是对Java静态方法的调用,对于实例方法,首先要通过静态方法获取到实例对象。首先有Java静态方法如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| package com.aillieo.cocos2d_x_jni_test; import org.cocos2dx.cpp.AppActivity;
public class SceneTest {
static AppActivity m_activity; public static AppActivity getJavaActivity() { return m_activity; } }
|
在类AppActivity
里增加一个实例方法testInstanceMethod()
:
1 2 3 4 5 6 7 8 9 10 11 12
| package org.cocos2dx.cpp;
public class AppActivity {
public void testInstanceMethod() { System.out.println("testInstanceMethod() is called!"); } }
|
接下来在C++中调用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
|
#include "SceneTest.hpp"
#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID) #include <jni.h> #include "platform/android/jni/JniHelper.h" #define CLASS_NAME "com/aillieo/cocos2d_x_jni_test/SceneTest" #define CLASS_NAME_APP "org/cocos2dx/cpp/AppActivity" #endif
#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID) void SceneTest::testInstanceMethod() { JniMethodInfo t; cocos2d::log("will try call method"); if(JniHelper::getStaticMethodInfo(t, CLASS_NAME, "getJavaActivity", "()Ljava/lang/Object;")) { cocos2d::log("has this static method"); jobject activityObj; activityObj = (jobject)t.env->CallStaticObjectMethod(t.classID, t.methodID); cocos2d::log("will try call instance method"); if(JniHelper::getMethodInfo(t, CLASS_NAME_APP, "testInstanceMethod", "()V")) { cocos2d::log("has this instance method"); t.env->CallVoidMethod(activityObj, t.methodID); } } } #endif
|
处理自定义类型
某些情况下,需要在C++中使用Java的对象,即处理Java的自定义类型。之前已经可以通过函数调用来获取jobject对象,本节我们将有更进一步的操作:
获取与解析class
获取Java类型有两种方法,可以直接使用FindClass()
或根据获取到的jobject对象通过GetObjectClass()
来获取:
1 2 3 4 5 6 7 8
| JniMethodInfo t; jobject activityObj;
jclass myclass = t.env->FindClass("org/cocos2dx/cpp/MyClass"); jclass myclass = t.env->GetObjectClass(activityObj);
|
有了jclass之后,就可以获取其中的字段和方法了,根据是否静态,通过JNIEnv
获取字段jfieldID
和获取方法jmethodID
的函数有以下四个:
1 2 3 4
| jfieldID GetStaticFieldID(JNIEnv *env, jclass clazz, const char *name, const char *sig); jfieldID GetFieldID(JNIEnv *env, jclass clazz, const char *name, const char *sig); jmethodID GetStaticMethodID(JNIEnv *env, jclass clazz, const char *name, const char *sig); jmethodID GetMethodID(JNIEnv *env, jclass clazz, const char *name, const char *sig);
|
其中name
表示字段或方法的名称,sig
表示字段类型或方法函数的签名。
操作字段与方法
获取到字段jfieldID
和方法jmethodID
之后,可以对字段进行get/set操作,具体的函数有以下的形式:
1 2 3 4 5 6 7 8 9 10 11
|
NativeType GetStatic<type>Field(JNIEnv *env, jclass clazz, jfieldID fieldID);
void SetStatic<type>Field(JNIEnv *env, jclass clazz, jfieldID fieldID, NativeType value);
NativeType Get<type>Field(JNIEnv *env, jobject obj, jfieldID fieldID);
void Set<type>Field(JNIEnv *env, jobject obj, jfieldID fieldID, NativeType value);
|
其中NativeType
是Java类型在C++中的映射,<type>
是Java中的基本数据类型,见下表:
<type> |
NativeType |
Object |
jobject |
Boolean |
jboolean |
Byte |
jbyte |
Char |
jchar |
Short |
jshort |
Long |
jint |
Int |
jlong |
Float |
jfloat |
Double |
jdouble |
对于获取到的jmethodID
,可以根据jmethodID
调用方法,和上文中的一样,使用t.env->Call...Method(...)
。
cocos2d-x 调用Android原生控件
通过jni调用到的函数并不能直接作用于Android的UI线程,必须通过消息来实现。如现在想要通过C++调用Java代码,显示一个Android原生的警示框AlertDialog
。Java中有类SceneAlert
,其有静态成员函数showAlert()
接受来自C++的调用,则需要有以下Java代码来实现,分别位于AppActivity
类和SceneAlert
类中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| package org.cocos2dx.cpp;
import org.cocos2dx.lib.Cocos2dxActivity;
import android.app.AlertDialog; import android.os.Bundle; import android.os.Handler; import android.os.Message;
import com.aillieo.cocos2d_x_sdk_integration.SceneAlert;
public class AppActivity extends Cocos2dxActivity {
public static final int SHOW_ALERT = 0x0001;
protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);
SceneAlert.setHandler(m_Handler); }
private Handler m_Handler = new Handler() { public void handleMessage(Message msg) { switch (msg.what) { case SHOW_ALERT: { AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(AppActivity.this); AlertDialog alertDialog = alertDialogBuilder.create(); alertDialog.setMessage("message"); alertDialog.show(); } break; } } }; }
|
以及
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| package com.aillieo.cocos2d_x_sdk_integration;
import org.cocos2dx.cpp.AppActivity; import android.os.Handler; import android.os.Message;
public class SceneAlert {
static private Handler m_handler;
public static void setHandler(Handler handler) { m_handler = handler; };
public static void showAlert() {
Message msg = new Message(); msg.what=AppActivity.SHOW_ALERT; m_handler.sendMessage(msg); } }
|
Java调用cocos2d-x
调用C++函数
在Java中声明本地方法,需要在方法前加上关键字Native
,然后在C++中实现本地方法,注意,声明方法名称和参数的匹配,Java代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| package org.cocos2dx.cpp;
public class AppActivity extends Cocos2dxActivity {
public static native void sendText(String text);
public void xxxx() { sendText("text from java); } }
|
在C++中要有对应sendText
的实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID)
extern "C" { void Java_org_cocos2dx_cpp_AppActivity_sendText(JNIEnv *env,jobject thiz,jstring text) { std::string str = JniHelper::jstring2string(text); cocos2d::log("text from java is : %s", str.c_str()); } } #endif
|
cocos2d-x中跨线程处理
C++中,接收到Java调用的函数,无法直接与cocos2d-x主线程直接交互,可以通过cocos2d-x提供的方法performFunctionInCocosThread
。如修改刚才的例子,接收到Java对Java_org_cocos2dx_cpp_AppActivity_sendText()
的调用之后,发送自定义事件TEXT_FROM_JAVA
,则需要将函数修改为:
1 2 3 4 5 6 7 8
| void Java_org_cocos2dx_cpp_AppActivity_sendText(JNIEnv *env,jobject thiz,jstring text) { std::string str = JniHelper::jstring2string(text); cocos2d::log("text from java is : %s", str.c_str()); Director::getInstance()->getScheduler()->performFunctionInCocosThread([str]()mutable{ Director::getInstance()->getEventDispatcher()->dispatchCustomEvent("TEXT_FROM_JAVA",&str); }); }
|
REFERENCE
http://blog.csdn.net/sgn132/article/details/50511912
http://blog.csdn.net/luxiaoyu_sdc/article/details/15874505
http://www.cs.princeton.edu/courses/archive/fall97/cs461/jdkdocs/guide/jni/spec/functions.doc.html
http://wiki.jikexueyuan.com/project/deep-android-v1/jni.html