簡體   English   中英

忽略從 C++ 對 Java 方法的調用

[英]Ignored call to Java method from C++

我試圖在一個事件處理程序中反復調用 Java 方法,該方法在我的程序生命周期內執行多次。

為此,我有以下代碼。

  1. JNI 助手 class 獲取 JNIEnv 的有效實例並適當地清理它。
#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. 一個 C++ 包裝器,用於存儲對 Java object 實現我要調用的方法的全局引用:
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);
    [...]
};

及其相應的實現:

[...]
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. PageEventObserver包裝器的創建發生在從 JNI 層對以下方法的初始調用中:
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);
    });
}

在那里我創建了對pageClasspageObj的全局引用(我懷疑我可以跳過創建對pageClass的全局引用,但這不在問題的 scope 范圍內)並創建PageEventObserver實例,將全局引用傳遞給其構造函數。 然后將PageEventObserver實例傳遞給wpe_browser_glue_new_web_view ,最終將其保存為 static。

  1. 在某些時候,會執行一個事件處理程序:
static void onLoadChanged(WebKitWebView*, WebKitLoadEvent loadEvent, gpointer) {
    ALOGV("onLoadCHanged %d", loadEvent);
    pageObserver->onLoadChanged(loadEvent);
}

在這個事件處理程序中,我使用了 static PageEventObserver實例,調用它的onLoadChanged方法。

一次執行此事件處理程序時,一切正常。 我在日志中看到了這一點:

 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

但是,在同一事件處理程序的后續執行中,沒有調用 Java 方法,但我根本看不到錯誤或崩潰:

 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

請注意WPE Glue Calling...WPE Glue called之間缺少的日志。

我找到了罪魁禍首。 關聯的事件處理程序拋出了一個未捕獲的異常:

android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.

修復使一切按預期工作。 我現在看到丟失的日志。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM