Android JNI - 在 Android UI 线程上从 C++ 调用函数

Android JNI - Call function on Android UI thread from C++

我们的游戏引擎 Cocos2d-x 在 android 上独立运行 non-Java-UI-thread。我们需要通过 Android UI thread.

上的 JNIC++ 调用某些 Java 函数

为了调用 JNI-Functions,我们使用 JNIHelper。h/cpp 来自此处 (GitHub): JniHelper.h, JniHelper.cpp

例如这个 C++ 代码:

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

理想情况下,我们希望所有这些调用都发生在 Android UI thread 上,并将 std::function 作为参数传递给 return 上的 return 值23=] 函数调用完成后再次调用。

调用函数的理想方式:

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());
});

但也有许多调用没有任何 return 值,因此对于那些调用它们应该更容易在 java 线程上调用它们。

到 运行 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方法class.接下来你必须创建一个 Handler 的实例 class (new Handler(mainLooper in 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) 以便稍后使用。 您将不得不编写一些 Java 来实现 Runnable 接口,因此这段代码进入 Java:

package my.package;

import java.lang.Runnable;

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

如你所见,运行() 方法是原生的,所以我们可以用 C++ 实现它,如下所示:

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

现在你必须在 C++ 中获取 Runner class 及其构造函数:

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

将 运行nerClass (jclass) 和 运行nerConstructor (jmethodID) 存储在某处以备后用。您要做的最后一件事实际上是创建 Runner 的实例 class 并 post 它到处理程序:

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

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

我在 Ouzel engines 代码中所做的是创建一个 std::function 的队列并用互斥锁保护它。每当我需要在 Android UI 线程上执行 std::function 时,我将 std::function 实例添加到队列中并将其从队列中弹出并在本机方法中执行( Java_my_package_Runner_run).

这是最接近不编写 Java 代码的情况(您将不得不编写 6 行代码来实现 Runnable 接口)。

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

Android NDK 提供了极其轻量级和高效的方式来 post 您的本机代码到任意循环程序。关键是您应该向循环器提供任意文件描述符并指定您感兴趣的文件事件(输入、输出等)。在引擎盖下,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 对中看到的那样 - 消息在主线程上接收。当然,您可能会发送比单字节消息更复杂的消息。

根据@Sergio的回答,这里有一个简单的包装器NativeHandler,它可以将函数、函数对象和lambda作为参数,尝试模仿android.os.Handler

的行为
class NativeHandler {
public:
    static constexpr auto TAG = "NativeHandler";
    static NativeHandler* forCurrentThread() {
        return new NativeHandler;
    }

    template<typename FUNC, typename... ARGS>
    bool post(FUNC&& func, ARGS&&... args) {
        auto callable = new Callable(func, std::forward<ARGS>(args)...);
        write(_pipeFDS[1], &callable, sizeof(decltype(callable)));
        return true;
    }

    NativeHandler(const NativeHandler&) = delete;
    NativeHandler(NativeHandler&&) = delete;
    NativeHandler& operator=(const NativeHandler&) = delete;
    NativeHandler& operator=(NativeHandler&&) = delete;
    virtual ~NativeHandler() {
        ALooper_removeFd(_looper, _pipeFDS[0]);
        ALooper_release(_looper);
        close(_pipeFDS[0]);
        close(_pipeFDS[1]);
    }

private:
    class Callable {
    public:
        void call() {
            if (_function) _function();
        }

        template<typename FUNC, typename... ARGS>
        Callable(FUNC func, ARGS... args) : _function(std::bind(func, args...)) {}

        Callable() = delete;
        Callable(const Callable&) = delete;
        Callable(Callable&&) = delete;
        Callable operator=(const Callable&) = delete;
        Callable operator=(Callable&&) = delete;
        virtual ~Callable() {}
    private:
        std::function<void()> _function;
    };

    NativeHandler() {
        if (pipe(_pipeFDS) != 0) {
            throw std::bad_alloc();
        }
        _looper = ALooper_forThread();
        ALooper_acquire(_looper);
        if (ALooper_addFd(_looper, _pipeFDS[0], ALOOPER_POLL_CALLBACK,
                          ALOOPER_EVENT_INPUT, _looperCallback, nullptr) == -1) {
            throw std::bad_alloc();
        }
    };

    ALooper* _looper;
    int _pipeFDS[2];
    static int _looperCallback(int fd, int events, void* data) {
        void* buf = new char[sizeof(Callable*)];
        ssize_t nr = read(fd, buf, sizeof(Callable*));
        Callable* callable = *((Callable**)buf);
        __android_log_print(ANDROID_LOG_INFO, "Callable", "read size is %d %p", nr, callable);
        callable->call();
        delete[] buf;
        return 1;
    }
};

然后是使用示例,希望它对任何想要在 JNI 中使用 android java api 处理程序的类似行为的人有所帮助。

void f(char c, short s) {
    __android_log_print(ANDROID_LOG_DEBUG, NativeHandler::TAG, "%s c = %c, s = %d", __FUNCTION__, c, s);
}

struct Task {
    void operator()(int i, double d) {
        __android_log_print(ANDROID_LOG_DEBUG, NativeHandler::TAG, "Task i = %d, d = %f", i, d);
    }
};

// ...
auto handler = NativeHandler::forCurrentThread();
std::thread worker([handler]() {
    handler->post([](int i, double d, void* p) {
        __android_log_print(ANDROID_LOG_DEBUG, "NativeHandler", "i = %d, d = %f, p = %p", i, d, p);
    }, 100, -123.4, nullptr);

    handler->post(f, 'c', 128);
    handler->post(Task(), 123, 3.1415926);
});
worker.detach();

另一种选择是使用 Arcana.cpp C++ 库,其中包含一个 Android 基于 Looper 的 "scheduler." 在最简单的形式中,您可以像这样使用它:

#include <arcana/threading/task_schedulers.h>

void SomeFunctionCalledFromUIThread()
{
  // Note: The '64' below is the max size of the callables passed to the scheduler.
  // This is done to reduce allocations and make schedulers more efficient.
  auto looper_scheduler = arcana::looper_scheduler<64>::get_for_current_thread();

  // Get on a background thread to test getting back on the UI thread.
  std::thread worker([looper_scheduler = std::move(looper_scheduler)]() {
    looper_scheduler([]() {
      // Do something on the UI (looper) thread
    });
  });
}

调度器是 Arcana.cpp 中的通用构造,也用于低开销跨平台异步任务系统,因此如果您选择使用它,则可以使用此调度器进行典型的异步任务编程:

#include <arcana/threading/task_schedulers.h>
#include <arcana/threading/task.h>
#include <arcana/threading/dispatcher.h>

// Schedulers need to outlive task chains, so imagine m_looper_scheduler and m_background_dispatcher are created and stored from some constructor.
// "Dispatchers" in Arcana.cpp are a class of schedulers that own their own work queue.

arcana::task<void, std::exception_ptr> SomeFunctionCalledFromUIThread() {
  return arcana::make_task(m_background_dispatcher, arcana::cancellation::none(), []() {
    // Do something on a background thread (via background_dispatcher).
  }).then(m_looper_scheduler, arcana::cancellation::none(), []() {
    // Do something on the UI thread (via looper_scheduler).
  });
}

如果你想更大胆一点,C++ 协同程序可以与任务一起使用,或者直接与调度程序一起使用:

#include <arcana/threading/task_schedulers.h>
#include <arcana/threading/task.h>
#include <arcana/threading/dispatcher.h>
#include <arcana/threading/coroutine.h>

arcana::task<void, std::exception_ptr> SomeFunctionCalledFromUIThread() { 
  auto looper_scheduler = arcana::looper_scheduler<64>::get_for_current_thread();
  arcana::background_dispatcher<64> background_dispatcher;

  // Code executing here is on the UI thread (since the function is called from the UI thread).

  co_await arcana::switch_to(background_dispatcher);

  // Code executing here is on a background thread.

  co_await arcana::switch_to(looper_scheduler);

  // Code executing here is back on the UI thread.
}

您可以在此处阅读有关 Arcana.cpp 调度程序、任务和协程的更多信息:https://github.com/microsoft/arcana.cpp/blob/master/Source/Arcana.Tasks.md