代码之家  ›  专栏  ›  技术社区  ›  keyboard

Android JNI-来自C的Android UI线程上的调用函数++

  •  12
  • keyboard  · 技术社区  · 7 年前

    我们的游戏引擎 Cocos2d-x non-Java-UI-thread .我们需要从 C++ 通过 JNI Android UI thread

    用于呼叫 JNI-Functions ,我们正在使用JNIHelper。此处的h/cpp(GitHub): JniHelper.h , JniHelper.cpp

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

    理想情况下,我们希望所有这些通话都在 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线程上调用它们应该更容易。

    4 回复  |  直到 5 年前
        1
  •  15
  •   Sergio    7 年前

    Looper 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对中看到的,消息是在主线程上接收的。当然,您可能会发送比单字节消息更复杂的消息。

        2
  •  3
  •   Elviss Strazdins    7 年前

    要在Android UI(主)线程上运行C++代码,您必须使用Android的looper(activity.getMainLooper()或looper)。Java中的getMainLooper():

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

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

    您需要编写一些Java来实现Runnable接口,因此以下代码采用Java语言:

    package my.package;
    
    import java.lang.Runnable;
    
    public class Runner implements Runnable
    {
        native public void run();
    }
    

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

    现在,您必须在C++中获得Runner类及其构造函数:

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

    jobject runner = jniEnv->NewObject(runnerClass, runnerConstructor);
    
    if (!jniEnv->CallBooleanMethod(handler, postMethod, runner))
    {
        // something wrong happened
    }
    

    我在 Ouzel engines

    这是最接近不编写Java代码的情况(您必须编写6行代码才能实现可运行的接口)。

        3
  •  1
  •   marc_s    5 年前

    根据@Sergio的回答,这里有一个简单的包装器 NativeHandler 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();
    
        4
  •  1
  •   Ryan Tremblay    5 年前

    另一种选择是使用 Arcana.cpp C++库,其中包括一个基于Android Looper的“调度程序”。在其最简单的形式中,您可以这样使用它:

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

    #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.
    }
    

    https://github.com/microsoft/arcana.cpp/blob/master/Source/Arcana.Tasks.md