JNI(Java Native Interface):Java的本地調用。本文通過JNI在Cocos2d-x3.3RC0中完成Java與C++的互調。具體實現以下兩個功能:(1)通過Android sdk的API得到應用程序的包名,並傳遞給C++層函數。(2)通過C++函數調用Android的Java層函數,顯示一個對話框。點擊按鈕退出程序。
詳細知識見:http://blog.csdn.net/yuxikuo_1/article/details/39577257。其中最重要的是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類解決Java與C++的通信。
下面介紹兩個常用的函數:
用來判斷java類中靜態函數是否存在,初始化結構體JniMethodInfo。該結構體封裝了JNIEnv*和java.lang.Class、函數ID。這樣可以使用JNIEnv*調用CallStaticXXXMethod(jclass clazz,jmethodID methodID,...)和CallXXXMethod(jobject obj,jmethodID methodID,...)等常用函數,其中XXX代表函數返回值類型,如void、int等。如下代碼:參數1:JniMethodInfo,參數2:類的絕對路徑,該路徑為:proj.android/src/下的目錄,例如引擎模板工程下的路徑為:src/org/cocos2dx/cpp/XXX。XXX為cpp下的java文件。記住路徑中不用加.java後綴,因為路徑使用的是類名。參數3:函數名,參數4:函數簽名,具體規則見3類型簽名
JniMethodInfo info; bool ret = JniHelper::getStaticMethodInfo(info,"org/cocos2dx/cpp/AppActivity","getObj","()Ljava/lang/Object;"); jobject jobj; if(ret) { log("call void getObj() succeed"); jobj = info.env->CallStaticObjectMethod(info.classID,info.methodID); } bool re = JniHelper::getMethodInfo(info,"org/cocos2dx/cpp/AppActivity","func1","()V"); if(re) { log("call func1 succeed"); info.env->CallVoidMethod(jobj,info.methodID); }
#if(CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID) JniMethodInfo info; //判斷org/cocos2dx/cpp/AppActivity.java中是否存在getObj靜態函數 bool ret = JniHelper::getStaticMethodInfo(info,"org/cocos2dx/cpp/AppActivity","getObj","()Ljava/lang/Object;"); jobject jobj;//用於存放返回的對象 if(ret) { log("call void getObj() succeed"); jobj = info.env->CallStaticObjectMethod(info.classID,info.methodID);//調用getObj函數,返回一個對象 } //判斷org/cocos2dx/cpp/AppActivity.java中是否存在func1非靜態函數 bool re = JniHelper::getMethodInfo(info,"org/cocos2dx/cpp/AppActivity","func1","()V"); if(re) { log("call func1 succeed"); info.env->CallVoidMethod(jobj,info.methodID);//通過返回的對象調用非靜態函數 } #endif
這個不做過多介紹,既然研究到Jni了,相比都不是太菜鳥了。
#ifndef __JniDemo__JniTest__ #define __JniDemo__JniTest__ #include "cocos2d.h" USING_NS_CC; void setPackageName(const char* packageName)//從Java層傳過來的包名在此處打印出來 { log("packageName = %s",packageName); } void exitApp()//Java層調用C++層的該函數,關閉程序。 { Director::getInstance()->end(); }
然後在HelloWorldScene.cpp中包含如下頭文件,並在menuCloseCallback中添加如下代碼:
//頭文件包含,判斷平台 #if(CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID) #include "../proj.android/jni/hellocpp/test.h"//一定是相對路徑 #endif //調用C++調用Java層代碼 void HelloWorld::menuCloseCallback(Ref* pSender) { #if(CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID) showTipDialog("exit","Exit,Really Go?"); #endif #if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS) exit(0); #endif }
打開ADT工程目錄下的jni/hellocpp/列表,在hellocpp下添加c++類,test.cpp和test.h。代碼如下:一定包含extern "C"
//test.h #ifndef TEST_H_ #define TEST_H_ extern "C" { void showTipDialog(const char* title,const char* msg); } #endiftest.cpp代碼如下:
//test.cpp #include "test.h" #include "cocos2d.h" #include "platform/android/jni/JniHelper.h"//引擎提供的JniHelper類 #include "../../../Classes/JniTest.h"//相對路徑,上一步在Xcode中創建的類 #include#define CLASS_NAME "org/cocos2dx/cpp/JniTestHelper"//這步的路徑即是上面紅線的路徑,很重要 using namespace cocos2d; extern "C" { //下面的函數通過Jni調用Java層的函數。 void showTipDialog(const char* title,const char* msg)//在HelloWorldScene中調用該處的此函數,此函數在通過Jni傳到Java層 { //調用Java層JniTestHelper.java中的showTipDialog函數 JniMethodInfo t; if(JniHelper::getStaticMethodInfo(t,CLASS_NAME,"showTipDialog","(Ljava/lang/String;Ljava/lang/String;)V"))//該函數的意思就是,尋找org/cocos2dx/cpp/JniTestHelper類中有無靜態函數showTipDialog,暫時還沒有放出JniTestHelper的代碼,稍後。 { 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); } }//以下兩個函數,是通過Jni調用C++層的setPackageName和exitApp,Java層的兩個函數在JniTestHelper中定義,參數通過JNIEnv傳入 void Java_org_cocos2dx_cpp_JniTestHelper_setPackageName(JNIEnv* env,jobject thiz,jstring packageName) { const char* pkgName = env->GetStringUTFChars(packageName,NULL); setPackageName(pkgName);//C++層代碼 env->ReleaseStringUTFChars(packageName,pkgName); } void Java_org_cocos2dx_cpp_JniTestHelper_exitApp(JNIEnv* env,jobject thiz) { exitApp();//C++層代碼 } }
在ADT工程目錄src/org.cocos2dx.cpp的目錄下添加java類,JniTestHelper.java1)JniTestHelper.java代碼如下:
package org.cocos2dx.cpp; import org.cocos2dx.lib.Cocos2dxHandler.DialogMessage; import android.os.Handler; import android.os.Message; public class JniTestHelper { private static Handler mHandler;//Java的Handler傳遞消息 public static void init(Handler handler) { JniTestHelper.mHandler = handler; } public static native void setPackageName(String packageName);//聲明兩個靜態nativa函數,在Jni中test.cpp中定義,調用C++層的對應函數。 public static native void exitApp(); private static void showTipDialog(final String title,final String text) { Message msg = mHandler.obtainMessage();//接受消息 msg.what = AppActivity.SHOW_DIALOG; DialogMessage dm = new DialogMessage(title, text);//重點是這一步,之前的教程自己定的數據結構,而新版本的Cocos的 dm.titile = title; //Jni庫為我們提供了DialogMessage這個數據結構類。所以不用自定義 dm.message = text; msg.obj = dm; msg.sendToTarget(); } }2)AppActivity.java代碼
package org.cocos2dx.cpp; import org.cocos2dx.lib.Cocos2dxActivity; import org.cocos2dx.lib.Cocos2dxGLSurfaceView; import org.cocos2dx.lib.Cocos2dxHandler.DialogMessage; import android.app.AlertDialog; import android.content.DialogInterface; import android.os.Bundle; import android.os.Handler; import android.os.Message; public class AppActivity extends Cocos2dxActivity{ public static final int SHOW_DIALOG = 0x0001; protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); JniTestHelper.init(mHandler);//將下面定義的Handler對象,初始化JniTestHelper中的Handler對象。 JniTestHelper.setPackageName(this.getPackageName()); } public Cocos2dxGLSurfaceView onCreateView(){ Cocos2dxGLSurfaceView glSurfaceView = new Cocos2dxGLSurfaceView(this); glSurfaceView.setEGLConfigChooser(5,6,5,0,16,8); return glSurfaceView; } static { System.loadLibrary("cocos2dcpp"); } private Handler mHandler = new Handler() { public void handleMessage(Message msg) { switch (msg.what) { case SHOW_DIALOG://設置提示框 DialogMessage dm = (DialogMessage)msg.obj; new AlertDialog.Builder(AppActivity.this) .setTitle(dm.titile) .setMessage(dm.message).setNegativeButton("cancle", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface arg0, int arg1) { // TODO Auto-generated method stub arg0.dismiss(); } }) .setPositiveButton("Ok",new DialogInterface.OnClickListener(){ public void onClick(DialogInterface arg0, int arg1) { arg0.dismiss(); JniTestHelper.exitApp(); } }) .create().show(); break; } } }; }
好了,代碼和注釋基本就結束了,還需要該的是Android.mk文件。代碼如下:
LOCAL_SRC_FILES := hellocpp/main.cpp \ hellocpp/test.cpp \ //將新建的test.cpp類加入mk文件 ../../Classes/AppDelegate.cpp \ ../../Classes/HelloWorldScene.cpp之前介紹過萬能mk文件生成方法,詳見http://blog.csdn.net/yuxikuo_1/article/details/39552431。為了減少出問題的幾率,建議改高AndroidManifest中的SDK版本,不改無所謂也。
如出現問題可參考 1)http://blog.csdn.net/yuxikuo_1/article/details/39654499 2)http://blog.csdn.net/yuxikuo_1/article/details/39552639 3)http://blog.csdn.net/yuxikuo_1/article/details/39671733注:環境Mac XCode6 ADT22.2.1 Cocos2d-x3.3RC0 紅米Note。
說了這麼多,沒有源碼那不是坑爹麼。源碼連接:http://pan.baidu.com/s/1jGn80QE