如何将 C++ 回调作为 android 中按钮的单击处理程序传递?
How to pass C++ callback as a click handler of a Button in android?
我正在构建一个 android 应用程序,我需要从 JNI 部分动态创建 UI 元素并将 C++ 函数指定为点击处理程序。
我定义了一个 java 函数来创建一个按钮并 return 它。
我有一个 C++ 函数,它调用 Java 函数并具有按钮的作业对象。现在我想指定另一个 C++ 函数作为此对象的点击处理程序。
Java:
public class MainActivity extends AppCompatActivity {
...
public View control(String text) {
Button bt = new Button(this);
bt.setText(text);
((ConstraintLayout)findViewById(R.id.layout_main)).addView(bt);
return bt;
}
}
C++:
extern "C" JNIEXPORT void JNICALL Java_com_shaidin_ebellum_MainActivity_start(JNIEnv *env, jobject me)
{
jmethodID jControl = env->GetMethodID(env->GetObjectClass(me), "control", "(Ljava/lang/String;)Landroid/view/View;");
jstring jText = env->NewStringUTF("test");
jobject jView = env->CallObjectMethod(me, jControl, jText);
// How to add listener here?
env->DeleteLocalRef(jView_);
env->DeleteLocalRef(jText);
}
在这个问题中
Android UI in C++
JNIpp 库
https://github.com/DmitrySkiba/itoa-jnipp
已经介绍并且它完全符合我的要求,但我想自己实现它,因为我不想在我的项目中添加那么多代码。此外,该库使用嵌套宏实现了复杂的实现,因此很难了解它的工作原理。
使 Java 对象回调 C++ 代码的过程几乎总是相同的;使 Java 对象存储某种对本机对象的引用(通常是指针),并将该指针传递回可以调用适当函数的 C++ 代码。
潜在的实现可能如下所示:
MyOnClickListener.java
public class MyOnClickListener implements View.OnClickListener {
private final long nativePeer;
public MyOnClickListener(final long nativePeer) {
this.nativePeer = nativePeer;
}
@Override
public void onClick(View v) {
OnClick(nativePeer);
}
@Override
public void finalize() {
Release(nativePeer);
}
private static native void OnClick(final long peer);
private static native void Release(final long peer);
}
C++
extern "C" JNIEXPORT void Java_com_example_myapp_MyOnClickListener_OnClick(
JNIEnv *env,
jclass clazz,
jlong nativePeer) {
auto f = reinterpret_cast<std::function<void(void)>*>(nativePeer);
(*f)();
}
extern "C" JNIEXPORT void Java_com_example_myapp_MyOnClickListener_Release(
JNIEnv *env,
jclass clazz,
jlong nativePeer) {
auto f = reinterpret_cast<std::function<void(void)>*>(nativePeer);
delete f;
}
...
// Setting the OnClickListener
// We allocate this object with `new` since we need it to remain alive
// until the Java code no longer needs it. It is the responsibility of
// the Java code to ensure that the memory gets freed.
auto callback = new std::function<void(void)>([] {
__android_log_print(ANDROID_LOG_WARN, "MyOnClickListener", "Hello from native onClick!");
});
jclass listener_clazz = env->FindClass("com/example/myapp/MyOnClickListener");
jmethodID new_listener = env->GetMethodID(listener_clazz, "<init>", "(J)V");
jobject listener = env->NewObject(listener_clazz, new_listener, callback);
jmethodID set_onclicklistener = env->GetMethodID(button_clazz, "setOnClickListener", "(Landroid/view/View$OnClickListener;)V");
env->CallVoidMethod(button, set_onclicklistener, listener);
注意 1:在您的真实代码中,您当然应该添加适当的错误处理,而不是盲目地假设每个 JNI 调用都会成功。
注 2: 当您不再需要本机时,您可能希望显式调用 Release
函数而不是依赖 finalize
被调用对等对象。
注 3: 我在示例中使用了指向 std::function
的指针。您可能希望使用指向您自己的一些 class 的指针。这完全由您决定。
我正在构建一个 android 应用程序,我需要从 JNI 部分动态创建 UI 元素并将 C++ 函数指定为点击处理程序。
我定义了一个 java 函数来创建一个按钮并 return 它。 我有一个 C++ 函数,它调用 Java 函数并具有按钮的作业对象。现在我想指定另一个 C++ 函数作为此对象的点击处理程序。
Java:
public class MainActivity extends AppCompatActivity {
...
public View control(String text) {
Button bt = new Button(this);
bt.setText(text);
((ConstraintLayout)findViewById(R.id.layout_main)).addView(bt);
return bt;
}
}
C++:
extern "C" JNIEXPORT void JNICALL Java_com_shaidin_ebellum_MainActivity_start(JNIEnv *env, jobject me)
{
jmethodID jControl = env->GetMethodID(env->GetObjectClass(me), "control", "(Ljava/lang/String;)Landroid/view/View;");
jstring jText = env->NewStringUTF("test");
jobject jView = env->CallObjectMethod(me, jControl, jText);
// How to add listener here?
env->DeleteLocalRef(jView_);
env->DeleteLocalRef(jText);
}
在这个问题中 Android UI in C++ JNIpp 库 https://github.com/DmitrySkiba/itoa-jnipp 已经介绍并且它完全符合我的要求,但我想自己实现它,因为我不想在我的项目中添加那么多代码。此外,该库使用嵌套宏实现了复杂的实现,因此很难了解它的工作原理。
使 Java 对象回调 C++ 代码的过程几乎总是相同的;使 Java 对象存储某种对本机对象的引用(通常是指针),并将该指针传递回可以调用适当函数的 C++ 代码。
潜在的实现可能如下所示:
MyOnClickListener.java
public class MyOnClickListener implements View.OnClickListener {
private final long nativePeer;
public MyOnClickListener(final long nativePeer) {
this.nativePeer = nativePeer;
}
@Override
public void onClick(View v) {
OnClick(nativePeer);
}
@Override
public void finalize() {
Release(nativePeer);
}
private static native void OnClick(final long peer);
private static native void Release(final long peer);
}
C++
extern "C" JNIEXPORT void Java_com_example_myapp_MyOnClickListener_OnClick(
JNIEnv *env,
jclass clazz,
jlong nativePeer) {
auto f = reinterpret_cast<std::function<void(void)>*>(nativePeer);
(*f)();
}
extern "C" JNIEXPORT void Java_com_example_myapp_MyOnClickListener_Release(
JNIEnv *env,
jclass clazz,
jlong nativePeer) {
auto f = reinterpret_cast<std::function<void(void)>*>(nativePeer);
delete f;
}
...
// Setting the OnClickListener
// We allocate this object with `new` since we need it to remain alive
// until the Java code no longer needs it. It is the responsibility of
// the Java code to ensure that the memory gets freed.
auto callback = new std::function<void(void)>([] {
__android_log_print(ANDROID_LOG_WARN, "MyOnClickListener", "Hello from native onClick!");
});
jclass listener_clazz = env->FindClass("com/example/myapp/MyOnClickListener");
jmethodID new_listener = env->GetMethodID(listener_clazz, "<init>", "(J)V");
jobject listener = env->NewObject(listener_clazz, new_listener, callback);
jmethodID set_onclicklistener = env->GetMethodID(button_clazz, "setOnClickListener", "(Landroid/view/View$OnClickListener;)V");
env->CallVoidMethod(button, set_onclicklistener, listener);
注意 1:在您的真实代码中,您当然应该添加适当的错误处理,而不是盲目地假设每个 JNI 调用都会成功。
注 2: 当您不再需要本机时,您可能希望显式调用 Release
函数而不是依赖 finalize
被调用对等对象。
注 3: 我在示例中使用了指向 std::function
的指针。您可能希望使用指向您自己的一些 class 的指针。这完全由您决定。