I am trying to repeatedly call a Java method from C++ inside an event handler that is executed several times along the lifetime of my program.
To do that I have the following code.
#pragma once
#include <jni.h>
static bool getJniEnv(JavaVM *vm, JNIEnv **env) {
bool didAttachThread = false;
*env = nullptr;
auto get_env_result = vm->GetEnv((void**)env, JNI_VERSION_1_6);
if (get_env_result == JNI_EDETACHED) {
if (vm->AttachCurrentThread(env, NULL) == JNI_OK) {
didAttachThread = true;
} else {
throw;
}
} else if (get_env_result == JNI_EVERSION) {
// Unsupported JNI version.
throw;
}
return didAttachThread;
}
class ScopedEnv {
public:
ScopedEnv(JavaVM *vm) : vm(vm), attachedToVm(false) {
attachedToVm = getJniEnv(vm, &env);
}
ScopedEnv(const ScopedEnv&) = delete;
ScopedEnv& operator=(const ScopedEnv&) = delete;
virtual ~ScopedEnv() {
if (attachedToVm) {
vm->DetachCurrentThread();
attachedToVm = false;
}
}
JNIEnv *getEnv() const { return env; }
private:
bool attachedToVm;
JavaVM *vm;
JNIEnv *env;
};
class PageEventObserver {
JavaVM *vm;
jclass pageClass;
jobject pageObj;
public:
PageEventObserver(JavaVM *vm, jclass klass, jobject obj) : vm(vm), pageClass(klass), pageObj(obj) {}
~PageEventObserver();
void onLoadChanged(WebKitLoadEvent);
[...]
};
With its corresponding implementation:
[...]
void PageEventObserver::onLoadChanged(WebKitLoadEvent loadEvent) {
ALOGV("PageEventObserver::onLoadChanged tid %d", gettid());
try {
JNIEnv *env = ScopedEnv(vm).getEnv();
jmethodID onLoadChanged = env->GetMethodID(pageClass, "onLoadChanged", "(I)V");
if (onLoadChanged == nullptr) {
throw;
}
ALOGV("Calling method env %p pageClass %p pageObj %p", env, pageClass, pageObj);
env->CallVoidMethod(pageObj, onLoadChanged, (int)loadEvent);
ALOGV("Called");
} catch(int) {
ALOGE("Could not send onLoadChanged event");
}
}
PageEventObserver
wrapper happens on an initial call from the JNI layer to the following method: JNIEXPORT void JNICALL
Java_com_wpe_wpe_BrowserGlue_newWebView(JNIEnv* env, jobject, jobject pageObj, jint width, jint height)
{
ALOGV("BrowserGlue.newWebView tid %d", gettid());
jclass pageClass = env->GetObjectClass(pageObj);
jclass _pageClass = reinterpret_cast<jclass>(env->NewGlobalRef(pageClass));
jobject _pageObj = reinterpret_cast<jobject>(env->NewGlobalRef(pageObj));
JavaVM *vm;
env->GetJavaVM(&vm);
std::unique_ptr<PageEventObserver> observer = std::make_unique<PageEventObserver>(vm, _pageClass, _pageObj);
wpe_browser_glue_new_web_view(width, height, std::move(observer), [env, pageObj, pageClass] (long viewRef) {
jmethodID onReady = env->GetMethodID(pageClass, "onWebViewReady", "(J)V");
if (onReady == nullptr) {
return;
}
ALOGV("webview %ld", (jlong)viewRef);
env->CallVoidMethod(pageObj, onReady, (jlong)viewRef);
});
}
There I create the global references to pageClass
and pageObj
(I suspect I can skip creating a global reference to pageClass
, but that is out of the scope of the question) and create the PageEventObserver
instance passing the global references to its constructor. The PageEventObserver
instance is then passed to wpe_browser_glue_new_web_view
which ends up saving it as a static.
static void onLoadChanged(WebKitWebView*, WebKitLoadEvent loadEvent, gpointer) {
ALOGV("onLoadCHanged %d", loadEvent);
pageObserver->onLoadChanged(loadEvent);
}
Within this event handler, I make use of the static PageEventObserver
instance, calling its onLoadChanged
method.
The first time this event handler is executed, everything works properly. I see this in the logs:
03-26 10:15:41.754 V [31342/31401] WPE Glue onLoadCHanged 0
03-26 10:15:41.754 V [31342/31401] WPE Glue PageEventObserver::onLoadChanged tid 31401
03-26 10:15:41.754 V [31342/31401] WPE Glue Calling method env 0xb400007915a86030 pageClass 0x294a pageObj 0x2956
03-26 10:15:41.754 V [31342/31401] WPE page0 onLoadChanged ...com.wpe.wpeview.WPEView{d68f3ce V.E...... ........ 0,154-1080,2151 #7f08019e app:id/wpe_view}
03-26 10:15:41.754 V [31342/31401] WPEView Load changed ///// <-- This is inside the Java method I want to call
03-26 10:15:41.754 V [31342/31401] WPE Glue Called
However, on subsequent executions of the same event handler, the Java method is not called, but I see no error or crash at all:
03-26 10:15:45.807 V [31342/31401] WPE Glue onLoadCHanged 3
03-26 10:15:45.808 V [31342/31401] WPE Glue PageEventObserver::onLoadChanged tid 31401
03-26 10:15:45.808 V [31342/31401] WPE Glue Calling method env 0xb400007915a86030 pageClass 0x294a pageObj 0x2956
03-26 10:15:45.808 V [31342/31401] WPE Glue Called
Note the missing logs between WPE Glue Calling...
and WPE Glue called
.
I found the culprit. There was an uncatched exception being thrown from an associated event handler:
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
Fixing that makes everything work as expected. I see the missing logs now.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.