相信所有使用cocos2d-x开发Android游戏的小伙伴们都遇过这样的问题:我们游戏逻辑在c++原生代码实现,但接入各类平台sdk时,却是java版本,于是写JNI接口占去了sdk接入的大部分时间。
背景交待完毕,进入正题。我们使用一个简单接口来使用JNI。以下接口实现比较粗糙,仅提供一个思路,实际操作中请结合自己项目灵活实现。
以下代码引入了c++11特性支持。
此功能主要涉及以下3个文件:
AndroidJNI.h
AndroidJNI.cpp
com.sample.x.AndroidJNI.java
// AndroidJNI.h
#pragma once
#include <string>
#include <functional>
#include <map>
namespace AndroidJNI
{
void callJNI(std::string name, std::string info, std::function<void (std::string)> onCallback);
void setKeptCallbacks(std::string name, std::function<void(std::string)> onCallback);
}
// AndroidJNI.cpp
#include "AndroidJNI.h"
#include "platform/android/jni/JniHelper.h"
#include "cocos2d.h"
using namespace cocos2d;
namespace AndroidJNI {
std::function<void (std::string)> popCallbacks(std::string name);
std::function<void (std::string)> getKeptCallbacks(std::string name);
}
void AndroidJNI::callJNI(std::string name, std::string data, std::function<void (std::string)> onCallback)
{
mOnCallbacks[name] = onCallback;
JniMethodInfo m;
bool isHave = JniHelper::getStaticMethodInfo(m, "com/sample/x/AndroidJNI", "Call", "(Ljava/lang/String;Ljava/lang/String;)V");
if(!isHave) throw "java method not found";
auto jname = m.env->NewStringUTF(name.data());
auto jdata = m.env->NewStringUTF(data.data());
m.env->CallStaticVoidMethod(m.classID, m.methodID, jname, jdata);
m.env->DeleteLocalRef(jname);
m.env->DeleteLocalRef(jdata);
}
void AndroidJNI::setKeptCallbacks(std::string name, std::function<void(std::string)> onCallback)
{
if(onCallback == nullptr)
{
mKeptCallbacks.erase(name);
}
else
{
mKeptCallbacks[name] = onCallback;
}
}
std::function<void (std::string)> AndroidJNI::popCallbacks(std::string name)
{
auto iter = mOnCallbacks.find(name);
if(iter == mOnCallbacks.end()) return nullptr;
auto func = iter->second;
mOnCallbacks.erase(iter);
return func;
}
std::function<void (std::string)> AndroidJNI::getKeptCallbacks(std::string name)
{
auto iter = mKeptCallbacks.find(name);
if(iter == mKeptCallbacks.end()) return nullptr;
auto func = iter->second;
return func;
}
extern "C" {
void Java_com_sample_x_AndroidJNI_OnResult(JNIEnv *env, jobject obj, jstring jname, jstring jdata)
{
std::string name = jname ? env->GetStringUTFChars(jname, nullptr) : "";
std::string data = jdata ? env->GetStringUTFChars(jdata, nullptr) : "";
auto func = AndroidJNI::popCallbacks(name);
if(!func)
{
func = AndroidJNI::getKeptCallbacks(name);
}
if(func)
{
Director::getInstance()->getScheduler()->performFunctionInCocosThread([func, data]() {
func(data);
});
}
else
{
// log error
}
}
}
// com.sample.x.AndroidJNI.java
package com.sample.x;
public class AndroidJNI
{
native private static void OnResult(final String name, final String data);
static public void Call(final String name, final String data)
{
MainStatic.RunInUI(new Runnable() {
//@Override
public void run()
{
if (name.equals("test"))
{
// Do any thing
OnResult("test", "result:ok");
}
}
});
}
}
在c++中通过callJNI方法来调用java接口,参数name为接口名称,info为接口参数,这两个值将传递到 java代码中的 Call(final String name, final String data)。java中根据调用的name,来作相关的操作。若需要传递多个参数可依需求将data改为c++中的std::vector<std::string>和java里的String[]相对应。当然也可对这里的string进行格式化来实现多个参数的传递。
callJNI最后一个参数是一个回调函数,java部分执行完工作后将结果通过回调函数传递回来。Java的OnResult方法的name参数与Call的name参数(即c++ callJNI的name参数)一致。
另一个c++方法是setKeptCallbacks,它设定一个回调函数,用来接收java端发起的通知。在c++端设定名称为name的回调后,java端可随时主动调用OnResult方法来将data传递到c++的name所对应的函数中。回调函数的string参数与OnResult的data参数相对应。
接下来在编写c++代码时,我们可以不用再考虑jni的东西了。另外如果需要在windows或mac下开发,可以实现一个AndroidJNI.cpp的windows版或iOS版,在其它平台上进行c++代码(包含android分发内容)的调试。
示例:
// c++
AndroidJNI::callJNI("getArgCount", "arg1\narg2\narg3", [](std::string res) { // 简单用换行分隔多个参数,需要保证每个参数不包含换行符
printf("the count is %s", res.data());
});
// Java
package com.sample.x;
public class AndroidJNI
{
native private static void OnResult(final String name, final String data);
static public void Call(final String name, final String data)
{
MainStatic.RunInUI(new Runnable() {
//@Override
public void run()
{
if (name.equals("getArgCount"))
{
// 这块代码可使用一个代理类接口来处理,如此一来这个文件就不需要再修改了
String[] args = data.split("\n");
OnResult("test", "result:" + args == null? 0 : args.length);
}
}
});
}
}