本文主要實現兩個功能:
(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相關的數據類型和接口。下面是相關代碼片斷: html
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 */
typedefsignedchar jbyte; /* signed 8 bits */
typedefunsigned short jchar; /* unsigned 16 bits */
typedefshort jshort; /* signed 16 bits */
typedefint jint; /* signed 32 bits */
typedeflonglong 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結構體,封裝了許多經常使用的函數,如: java
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 */
conststructJNINativeInterface* 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源碼: android
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
typedefstructJniMethodInfo_
{
JNIEnv * env;
jclass classID;
jmethodID methodID;
} JniMethodInfo;
classCC_DLL JniHelper
{
public:
staticJavaVM* getJavaVM();
staticvoidsetJavaVM(JavaVM *javaVM);
staticconstchar* getExternalAssetPath();
staticvoidsetExternalAssetPath(constchar* externalAssetPath);
staticjclass getClassID(constchar*className, JNIEnv *env=0);
staticboolgetStaticMethodInfo(JniMethodInfo &methodinfo, constchar*className, constchar*methodName, constchar*paramCode);
staticboolgetMethodInfo(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,第二個參數是類的絕對路徑,第三個參數是函數名,第四個參數是函數簽名(參數和返回類型),示例代碼以下: c++
1
2
3
4
|
if(JniHelper::getStaticMethodInfo(t, CLASS_NAME, "showTipDialog", "(Ljava/lang/String;Ljava/lang/String;)V"))
{
//...
}
|
關於類型簽名,請對照下圖:
ide
(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
|
privatestaticHandler mHandler;
publicstaticvoidinit(Handler handler)
{
JniTestHelper.mHandler = handler;
}
publicstaticnativevoidsetPackageName(String packageName);
|
(3)打開JniTest.java,在onCreate函數中添加下面的代碼: ui
1
2
3
4
5
|
protectedvoidonCreate(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 this
1
2
3
4
|
#ifndef TEST_H
#define TEST_H
#endif
|
test.cpp spa
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"
usingnamespacecocos2d;
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"
usingnamespacecocos2d;
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
|
publicstaticnativevoidexitApp();
privatestaticvoidshowTipDialog(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
**/
publicclassDialogMessage {
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
|
publicstaticfinalintSHOW_DIALOG = 0x0001;
privateHandler mHandler = newHandler()
{
@Override
publicvoidhandleMessage(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
publicvoidonClick(DialogInterface dialog, intwhich) {
dialog.dismiss();
}
})
.setPositiveButton("Ok",
newDialogInterface.OnClickListener() {
@Override
publicvoidonClick(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/u013388439/9459321