cocos2d-xでsocket.ioを使う Android版
cocos2d-xでsocket.ioを使う iOS版 - おかひろの雑記 ではCocos2d-xでsocket.ioを使うためのiOSの設定をしましたが、今度はAndroidの方を設定します。
Androidではsocket.io-java-client(https://github.com/Gottox/socket.io-java-client)を使いますが、そのまま使うと、切断時にCPU使用率が下がらなくなるバグがあるようなので、少し手を加えます。
今回使用したバージョン
動作するサンプルはこちら。 https://github.com/okahiro/Cocos2dxSocketIO
(Cocos2d-xのフルプロジェクトなので容量大きいです。socket.ioサーバーを用意する必要があります。)
ライブラリをセットアップ
まずはsocket.io-java-clientとJava-WebSocketをそれぞれダウンロードしてzipを解凍します。
まず、Java-WebSocketの方を解凍したフォルダにターミナルで移動し、antコマンドを実行します。
$ ant
するとdistフォルダにjava_websocket.jarが作成されるので、これをWebSocket.jarという名前に変更し、
socket.io-java-clientを解凍したフォルダのlibsのWebSocket.jarを上書きします。
その後、socket.io-java-clientのフォルダにターミナルで移動し、ant jarコマンドを実行します。
$ ant jar
これによりjarフォルダと中にsocketio.jarが作成されます。
このsocketio.jarをCocos2d-xのプロジェクトに組み込みます。(AndroidStudioを使う場合は、proj.android-studio/app/libsにコピー)
実装
NativeCodeLauncher.hとSocketManagerクラスはcocos2d-xでsocket.ioを使う iOS版 - おかひろの雑記と同じです。
NativeCodeLauncher.cpp
#include "NativeCodeLauncher.h" #include "NativeCodeLauncherJni.h" namespace Cocos2dExt { // Socket.IO関連処理 void NativeCodeLauncher::connectToSocketIO(char const *host,int port) { connectToSocketIOJNI(host,port); } void NativeCodeLauncher::emitToSocketIO(char const *event,char const *message) { emitToSocketIOJNI(event,message); } void NativeCodeLauncher::disconnectFromSocketIO() { disconnectFromSocketIOJNI(); } }
NativeCodeLauncherJni.h
#include <jni.h> #include <string> extern "C" { // Socket.IO関連処理 extern void connectToSocketIOJNI(char const *host,int port); extern void emitToSocketIOJNI(char const *event,char const *message); extern void disconnectFromSocketIOJNI(); }
NativeCodeLauncherJni.cpp
#include "NativeCodeLauncherJni.h" #include <android/log.h> #include "platform/android/jni/JniHelper.h" #include "SocketManager.hpp" #define LOG_TAG "NativeCodeLauncher" #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG,__VA_ARGS__) #define CLASS_NAME "org/cocos2dx/cpp/AppActivity" typedef struct JniMethodInfoEx_ { JNIEnv * env; jclass classID; jmethodID methodID; } JniMethodInfoEx; extern "C" { // get env and cache it static JNIEnv* getJNIEnv(void) { JNIEnv *env = 0; // get jni environment if (cocos2d::JniHelper::getJavaVM()->GetEnv((void**)&env, JNI_VERSION_1_4) != JNI_OK) { LOGD("Failed to get the environment using GetEnv()"); } if (cocos2d::JniHelper::getJavaVM()->AttachCurrentThread(&env, 0) < 0) { LOGD("Failed to get the environment using AttachCurrentThread()"); } return env; } // get class and make it a global reference, release it at endJni(). static jclass getClassID(JNIEnv *pEnv) { jclass ret = pEnv->FindClass(CLASS_NAME); if (! ret) { LOGD("Failed to find class of %s", CLASS_NAME); } return ret; } static bool getStaticMethodInfo(JniMethodInfoEx &methodinfo, const char *methodName, const char *paramCode) { jmethodID methodID = 0; JNIEnv *pEnv = 0; bool bRet = false; do { pEnv = getJNIEnv(); if (! pEnv) { break; } jclass classID = getClassID(pEnv); methodID = pEnv->GetStaticMethodID(classID, methodName, paramCode); if (! methodID) { LOGD("Failed to find static method id of %s", methodName); break; } methodinfo.classID = classID; methodinfo.env = pEnv; methodinfo.methodID = methodID; bRet = true; } while (0); return bRet; } // Socket.IO void connectToSocketIOJNI(char const *host,int port) { JniMethodInfoEx methodInfo; if (!getStaticMethodInfo(methodInfo, "connectToSocketIO", "(Ljava/lang/String;I)V")) { return; } jstring hostArg = methodInfo.env->NewStringUTF(host); methodInfo.env->CallStaticVoidMethod(methodInfo.classID, methodInfo.methodID,hostArg,port); methodInfo.env->DeleteLocalRef(hostArg); methodInfo.env->DeleteLocalRef(methodInfo.classID); } void emitToSocketIOJNI(char const *event,char const *message) { JniMethodInfoEx methodInfo; if (!getStaticMethodInfo(methodInfo, "emitToSocketIO", "(Ljava/lang/String;Ljava/lang/String;)V")) { return; } jstring eventArg = methodInfo.env->NewStringUTF(event); jstring messageArg = methodInfo.env->NewStringUTF(message); methodInfo.env->CallStaticVoidMethod(methodInfo.classID, methodInfo.methodID, eventArg,messageArg); methodInfo.env->DeleteLocalRef(eventArg); methodInfo.env->DeleteLocalRef(messageArg); methodInfo.env->DeleteLocalRef(methodInfo.classID); } void disconnectFromSocketIOJNI() { JniMethodInfoEx methodInfo; if (!getStaticMethodInfo(methodInfo, "disconnectFromSocketIO", "()V")) { return; } methodInfo.env->CallStaticVoidMethod(methodInfo.classID, methodInfo.methodID); methodInfo.env->DeleteLocalRef(methodInfo.classID); } // メッセージを受け取った時の処理 void Java_org_cocos2dx_cpp_AppActivity_nativeReceiveMessage(JNIEnv *env,jobject thiz, jstring event,jstring message) { const char *eventChar = env->GetStringUTFChars(event,0); const char *messageChar = env->GetStringUTFChars(message,0); SocketManager::getInstance()->receiveMessage(eventChar,messageChar); env->ReleaseStringUTFChars(event, eventChar); env->ReleaseStringUTFChars(message, messageChar); } // Socket接続された時 void Java_org_cocos2dx_cpp_AppActivity_nativeOnSocketConnected(JNIEnv *env,jobject thiz) { SocketManager::getInstance()->onConnected(); } // Socket切断された時 void Java_org_cocos2dx_cpp_AppActivity_nativeOnSocketDisconnected(JNIEnv *env,jobject thiz) { SocketManager::getInstance()->onDisconnected(); } // Socketでエラーが発生した時 void Java_org_cocos2dx_cpp_AppActivity_nativeOnSocketError(JNIEnv *env,jobject thiz,jstring errorMessage) { const char *errorMessageChar = env->GetStringUTFChars(errorMessage,0); SocketManager::getInstance()->onError(errorMessageChar); env->ReleaseStringUTFChars(errorMessage, errorMessageChar); } }
AppActivity.java
package org.cocos2dx.cpp; import android.util.Log; import org.cocos2dx.lib.Cocos2dxActivity; import org.json.JSONObject; import java.net.MalformedURLException; import io.socket.*; public class AppActivity extends Cocos2dxActivity { // C++側のメソッドを呼ぶ宣言 public static native void nativeReceiveMessage(String event, String message); public static native void nativeOnSocketConnected(); public static native void nativeOnSocketDisconnected(); public static native void nativeOnSocketError(String errorMessage); static private SocketIO socket = null; private static final String TAG = "Cocos2dxSocketIOSample"; // Socket.IO接続 public static void connectToSocketIO(String host, int port) throws MalformedURLException { String url = String.format("http://%s:%d", host, port); socket = new SocketIO(url); socket.connect(iocallback); Log.d(TAG, "Socket 接続しようとします"); } // Socketにイベント送信 public static void emitToSocketIO(String event, String message) { if (socket != null && socket.isConnected()) { // イベント送信 socket.emit(event, message); Log.d(TAG, "emit. " + event + " , " + message); } else if(socket != null && !socket.isConnected()) { // Socket接続されていないのに送信しようとした場合 AppActivity.nativeOnSocketError("Socket is not connected."); } } // Socket.IO切断 public static void disconnectFromSocketIO() { if(socket != null && socket.isConnected()) { socket.disconnect(); Log.d(TAG, "Socket 切断します"); } } // Socket.IOコールバック関数集合 private static IOCallback iocallback = new IOCallback() { @Override public void onConnect() { Log.d(TAG, "Socket 接続されました"); AppActivity.nativeOnSocketConnected(); } @Override public void onDisconnect() { Log.d(TAG, "Socket 切断されました"); AppActivity.nativeOnSocketDisconnected(); } @Override public void onMessage(JSONObject json, IOAcknowledge ack) { Log.d(TAG, "Socket onMessage"); } @Override public void onMessage(String data, IOAcknowledge ack) { Log.d(TAG, "Socket onMessage"); } @Override public void on(String event, IOAcknowledge ack, Object... args) { Log.d(TAG, "received . " + args[0]); AppActivity.nativeReceiveMessage(event, String.valueOf(args[0])); } @Override public void onError(SocketIOException socketIOException) { Log.d(TAG, "Socket エラー発生しました"); socketIOException.printStackTrace(); AppActivity.nativeOnSocketError(socketIOException.getLocalizedMessage()); } }; }