Cocos2dx之通過JNI實現c/c++和Android的java層函式互調

君墨痕發表於2014-03-16

親測成功!

呼叫的流程是:

1、啟動app的時候在onCreate方法裡呼叫的C++裡的setPackageName方法,控制檯輸出相應包名資訊,

2、點選關閉按鈕的時候,在menuCloseCallback先判斷是否為android平臺,是的話就呼叫java裡的showTipDialog方法,

3、彈出對話方塊是點選OK按鈕又呼叫C++裡的方法關閉app。



原文出處: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相關的資料型別和介面。下面是相關程式碼片段:

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
# include <inttypes.h>      /* C99 */
typedefuint8_t         jboolean;       /* unsigned 8 bits */
typedefint8_t          jbyte;          /* signed 8 bits */
typedefuint16_t        jchar;          /* unsigned 16 bits */
typedefint16_t         jshort;         /* signed 16 bits */
typedefint32_t         jint;           /* signed 32 bits */
typedefint64_t         jlong;          /* signed 64 bits */
typedeffloat           jfloat;         /* 32-bit IEEE 754 */
typedefdouble          jdouble;        /* 64-bit IEEE 754 */
#else
typedefunsigned char  jboolean;       /* unsigned 8 bits */
typedefsigned char     jbyte;          /* signed 8 bits */
typedefunsigned short jchar;          /* unsigned 16 bits */
typedefshort           jshort;         /* signed 16 bits */
typedefint             jint;           /* signed 32 bits */
typedeflong long       jlong;          /* signed 64 bits */
typedeffloat           jfloat;         /* 32-bit IEEE 754 */
typedefdouble          jdouble;        /* 64-bit IEEE 754 */
#endif
 
/* "cardinal indices and sizes" */
typedefjint            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.
 */
typedefvoid*           jobject;
typedefjobject         jclass;
typedefjobject         jstring;
typedefjobject         jarray;
typedefjarray          jobjectArray;
typedefjarray          jbooleanArray;
typedefjarray          jbyteArray;
typedefjarray          jcharArray;
typedefjarray          jshortArray;
typedefjarray          jintArray;
typedefjarray          jlongArray;
typedefjarray          jfloatArray;
typedefjarray          jdoubleArray;
typedefjobject         jthrowable;
typedefjobject         jweak;
 
#endif /* not __cplusplus */

我們經常用到的是JNIEnv*,它是一個c結構體,封裝了許多常用的函式,如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
struct_JNIEnv {
    /* do not rename this; it does not seem to be entirely opaque */
    conststruct JNINativeInterface* functions;
 
#if defined(__cplusplus)
 
    jint GetVersion()
    {returnfunctions->GetVersion(this); }
 
    jclass DefineClass(constchar *name, jobject loader, constjbyte* buf,
        jsize bufLen)
    {returnfunctions->DefineClass(this, name, loader, buf, bufLen); }
 
    jclass FindClass(constchar* name)
    {returnfunctions->FindClass(this, name); }
// 這裡省略其他函式...
 
}

cocos2d-x引擎對jni的操作進行了封裝,提供了一個非常好用的類:JniHelper,定義了一些常用的介面,該檔案位於cocos2dx/platform/android/jni目錄下。下面看看JniHelper.h原始碼:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
typedefstruct JniMethodInfo_
{
    JNIEnv *    env;
    jclass      classID;
    jmethodID   methodID;
} JniMethodInfo;
 
classCC_DLL JniHelper
{
public:
    staticJavaVM* getJavaVM();
    staticvoid setJavaVM(JavaVM *javaVM);
    staticconst char* getExternalAssetPath();
    staticvoid setExternalAssetPath(constchar* externalAssetPath);
    staticjclass getClassID(constchar *className, JNIEnv *env=0);
    staticbool getStaticMethodInfo(JniMethodInfo &methodinfo, constchar *className, constchar *methodName, constchar *paramCode);
    staticbool getMethodInfo(JniMethodInfo &methodinfo, constchar *className, constchar *methodName, constchar *paramCode);
    staticstd::string jstring2string(jstring str);
 
private:
    staticJavaVM *m_psJavaVM;
    staticstd::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,第二個引數是類的絕對路徑,第三個引數是函式名,第四個引數是函式簽名(引數和返回型別),示例程式碼如下:

1
2
3
4
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++呼叫的函式,新增如下程式碼:

1
2
3
4
5
6
7
8
privatestatic Handler mHandler;
 
publicstatic void init(Handler handler)
{
    JniTestHelper.mHandler = handler;
}
 
publicstatic native void setPackageName(String packageName);

(3)開啟JniTest.java,在onCreate函式中新增下面的程式碼:

1
2
3
4
5
protectedvoid 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

1
2
3
4
#ifndef TEST_H
#define TEST_H
 
#endif

test.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#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"
 
usingnamespace cocos2d;
 
extern"C"
{
 
voidJava_com_alexzhou_jni_JniTestHelper_setPackageName(JNIEnv *env, jobject thiz, jstring packageName)
{
    constchar *pkgName = env->GetStringUTFChars(packageName, NULL);
    setPackageName(pkgName);
    env->ReleaseStringUTFChars(packageName, pkgName);
}
 
}

必須加上extern “C”,宣告以c語言的方式進行編譯,因為c++和c在編譯時生成的函式簽名不一樣,可以在網上查詢相關資料,不然執行的時候會出現連結錯誤。
(5)現在編寫c++函式,在Classes目錄下建立JniTest.h,程式碼如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
#ifndef JNI_TEST_H
#define JNI_TEST_H
 
#include "cocos2d.h"
 
usingnamespace cocos2d;
 
voidsetPackageName(constchar *packageName)
{
    CCLog("packageName: %s", packageName); 
}
 
#endif

(6)修改jni/Android.mk檔案的LOCAL_SRC_FILES值 ,內容如下:

1
2
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,修改後的值:

1
2
3
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新增如下程式碼:

1
2
3
4
5
6
7
8
9
10
11
12
publicstatic native void exitApp();
 
privatestatic void showTipDialog(finalString title, finalString text)
{
    Message msg = mHandler.obtainMessage();
    msg.what = JniTest.SHOW_DIALOG;
    DialogMessage dm = newDialogMessage();
    dm.title = title;
    dm.msg = text;
    msg.obj = dm;
    msg.sendToTarget();
}

(9)建立一個DialogMessage.java,封裝dialog要顯示的資料。

1
2
3
4
5
6
7
8
9
10
11
/**
author:alexzhou
email :zhoujiangbohai@163.com
date  :2012-12-14
 **/
 
publicclass DialogMessage {
 
    publicString title;
    publicString msg;
}

(10) 修改JniTest.java,新增顯示對話方塊的函式:

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
publicstatic final int SHOW_DIALOG = 0x0001;
 
   privateHandler mHandler = newHandler()
    {
        @Override
        publicvoid handleMessage(Message msg) {
            switch(msg.what)
            {
            caseSHOW_DIALOG:
                DialogMessage dm = (DialogMessage)msg.obj;
                newAlertDialog.Builder(JniTest.this)
                .setTitle(dm.title)
                .setMessage(dm.msg).setNegativeButton("cancle",newDialogInterface.OnClickListener() {
 
                    @Override
                    publicvoid onClick(DialogInterface dialog, intwhich) {
                        dialog.dismiss();
                    }
                })
                .setPositiveButton("Ok",
                        newDialogInterface.OnClickListener() {
 
                    @Override
                    publicvoid onClick(DialogInterface dialog, intwhich) {
                        dialog.dismiss();
                        JniTestHelper.exitApp();
                    }
                })
                .create().show();
                break;
            }
        }
    };

(11)在test.h和test.cpp中新增顯示對話方塊的介面:
test.h

1
2
3
4
extern"C"
{
voidshowTipDialog(constchar *title, constchar *msg);
}

test.cpp

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
extern"C"
{
voidshowTipDialog(constchar *title, constchar *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);
    }
}
 
voidJava_com_alexzhou_jni_JniTestHelper_setPackageName(JNIEnv *env, jobject thiz, jstring packageName)
{
    constchar *pkgName = env->GetStringUTFChars(packageName, NULL);
    setPackageName(pkgName);
    env->ReleaseStringUTFChars(packageName, pkgName);
}
 
voidJava_com_alexzhou_jni_JniTestHelper_exitApp(JNIEnv *env, jobject thiz)
{
    exitApp();
}
 
}

(12) 修改Classes目錄下的JniTest.h,新增程式碼:

1
2
3
4
voidexitApp()
{
    CCDirector::sharedDirector()->end();
}

(13)到此為止,所有程式碼都已經完成了,原始碼地址:http://download.csdn.net/detail/zhoujianghai/4890792



相關文章