JNI笔记
由于要做一个能够加红字体的dialog,而cocos2d中的CCMessageBox是系统内带的,我无法修改其字体颜色。事实上是可以修改的,通过观察发现CCMessageBox被调用后,在安卓平台中会调用org.cocos2dx.lib.Cocos2dxHandler类中的showDialog方法,结果发现Cocos2dx使用AlertDialog来实现的,贴上代码:
private void showDialog(Message msg) { Cocos2dxActivity theActivity = this.mActivity.get(); DialogMessage dialogMessage = (DialogMessage)msg.obj; new AlertDialog.Builder(theActivity) .setTitle(dialogMessage.titile) .setMessage(dialogMessage.message) //.setView(view) 有一个setView成员方法允许我们定义自己的View .setPositiveButton("ok", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { // TODO Auto-generated method stub } }).create().show(); }
然后里面可以通过setView来实现字体加红,但是这个org.cocos2dx.lib是公共库,每个游戏都需要依赖它,修改它可能会带来不知道的隐患,因此我觉得提供一个方法给游戏调用,能降低耦合。这样就需要在Cocos2dx中调用java的AlertDialog,所以便引出了想记录的东西JNI。
利用create_project命令创建cocos2dx游戏工程,在eclipse中import进来,之后在src文件夹中创建一个.java文件,记录代码如下:
package com.My.Dialog; import android.app.Activity; import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; import android.content.res.Resources; import android.os.Handler; import android.os.Message; import android.text.Html; import android.text.Spanned; import android.text.method.ScrollingMovementMethod; import android.util.Log; import android.view.LayoutInflater; import android.widget.LinearLayout; import android.widget.TextView; public class ShowRedDialog { private static Handler mHandler; private Activity activity; public void init(Activity act){ Log.i("%s", "show dialog init"); activity = act; mHandler = new Handler() { @Override public void handleMessage(Message msg) { Log.i("%s", "we are in handler"); Resources res = activity.getResources(); //通过在strings.xml中配置layout的位置的方法来添加View String layoutResStr = res.getString(com.My.Dialog.R.string.view_layout); if(layoutResStr == null){ Log.e("%s", "layoutResStr can not find !!!"); new AlertDialog.Builder(activity).setTitle("Error").setMessage("layoutResStr can not find !!!") .setPositiveButton("ok", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { // TODO Auto-generated method stub } }).create().show(); } int layoutId = res.getIdentifier(layoutResStr, "", ""); Log.i("%s", "the resource is found!!"); //反射出layout中的textView LayoutInflater inflater = (LayoutInflater)activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE); LinearLayout view = (LinearLayout)inflater.inflate(layoutId, null); TextView textview = (TextView) view.getChildAt(0); //Spanned prasedText = Html.fromHtml("<font color=\"FF0000\"> MESSAGE </font> " + Integer.toString(layoutId)); //设置textView的文本显示,利用html来修改文本样式 Spanned prasedText = Html.fromHtml("<font color=\"FF0000\"> MESSAGE </font>"); textview.setText(prasedText); textview.setMovementMethod(ScrollingMovementMethod.getInstance()); Log.i("%s", "ready to show our messagebox"); //messageBox弹出 new AlertDialog.Builder(activity) .setTitle("Warning") .setView(view) .setPositiveButton("ok", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { // TODO Auto-generated method stub } }).create().show(); } }; } public static void showMyDialog(String data) { Log.i("in java show my dialog %s", data); Message msg = mHandler.obtainMessage(); msg.sendToTarget(); } }
这里还有很多要改进的地方,可是不是现在的重点,例如我想把View分离出来,可以让别人去实现;还有方法到底该怎么设计更好,变量是静态还是非静态等等。在这里通过Handler实现C++调用静态函数,然后静态函数调用非静态函数,这只是其中一个实现C++调用非静态函数的办法,网上还有好多。我在strings.xml中加入了如下配置:
<?xml version="1.0" encoding="utf-8"?> <resources> <string name="app_name">JniDialog</string> <string name="hello_world">Hello</string> <string name="view_layout">com.My.Dialog:layout/godmsgdialog</string> </resources>
在jni/hellocpp下新建test.h和test.cpp,代码:
#ifndef TEST_H #define TEST_H void showMyDialog(const char *tmp); #endif
test.cpp中的代码:
#include <jni.h> #include "test.h" #include "platform/android/jni/JniHelper.h" #include "cocos2d.h" #include <android/log.h> #define TAG "myDemo-jni" // 这个是自定义的LOG的标识 #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,TAG ,__VA_ARGS__) // 定义LOGD类型 #define LOGI(...) __android_log_print(ANDROID_LOG_INFO,TAG ,__VA_ARGS__) // 定义LOGI类型 #define LOGW(...) __android_log_print(ANDROID_LOG_WARN,TAG ,__VA_ARGS__) // 定义LOGW类型 #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,TAG ,__VA_ARGS__) // 定义LOGE类型 #define LOGF(...) __android_log_print(ANDROID_LOG_FATAL,TAG ,__VA_ARGS__) // 定义LOGF类型 using namespace cocos2d; void showMyDialog(const char *tmp){ LOGI("########## i = %d", "call in JNI"); #if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID) JniMethodInfo t; if(JniHelper::getStaticMethodInfo(t, "com/My/Dialog/ShowRedDialog", "showMyDialog", "(Ljava/lang/String;)V")){ jstring jMsg = t.env->NewStringUTF("do not touch me !!"); t.env->CallStaticVoidMethod(t.classID, t.methodID, jMsg); t.env->DeleteLocalRef(jMsg); } #endif }
在这里遇到了各种问题:
1、第一个是JniHelper::getStaticMethodInfo静态方法在Eclipse中报错说找不到,解决办法是加上编译头#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID)和#endif。
2、t.env->CallStaticVoidMethod(t.classID, t.methodID, "do not touch me !!");之前是这样调用这个方法的,结果出错需要将C++的字符串转为jstring,jstring jMsg = t.env->NewStringUTF("do not touch me !!");
3、如何打印日志,通过引入<android/log.h>并添加宏定义然后就可以在LOGCAT中打印日志了,并且参考:http://blog.csdn.net/zengraoli/article/details/11644815来修改Android.mk文件。
4、jni/hellocpp/main.cpp报错,找不到方法,重启eclipse即可。。。
5、第四个参数是方法签名例如:"(Ljava/lang/String;)V",可以通过在.class文件目录下打开命令行窗口,输入命令 javap -s -p ShowRedDialog (-s表示打印签名信息 -p表示打印所有函数和成员的签名信息,默认只打印public的签名信息)。
修改Android.mk文件,加入要编译的test.cpp文件:
LOCAL_SRC_FILES := hellocpp/main.cpp \ hellocpp/test.cpp \ ../../Classes/AppDelegate.cpp \ ../../Classes/HelloWorldScene.cpp \
在游戏中如下修改,添加头文件:
#if(CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID) #include "../proj.android/jni/hellocpp/test.h" #endif
添加菜单事件:
void HelloWorld::menuCloseCallback(CCObject* pSender) { #if (CC_TARGET_PLATFORM == CC_PLATFORM_WINRT) || (CC_TARGET_PLATFORM == CC_PLATFORM_WP8) CCMessageBox("You pressed the close button. Windows Store Apps do not implement a close button.","Alert"); #elif (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID) CCLog("in cocos2d show dialog"); showMyDialog("dont touch me !!"); #else CCDirector::sharedDirector()->end(); #if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS) exit(0); #endif #endif }
以上说的是C++调用JAVA,之后有时间再写JAVA调用C++。