【问题标题】:How do I post code to be run on the Android main thread from a separate thread in C++?如何从 C++ 中的单独线程发布要在 Android 主线程上运行的代码?
【发布时间】:2016-03-16 16:31:33
【问题描述】:

我在后台有一个在 C++ 中运行的单独线程,我希望它能够发布代码以在另一个已经运行 android.os.Looper 的线程(例如主线程)上运行。通过“发布”,我的意思类似于View#post,其中Runnable 被排入队列以在事件循环上运行。将要执行的代码也是用 C++ 编写的。

我找到了 ALooper API (http://developer.android.com/ndk/reference/group___looper.html),但文档不是很好,我不清楚是否让 ALooper 与目标线程相关联、添加另一个 FD 并发出信号,这将使我的代码保持正确的顺序在相对于其他排队的 Runnables 的事件队列中。

我宁愿不必通过 Java 并获得 Handler 等。这似乎没有必要,因为我尝试运行的代码和发布它的代码都是用 c++ 编写的。

【问题讨论】:

标签: android android-ndk


【解决方案1】:

一个线程只能有一个与之关联的 Looper,一个 Looper 只有一个消息队列,因此混合使用 Java 和本机回调将保持顺序。

因此,我认为今天的 Android 不存在任何保证post() 以特定顺序执行的合同义务,即

getHandler().post(new Runnable() {
    @Override
    public void run() {
        mTextView.setText("first");
    }
});
getHandler().post(new Runnable() {
    @Override
    public void run() {
        mTextView.setText("second");
    }
});

不正式保证离开 mTextView 显示 。当两个帖子从不同的线程发布或延迟时,绝对没有什么是一成不变的。

您可以在很棒的blog post 中找到Android messaging and concurrency framework for native code development

更新

这是所需的证明。在处理一个不相关的问题时收到了下面的堆栈跟踪:

A/art: art/runtime/check_jni.cc:65]   native: #00 pc 0000484c  /system/lib/libbacktrace_libc++.so (UnwindCurrent::Unwind(unsigned int, ucontext*)+23)
A/art: art/runtime/check_jni.cc:65]   native: #01 pc 00003031  /system/lib/libbacktrace_libc++.so (Backtrace::Unwind(unsigned int, ucontext*)+8)
A/art: art/runtime/check_jni.cc:65]   native: #02 pc 002441f9  /system/lib/libart.so (art::DumpNativeStack(std::__1::basic_ostream<char, std::__1::char_traits<char> >&, int, char const*, art::mirror::ArtMethod*)+68)
A/art: art/runtime/check_jni.cc:65]   native: #03 pc 002285a1  /system/lib/libart.so (art::Thread::Dump(std::__1::basic_ostream<char, std::__1::char_traits<char> >&) const+144)
A/art: art/runtime/check_jni.cc:65]   native: #04 pc 000afe9b  /system/lib/libart.so (art::JniAbort(char const*, char const*)+582)
A/art: art/runtime/check_jni.cc:65]   native: #05 pc 000b05d1  /system/lib/libart.so (art::JniAbortF(char const*, char const*, ...)+60)
A/art: art/runtime/check_jni.cc:65]   native: #06 pc 000b299d  /system/lib/libart.so (art::ScopedCheck::Check(bool, char const*, ...) (.constprop.129)+672)
A/art: art/runtime/check_jni.cc:65]   native: #07 pc 000bab87  /system/lib/libart.so (art::CheckJNI::CallVoidMethodV(_JNIEnv*, _jobject*, _jmethodID*, std::__va_list)+50)
A/art: art/runtime/check_jni.cc:65]   native: #08 pc 00060817  /system/lib/libandroid_runtime.so (???)
A/art: art/runtime/check_jni.cc:65]   native: #09 pc 000a5b29  /system/lib/libandroid_runtime.so (???)
A/art: art/runtime/check_jni.cc:65]   native: #10 pc 00010fd7  /system/lib/libutils.so (android::Looper::pollInner(int)+482)
A/art: art/runtime/check_jni.cc:65]   native: #11 pc 00011081  /system/lib/libutils.so (android::Looper::pollOnce(int, int*, int*, void**)+92)
A/art: art/runtime/check_jni.cc:65]   native: #12 pc 0007fbe5  /system/lib/libandroid_runtime.so (android::NativeMessageQueue::pollOnce(_JNIEnv*, int)+22)
A/art: art/runtime/check_jni.cc:65]   native: #13 pc 00051b8b  /system/framework/arm/boot.oat (Java_android_os_MessageQueue_nativePollOnce__JI+102)
A/art: art/runtime/check_jni.cc:65]   at android.os.MessageQueue.nativePollOnce(Native method)
A/art: art/runtime/check_jni.cc:65]   at android.os.MessageQueue.next(MessageQueue.java:143)
A/art: art/runtime/check_jni.cc:65]   at android.os.Looper.loop(Looper.java:122)
A/art: art/runtime/check_jni.cc:65]   at android.app.ActivityThread.main(ActivityThread.java:5411)
A/art: art/runtime/check_jni.cc:65]   at java.lang.reflect.Method.invoke!(Native method)
A/art: art/runtime/check_jni.cc:65]   at java.lang.reflect.Method.invoke(Method.java:372)
A/art: art/runtime/check_jni.cc:65]   at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:916)
A/art: art/runtime/check_jni.cc:65]   at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:709)

【讨论】:

  • c++ 中的 ALooper API 不涉及 MessageQueue 问题。从同一个线程调用来自 Java 的两个 posts(无延迟)的情况确实保证了顺序。
  • 您在哪里找到的订购的正式保证?
  • ALooper 只不过是 android::os::Looper 类的薄包装,它拥有一个 android::os::MessageLoop。您可以通过googlesource 中系统 libutils.so 的来源来判断自己(请注意,它最近从 frameworks/native 移至 system/core)。
  • ALooper 不是围绕 android.os.Looper 的瘦包装器,它的名字真的很糟糕。我通过阅读代码得出的订购保证。
  • 请查看 AOSP 中的implementation。只需澄清一点:不是 Java 类 android.os.Looper,而是 C++ 类 android::os::Looper,除其他外,由SurfaceFlinger。
【解决方案2】:

你需要一个已经在主线程中执行的函数。如果你在那里调用ALooper_forThread()ALooper_prepare(),你会得到一个指向与主线程关联的looper 的指针。记得调用ALooper_acquire(),这样它就可以在不同的线程之间共享了。

【讨论】:

  • 主线程比较特殊,保证你的JNI库加载时有一个Looper。因此,使用从 fd 读取的 int handler(int fd, int, void* data) 调用 pipe2(messagePipe, O_NONBLOCK | O_CLOEXEC); ALooper_addFd(ALooper_forThread(), messagePipe[0], 0, ALOOPER_EVENT_INPUT, handler, this); 就足够了(使用相同大小的消息更容易),并选择在 UI 线程上执行的回调。 本地版本的 Handler.sendEmptyMessage() 可以像 write(messagePipe[1], &amp;what, 4) 一样简单。
【解决方案3】:

这可以帮助你https://groups.google.com/forum/#!topic/android-ndk/v2OITtaZTes

但是通过java端的处理程序很容易实现,通过jni调用在native和java之间来回发送和处理消息。

【讨论】:

    【解决方案4】:

    您必须通过 Java,因为 android.os.Looper 未在本机代码中实现(至少在 the currently most recent commit 中)。

    我没有足够的 NDK 经验来快速键入所需的样板,但显而易见的选择似乎是基于本机代码创建一个 java Runnable 并将其发送到 looper。

    不那么明显的解决方案是直接对线程的MessageQueue进行操作。一旦你有了它的引用,你就可以在那里注册一个本地管道的一端,并将消息写入另一端;管道基本上具有Handler 的功能,但在本机代码上。从技术上讲,您的代码仍然是从 Java 调用的,但您不需要开销。我没有找到关于整个事情的太多文档,但this thread 可能是一个很好的起点。¹

    但是,完全有可能您的代码实际上不必从主线程中调用,或者有另一个选项可以在不通过 Java 的情况下解决您的问题。但是,这取决于您要解决的问题。


    注意:我假设主线程场景。如果您可以在要部署的线程中使用基于本机代码的循环器,那么您有更多选择。

    ¹ ALooper 也有可能用于某种客户端模式来执行此操作。对此非常不确定。

    【讨论】:

    • 你说得对,android.os.Looper不使用native方法,但是系统库libutils.so包含native类 android::os::Looperandroid::os::MessageLoop。 NDK ALooper API 通过这些类实现(参见comments in the source code)。例如,这些在 libgui.so 中使用,其中负责SurfaceFlinger
    • @AlexCohn OP 指定使用 android.os.Looper。是否已在某处澄清 android::os::Looper 是一个选项?如果是这样,我错过了 - 这就是我打算用“如果你可以使用基于本机代码的循环器”来表达的意思,但也许这还不够清楚?
    • 你能想出一个实验来证明 ALooper 不能用于将可调用对象发布到 UI 线程吗?
    • @AlexCohn 再想一想,我不再确定Runnable 是最好的解决方案——尽管它可能是最简单的解决方案。如果可以从 C++ 获得 Java-Looper 后面的 MessageQueue(可能有,还没有研究过),那么 C++ 代码可以在那里注册并从 Looper 调用。从技术上讲,它仍然会通过 Java,但不再需要处理围绕它的样板文件,并且可能会获得一些性能。
    • 重读Dianne的帖子,相信ALooper是安全的。 Native Activity 使用 ALooper 来处理它的生命周期。 Native Activity 背后确实有一些 Java 代码。您甚至可以扩展 Native Activity,它会继续工作。
    【解决方案5】:

    如果你想从另一个线程在主线程中做一些东西,我建议你使用 runOnUiThread 函数。 Android 中的主线程是用户界面线程。不知道能不能在ndk代码中使用这个函数。

    代码示例如下:

    private void runOnMainThread() {

    runOnUiThread(new Runnable(){       
       public void run() {            
       try {
           // do some stuffs
       } catch (final Exception ex) {
           // handle the possible exception           
       }      
    }         });   }
    

    无论如何,我建议您阅读以下链接:link1link2link3

    希望对你有帮助。

    【讨论】:

    猜你喜欢
    • 2014-02-22
    • 2019-03-09
    • 1970-01-01
    • 1970-01-01
    • 2018-07-24
    • 2012-06-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多