本文主要实现两个功能:
(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 */
#else
typedef 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.com
date  :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.h

extern "C"
{
void showTipDialog(const char *title, const char *msg);
}

test.cpp

extern "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

cocos2d-x手游性能优化总结

近段时间在使用cocos2d-x开发2D手游,技术方案使用的是cocos2d-x+lua,因为游戏使用的是cocos2d-x 2.1.5版本,有些优化方案在最新版的cocos2d-x版本已经实现...

阅读全文

cocos2dx-html5 实现网页版flappy bird游戏

我也是第一次使用cocos2d_html5,对js和html5也不熟,看引擎自带的例子和引擎源码,边学边做,如果使用过cocos2d-x的话,完成这个游戏还是十分简单的。游戏体...

阅读全文

【cocos2d-x开发实战 特训99-终结篇】移植到android平台和添加admob广告

上一篇已经完成特性99在win32平台下的开发,现在把它移植到android上,首先修改Android.mk文件,内容如下: LOCAL_PATH := $(call my-dir) include $(CLEAR_...

阅读全文

8 条评论

  1. 请问那个c++调用java的showdialog()是怎么做的,我知道有showdialog()这个函数,但是没看到调用它啊!!

  2. 大神啊,我在c++里调用showTipDialog 这个方法 编译的时候报错啊

  3. 求大神帮忙,我用的是cocos2d-x建的工程,没有用cygwin,直接在Eclipse下编译,按照大神的代码编译,遇到几个问题:
    1、在第(4)步的时候#include “JniTest.h”,Eclipse 找不到头文件,调用不到 setPackageName(pkgName);
    2、在第(11)步的时候test.cpp文件 #include “platform/android/jni/JniHelper.h” 貌似也找不到,JniHelper::getStaticMethodInfo出错,但JniMethodInfo却可以用,不解?
    3、另外的小问题是我自己新建的工程java和c++互相调用没问题,同样的代码放到Cocos2d-x下的android工程确有有问题,例如–JNIEXPORT jstring JNICALL Java_org_cocos2dx_hellocpp_HelloCpp_getString(JNIEnv* env,jobject thiz)会出错,改成 void JNICALL Java_org…((JNIEnv* env,jobject thiz)又可以。。。

  4. 师兄有没有java去调一个C++类的函数的方法,是已知类,不是全局的函数,比如cocos2d 去用android的硬件,录音录完了,你的播放动画,你就必须有个通知,执行动画那当然是类的方法,不是全局,这个怎么做

欢迎留言