【问题标题】:Using NDK from C++ updating Android UI使用 C++ 中的 NDK 更新 Android UI
【发布时间】:2014-01-27 14:41:31
【问题描述】:

我的问题与这篇文章直接相关:https://groups.google.com/forum/#!topic/android-ndk/291sBdkITyI

基本上,我有一个用 C++ 编写的应用程序,它使用 NDK 和一个基本的 Android(活动)编译。我有一个文本视图(在 Java 中),当 c++ 端发生某些事情时需要更新它(例如状态更改)。我想从 C++ 调用 Java 并在状态更改时更新 textview。

在上面的链接中,他们使用的代码是(可能是伪代码):

public class Example extends Activity{    

    Handler handler = new Handler() {

         @Override
         public void handleMessage(Message msg) {
             this.currentDownloadField.setText(""+ msg.what);
         }

    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        // whatever
    }   

    public static void sendMessage(int  id){ 
         handler.sendEmptyMessage(id); // just the what() is filled with the id
    }
}

从 C++ 调用会是

void sendMessage(char* buffer, int bufferlen) {
    JNIEnv *env = NULL;
    jmethodID mid = NULL;
    jbyteArray message;

    jint res = (jjvm->AttachCurrentThread(&jjvm, &env, NULL));

    if (res >= 0) {
        message = (*env)->NewByteArray(env, bufferlen);
        (*env)->SetByteArrayRegion(env, message, 0, bufferlen, (const jbyte *) ((BYTE*) buffer));
        mid = (*env)->GetStaticMethodID(env, jcls, "sendMessage", "([B)V");

        // Mid <= 0 ? Not found : OK
        if (mid > 0) {
        (*env)->CallStaticVoidMethod(env, jcls, mid, message);
        }
    }
}

问题是在静态函数的活动中使用“处理程序”不起作用(因为它不是静态的)。如果它是静态的,那么“this.currentDownloadField”如何被引用?

我也尝试过从 c++ 调用一个公共函数

public void update(String message) {
    Log.i("logging", "Hit here")
    mMyTextField.setText(message);
}

当 C++ 调用 Java 函数“更新”时,日志命中(在 logcat 中)但文本视图没有更新。也许这是线程的问题,但我不知道如何正确更新文本字段。

另一种选择是轮询并让 Java 调用 C++ 来读取变量(状态),但这很乏味且不是良好的编程习惯。

有解决这个问题的建议吗?

提前致谢。

【问题讨论】:

    标签: java android c++ user-interface android-ndk


    【解决方案1】:

    你是对的,你必须从 UI 线程调用mMyTextField.setText()。 Android Java 类提供了多种方法来做到这一点,例如View.post()Handler.post()Handler.sendMessage()Activity.runOnUiThread()。这些方法都不是静态,它们都需要一个实时的 this 对象才能工作。

    根据您的目的,有不同的方法可以解决此问题。可能对代码的最小更改如下:

    public class Example extends Activity {
    
        private static Handler staticHandler;
        private TextView currentDownloadField;
    
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            Handler handler = new Handler() {
                @Override
                public void handleMessage(Message msg) {
                    this.currentDownloadField.setText(""+ msg.what);
                }
            }
            staticHandler = handler;
            setContentView(R.layout.main_view);
            currentDownloadField = (TextView)findViewById(R.id.download_field);
        }
    
        @Override
        protected void onDestroy() {
            staticHandler = null;
        }
    
        public static void sendMessage(int id) {
            staticHandler.sendEmptyMessage(id); // just the what() is filled with the id
        }
    }
    

    在上面的代码中,为了简洁,错误检查被跳过了。

    或者,您可以将 Activity(或处理程序,或 downloadFiled)的引用传递给 C 库,而不是调用 CallStaticVoidMethod(),而是使用对象全局引用并使用 CallVoidMethod(env, jactivityObj, mid, message)

    PS我只是想知道,如果您将jbyteArray(在Java 中为byte[])发送到需要int 的回调方法,您希望系统如何工作? p>

    更新上面的简单代码会受到Handler Leak的影响,因此如果您想在生产中重用它,请包含static inner class wrapper around the Handler

    【讨论】:

    【解决方案2】:

    当您的 JNI C/C++ 函数被调用时,您将拥有其方法被调用的对象,以及 JNIEnv:

    JNIEXPORT void JNICALL Java_com_xxx_Yyy_myfunc(JNIEnv *, jobject);
    

    您可以调用该对象的非静态方法。

    如果您的 C++ 代码没有提供在其中传递额外 void* 的方法,只需将这些内容存储到静态变量中,并确保仅在 UI 线程上使用它。或者至少总是来自同一个线程。

    PS 您可以尝试通过线程本地存储等添加线程安全性。但是您无法控制线程的生命周期。由于不清楚的原因,您可能会遇到晦涩难懂的错误。因此,IMO 如果您只需要两个线程,则具有两对 (JNIEnv*, jobject) 内容的结构或数组。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-01-20
      • 1970-01-01
      • 2018-11-04
      • 2014-02-16
      • 2013-06-13
      • 1970-01-01
      相关资源
      最近更新 更多