2017-06-28 126 views
2

我们的游戏引擎Cocos2d-x在其本身的非Java-UI线程上在android上原生运行。我们需要在Android UI线程上通过JNI从C++调用某些Java函数。Android JNI - 从C++的Android UI线程调用函数

对于JNI调用的功能,我们使用的是从这里JNIHelper.h/CPP: https://github.com/cocos2d/cocos2d-x/blob/v3/cocos/platform/android/jni/JniHelper.h https://github.com/cocos2d/cocos2d-x/blob/v3/cocos/platform/android/jni/JniHelper.cpp

例如这个C++代码:

auto retVal = JniHelper::callStaticStringMethod("org/utils/Facebook", "getFacebookTokenString");

理想情况下,我们” d喜欢在Android UI线程上发生所有这些调用,并在函数调用完成后再次传递std :: function作为参数,并使用Cocos2d-x-thread上的返回值再次调用该参数。

理想的方式调用该函数:

auto retVal = JniHelper::callStaticStringMethod("org/utils/Facebook", "getFacebookTokenString", [=](std::string retVal) { 
     printf("This is the retval on the C++ caller thread again: %s", retVal.c_str()); 
}); 

但也有许多人呼吁没有任何返回值,所以对于那些应该比较容易,只是称他们为Java线程上。

+0

据我所见,native-to-java调用应该在一些非主线程上发出?然后你想发送调用主线程,并等待结果准备好了吗? – Sergio

+0

感谢您提出的问题,我想了很多,并编辑了这个问题,我将如何将它想象为理想的工作。是否有意义? – keyboard

回答

5

正如@Elviss所提到的 - 要将您的代码发布到主线程,您应该使用Looper。实际上,这可以在不额外处理JNI和创建自定义java.lang.Runnable并通过复杂的JNI东西发布的情况下完成。

Android NDK提供了一种非常轻量级且高效的方式将您的本机代码发布到任意循环游戏。关键是你应该为循环提供任意文件描述符,并指定你感兴趣的文件事件(输入,输出,等等)。在引擎盖下,looper将轮询该文件描述符,一旦事件变为可用 - 它将在适当的线程上运行您的回调。

有最小示例(没有错误检查和拆解):

#include <android/looper.h> 
#include <unistd.h> 

#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, "sergik", __VA_ARGS__) 

static ALooper* mainThreadLooper; 
static int messagePipe[2]; 

static int looperCallback(int fd, int events, void* data); 

void someJniFuncThatYouShouldCallOnceOnMainThread() { 
    mainThreadLooper = ALooper_forThread(); // get looper for this thread 
    ALooper_acquire(mainThreadLooper); // add reference to keep object alive 
    pipe(messagePipe); //create send-receive pipe 
    // listen for pipe read end, if there is something to read 
    // - notify via provided callback on main thread 
    ALooper_addFd(mainThreadLooper, messagePipe[0], 
        0, ALOOPER_EVENT_INPUT, looperCallback, nullptr); 
    LOGI("fd is registered");  

    // send few messages from arbitrary thread 
    std::thread worker([]() { 
     for(char msg = 100; msg < 110; msg++) { 
      LOGI("send message #%d", msg); 
      write(messagePipe[1], &msg, 1); 
      sleep(1); 
     } 
    }); 
    worker.detach(); 
} 

// this will be called on main thread 
static int looperCallback(int fd, int events, void* data) { 
    char msg; 
    read(fd, &msg, 1); // read message from pipe 
    LOGI("got message #%d", msg); 
    return 1; // continue listening for events 
} 

这段代码产生下一个输出:

06-28 23:28:27.076 30930-30930/? I/sergik: fd is registered 
06-28 23:28:27.076 30930-30945/? I/sergik: send message #100 
06-28 23:28:27.089 30930-30930/? I/sergik: got message #100 
06-28 23:28:28.077 30930-30945/? I/sergik: send message #101 
06-28 23:28:28.077 30930-30930/? I/sergik: got message #101 
06-28 23:28:29.077 30930-30945/? I/sergik: send message #102 
06-28 23:28:29.078 30930-30930/? I/sergik: got message #102 
06-28 23:28:30.078 30930-30945/? I/sergik: send message #103 
06-28 23:28:30.078 30930-30930/? I/sergik: got message #103 
06-28 23:28:31.079 30930-30945/? I/sergik: send message #104 
06-28 23:28:31.079 30930-30930/? I/sergik: got message #104 
06-28 23:28:32.079 30930-30945/? I/sergik: send message #105 
06-28 23:28:32.080 30930-30930/? I/sergik: got message #105 
06-28 23:28:33.080 30930-30945/? I/sergik: send message #106 
06-28 23:28:33.080 30930-30930/? I/sergik: got message #106 
06-28 23:28:34.081 30930-30945/? I/sergik: send message #107 
06-28 23:28:34.081 30930-30930/? I/sergik: got message #107 
06-28 23:28:35.081 30930-30945/? I/sergik: send message #108 
06-28 23:28:35.082 30930-30930/? I/sergik: got message #108 
06-28 23:28:36.082 30930-30945/? I/sergik: send message #109 
06-28 23:28:36.083 30930-30930/? I/sergik: got message #109 

当你从PID-TID对看到 - 消息接收的上主线程。当然,你可能会发送比单字节消息更复杂的内容。

+0

看起来很酷,谢谢!主线程是指Java-Main-Thread,对吧?你现在只交换字符串?我想念什么?在文中你说过可以执行任意代码。 – keyboard

+0

@keyboard是的我的意思是Java主线程(实际上主线程对于Java和本地代码都是相同的)。你可以从'Activity.onCreate()'例如这个例子没有交换任何东西:-)。它只是从工作线程向主(UI)发送数字'100 ... 109'。它们可能会作为消息ID被威胁。如果你需要 - 你可以发送一些序列化的对象。唯一的是设计序列化 - 写入 - 读取 - 反序列化流程。 – Sergio

1

运行在Android UI的C++代码(主)线程,你将不得不使用Android弯针(activity.getMainLooper()或Looper.getMainLooper()在Java中):

jmethodID getMainLooperMethod = jniEnv->GetMethodID(mainActivityClass, "getMainLooper", "()Landroid/os/Looper;"); 
jobject mainLooper = jniEnv->CallObjectMethod(mainActivity, getMainLooperMethod); 

“mainActivity”是android.app.Activity的一个实例,它是从Java传递给JNI的,但您也可以简单地使用Looper类的静态getMainLooper方法。接下来,您要创建处理程序类的(新的处理程序(mainLooper在Java中)的实例:

jclass handlerClass = jniEnv->FindClass("android/os/Handler"); 
jmethodID handlerConstructor = jniEnv->GetMethodID(handlerClass, "<init>", "(Landroid/os/Looper;)V"); 
postMethod = jniEnv->GetMethodID(handlerClass, "post", "(Ljava/lang/Runnable;)Z"); 
handler = jniEnv->NewObject(handlerClass, handlerConstructor, mainLooper); 
handler = jniEnv->NewGlobalRef(handler); 

要知道,你有存储处理器(jobject)后使用它 你必须写一个爪哇位来实现Runnable接口,因此这一代码进入Java中:

package my.package; 

import java.lang.Runnable; 

public class Runner implements Runnable 
{ 
    native public void run(); 
} 

正如你所看到的run()方法是本地的,所以我们可以用C实现它++如下:

extern "C" JNIEXPORT void JNICALL 
Java_my_package_Runner_run(JNIEnv*, jclass) 
{ 
    // here goes your native code 
} 

现在,你必须获得亚军类及其构造函数C++:

runnerClass = jniEnv->FindClass("org/ouzelengine/Runner"); 
runnerClass = static_cast<jclass>(jniEnv->NewGlobalRef(runnerClass)); 
runnerConstructor = jniEnv->GetMethodID(runnerClass, "<init>", "()V"); 

商店runnerClass(JCLASS)和runnerConstructor(jmethodID)的地方以备后用。你必须做的最后一件事实际上是创建亚军类的实例,并将其发布到处理程序:

jobject runner = jniEnv->NewObject(runnerClass, runnerConstructor); 

if (!jniEnv->CallBooleanMethod(handler, postMethod, runner)) 
{ 
    // something wrong happened 
} 

我在Ouzel engines代码做的是我创建的std ::函数的队列中,并且保护它与互斥体。无论何时我需要在Android UI线程上执行std :: function,我都会将std :: function实例添加到队列中,并将其从队列中弹出并在本地方法(Java_my_package_Runner_run)中执行。

这是最接近你写不用Java代码(你将不得不写6行来实现Runnable接口)。

+0

为什么你想让你的生活更难?只需从你的C++后台线程和你的java回调中调用一个java回调方法(将任何数据作为参数传递给参数),通过调用'Handler#post' /'Handler#sendMessage'/whatever – pskink

+0

我的引擎是因为Java驻留在应用程序的“用户部分”中,而不是我的引擎,所以我希望尽可能使用Java。 –

+1

绝对没有必要利用JNI与主要活套进行交互。有NDK头文件''将接口暴露给本地looper。看到我的答案。 – Sergio