简体   繁体   中英

Ignored call to Java method from C++

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.

  1. A JNI helper class to get a valid instance to JNIEnv and clean it up appropriately.
#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;
};
  1. A C++ wrapper to store global references to the Java object implementing the method that I want to call:
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");
    }
}
  1. The creation of the 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.

  1. At some point, an event handler is executed:
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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM