cocos2d-x 通过JNI实现c/c++和Android的java层函数互调 .
来源:互联网 发布:达拉斯2000年人口数据 编辑:程序博客网 时间:2024/05/17 08:49
转载请注明来自:Alex Zhou的程序世界,本文链接:http://codingnow.cn/cocos2d-x/992.html
本文主要实现两个功能:
(1)通过Android sdk的API得到应用程序的包名(PackageName),然后传递给c++层函数。
(2)通过c++函数调用Android的java层函数,显示一个对话框,点击按钮退出程序。
1. 首先来简单学习一下JNI的相关知识,我这篇文章中简单实现了怎么在Android Java层调用c++函数。要想使用JNI,必须得包含头文件,android是使用ndk编译c/c++的,这里jni.h文件位于:\android-ndk-r8b\platforms\android-14\arch-arm\usr\include\jni.h,该文件定义了所有和JNI相关的数据类型和接口。下面是相关代码片段:
# include <inttypes.h> /* C99 */typedef uint8_t jboolean; /* unsigned 8 bits */typedef int8_t jbyte; /* signed 8 bits */typedef uint16_t jchar; /* unsigned 16 bits */typedef int16_t jshort; /* signed 16 bits */typedef int32_t jint; /* signed 32 bits */typedef int64_t jlong; /* signed 64 bits */typedef float jfloat; /* 32-bit IEEE 754 */typedef double jdouble; /* 64-bit IEEE 754 */#elsetypedef unsigned char jboolean; /* unsigned 8 bits */typedef signed char jbyte; /* signed 8 bits */typedef unsigned short jchar; /* unsigned 16 bits */typedef short jshort; /* signed 16 bits */typedef int jint; /* signed 32 bits */typedef long long jlong; /* signed 64 bits */typedef float jfloat; /* 32-bit IEEE 754 */typedef double jdouble; /* 64-bit IEEE 754 */#endif /* "cardinal indices and sizes" */typedef jint jsize; #ifdef __cplusplus/* * Reference types, in C++ */class _jobject {};class _jclass : public _jobject {};class _jstring : public _jobject {};class _jarray : public _jobject {};class _jobjectArray : public _jarray {};class _jbooleanArray : public _jarray {};class _jbyteArray : public _jarray {};class _jcharArray : public _jarray {};class _jshortArray : public _jarray {};class _jintArray : public _jarray {};class _jlongArray : public _jarray {};class _jfloatArray : public _jarray {};class _jdoubleArray : public _jarray {};class _jthrowable : public _jobject {}; typedef _jobject* jobject;typedef _jclass* jclass;typedef _jstring* jstring;typedef _jarray* jarray;typedef _jobjectArray* jobjectArray;typedef _jbooleanArray* jbooleanArray;typedef _jbyteArray* jbyteArray;typedef _jcharArray* jcharArray;typedef _jshortArray* jshortArray;typedef _jintArray* jintArray;typedef _jlongArray* jlongArray;typedef _jfloatArray* jfloatArray;typedef _jdoubleArray* jdoubleArray;typedef _jthrowable* jthrowable;typedef _jobject* jweak; #else /* not __cplusplus */ /* * Reference types, in C. */typedef void* jobject;typedef jobject jclass;typedef jobject jstring;typedef jobject jarray;typedef jarray jobjectArray;typedef jarray jbooleanArray;typedef jarray jbyteArray;typedef jarray jcharArray;typedef jarray jshortArray;typedef jarray jintArray;typedef jarray jlongArray;typedef jarray jfloatArray;typedef jarray jdoubleArray;typedef jobject jthrowable;typedef jobject jweak; #endif /* not __cplusplus */
我们经常用到的是JNIEnv*,它是一个c结构体,封装了许多常用的函数,如:struct _JNIEnv { /* do not rename this; it does not seem to be entirely opaque */ const struct JNINativeInterface* functions; #if defined(__cplusplus) jint GetVersion() { return functions->GetVersion(this); } jclass DefineClass(const char *name, jobject loader, const jbyte* buf, jsize bufLen) { return functions->DefineClass(this, name, loader, buf, bufLen); } jclass FindClass(const char* name) { return functions->FindClass(this, name); }// 这里省略其他函数... }
cocos2d-x引擎对jni的操作进行了封装,提供了一个非常好用的类:JniHelper,定义了一些常用的接口,该文件位于cocos2dx/platform/android/jni目录下。下面看看JniHelper.h源码:typedef struct JniMethodInfo_{ JNIEnv * env; jclass classID; jmethodID methodID;} JniMethodInfo; class CC_DLL JniHelper{public: static JavaVM* getJavaVM(); static void setJavaVM(JavaVM *javaVM); static const char* getExternalAssetPath(); static void setExternalAssetPath(const char* externalAssetPath); static jclass getClassID(const char *className, JNIEnv *env=0); 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); static std::string jstring2string(jstring str); private: static JavaVM *m_psJavaVM; static std::string m_externalAssetPath;};
下面来解释JniHelper的两个常用函数:
(1)getStaticMethodInfo
用来判断Java的类静态函数是否存在,并初始化结构体JniMethodInfo,该结构体封装了JNIEnv*和java.lang.Class对象、函数ID。这样就可以使用JNIEnv*调用 CallStaticXXXMethod(jclass clazz, jmethodID methodID, …)和 CallXXXMethod(jobject obj, jmethodID methodID, …)等常用函数(XXX替换为函数返回值类型,如:Void,Int等)。
第一个参数为JniMethodInfo,第二个参数是类的绝对路径,第三个参数是函数名,第四个参数是函数签名(参数和返回类型),示例代码如下:if(JniHelper::getStaticMethodInfo(t, CLASS_NAME, "showTipDialog", "(Ljava/lang/String;Ljava/lang/String;)V")){//...}
关于类型签名,请对照下图:
(2)getMethodInfo
该函数与getStaticMethodInfo类似,用于Java类的非静态函数。
2. 下面开始实现文章开头所述的两个功能,本文是在cocos2d-x 2.0版本 自适应屏幕分辨率demo的基础上添加的。
(1)利用cocos2d-x创建一个Android工程,名为JniTest,包名为com.alexzhou.jni,此时该包下会自动生成一个JniTest.java文件。
(2)首先来实现把应用程序的包名传递给c++函数,在包下创建JniTestHelper.java,该类封装了给c++调用的函数,添加如下代码:
private static Handler mHandler; public static void init(Handler handler){ JniTestHelper.mHandler = handler;} public static native void setPackageName(String packageName);
(3)打开JniTest.java,在onCreate函数中添加下面的代码:protected void onCreate(Bundle savedInstanceState){ super.onCreate(savedInstanceState); JniTestHelper.init(mHandler); JniTestHelper.setPackageName(this.getPackageName()); }
(4)java层的代码已经完成了,下面来编写jni层代码,在/jni/hellocpp/下创建test.h和test.cpp文件,test.h文件暂时不添加任何函数,代码如下:
test.h
#ifndef TEST_H#define TEST_H #endif
test.cpp#include "cocos2d.h"#include <jni.h>#include "platform/android/jni/JniHelper.h"#include "test.h"#include "JniTest.h" #define CLASS_NAME "com/alexzhou/jni/JniTestHelper" using namespace cocos2d; extern "C"{ void Java_com_alexzhou_jni_JniTestHelper_setPackageName(JNIEnv *env, jobject thiz, jstring packageName){ const char *pkgName = env->GetStringUTFChars(packageName, NULL); setPackageName(pkgName); env->ReleaseStringUTFChars(packageName, pkgName);} }
必须加上extern “C”,声明以c语言的方式进行编译,因为c++和c在编译时生成的函数签名不一样,可以在网上查找相关资料,不然运行的时候会出现链接错误。
(5)现在编写c++函数,在Classes目录下创建JniTest.h,代码如下:#ifndef JNI_TEST_H#define JNI_TEST_H #include "cocos2d.h" using namespace cocos2d; void setPackageName(const char *packageName){ CCLog("packageName: %s", packageName); } #endif
(6)修改jni/Android.mk文件的LOCAL_SRC_FILES值 ,内容如下:LOCAL_SRC_FILES := hellocpp/main.cpp \ hellocpp/test.cpp
(7)编译运行,因为我是使用cygwin编译的,而且Android项目不在cocos2d-x的根目录下,所以需要修改build_native.sh,修改COCOS2DX_ROOT和NDK_MODULE_PATH的值,把当前cocos2d-x项目的路径添加到NDK_MODULE_PATH,修改后的值:COCOS2DX_ROOT="/cygdrive/e/cocos2d-x/cocos2d-2.0-x-2.0.4" "NDK_MODULE_PATH=${COCOS2DX_ROOT}:${COCOS2DX_ROOT}/cocos2dx/platform/third_party/android/prebuilt:${APP_ROOT}"
运行结果:
(8)现在来实现通过c++函数调用java层函数,显示一个对话框。在JniTestHelper.java添加如下代码:
public static native void exitApp(); private static void showTipDialog(final String title, final String text){ Message msg = mHandler.obtainMessage(); msg.what = JniTest.SHOW_DIALOG; DialogMessage dm = new DialogMessage(); dm.title = title; dm.msg = text; msg.obj = dm; msg.sendToTarget();}
(9)创建一个DialogMessage.java,封装dialog要显示的数据。/**author:alexzhou email :zhoujiangbohai@163.comdate :2012-12-14 **/ public class DialogMessage { public String title; public String msg;}
(10) 修改JniTest.java,添加显示对话框的函数:public static final int SHOW_DIALOG = 0x0001; private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { switch(msg.what) { case SHOW_DIALOG: DialogMessage dm = (DialogMessage)msg.obj; new AlertDialog.Builder(JniTest.this) .setTitle(dm.title) .setMessage(dm.msg).setNegativeButton("cancle", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); } }) .setPositiveButton("Ok", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); JniTestHelper.exitApp(); } }) .create().show(); break; } } };
(11)在test.h和test.cpp中添加显示对话框的接口:
test.hextern "C"{void showTipDialog(const char *title, const char *msg);}
test.cppextern "C"{void showTipDialog(const char *title, const char *msg){ JniMethodInfo t; if(JniHelper::getStaticMethodInfo(t, CLASS_NAME, "showTipDialog", "(Ljava/lang/String;Ljava/lang/String;)V")) { jstring jTitle = t.env->NewStringUTF(title); jstring jMsg = t.env->NewStringUTF(msg); t.env->CallStaticVoidMethod(t.classID, t.methodID, jTitle, jMsg); t.env->DeleteLocalRef(jTitle); t.env->DeleteLocalRef(jMsg); }} void Java_com_alexzhou_jni_JniTestHelper_setPackageName(JNIEnv *env, jobject thiz, jstring packageName){ const char *pkgName = env->GetStringUTFChars(packageName, NULL); setPackageName(pkgName); env->ReleaseStringUTFChars(packageName, pkgName);} void Java_com_alexzhou_jni_JniTestHelper_exitApp(JNIEnv *env, jobject thiz){ exitApp();} }
(12) 修改Classes目录下的JniTest.h,添加代码:void exitApp(){ CCDirector::sharedDirector()->end();}
(13)到此为止,所有代码都已经完成了,代码比较简单就不详细解释了,现在编译运行,效果如下:
源码下载地址:http://download.csdn.net/detail/zhoujianghai/4890792