Android JNI,调用 getMethodID 使应用程序崩溃

Android JNI, call getMethodID crashes app

我正在尝试在 Android 应用程序中使用 JNI。我的代码正确编译并启动了应用程序,但是当我想在我的 C++ 代码中调用 Java 方法(从调用 class)时,应用程序立即崩溃并提示找不到该方法。

你将不得不原谅代码,它有点乱,因为我尝试了很多东西,它还混合了 React-Native 的东西,但 JNI 崩溃与它无关。

java模块:

package com.jsirnwalletcore;

import androidx.annotation.NonNull;
import androidx.annotation.Keep;
import android.util.Log;

import android.content.Context;
import android.content.SharedPreferences;
import android.util.Base64;

import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;

@Keep
class WalletCoreModule extends ReactContextBaseJavaModule {
  public static final String NAME = "WalletCore";
  private static native void initialize(long jsiPtr);
  private SharedPreferences prefs;

  public WalletCoreModule(ReactApplicationContext context) {
    super(context);
    
    this.prefs = context.getSharedPreferences("WALLETCORE", Context.MODE_PRIVATE);
  }

  @NonNull
  @Override
  public String getName() {
    return NAME;
  }

  public String getSeed() {
    return "HARDCODED SEED";
  }

  @ReactMethod(isBlockingSynchronousMethod = true)
  public boolean install() {
    try {
      String seed = getSeed();
      Log.w(NAME, "ROPO CALLING GET SEED: " + seed);

      System.loadLibrary("jsi-rn-wallet-core");

      ReactApplicationContext context = getReactApplicationContext();

      initialize(context.getJavaScriptContextHolder().get());
      return true;
    } catch(Exception exception) {
      return false;
    }
  }
}

cpp-adapter.cpp:

#include <jni.h>
#include "installer.hpp"
#include "logs.h"
#include <string>
#include "pthread.h"
#include <sys/types.h>
#include <jsi/jsi.h>

JavaVM *java_vm;
jclass java_class;
jobject java_object;

/**
 * A simple callback function that allows us to detach current JNI Environment
 * when the thread
 * See  for detailed explanation
 */

void DeferThreadDetach(JNIEnv *env)
{
    static pthread_key_t thread_key;

    // Set up a Thread Specific Data key, and a callback that
    // will be executed when a thread is destroyed.
    // This is only done once, across all threads, and the value
    // associated with the key for any given thread will initially
    // be NULL.
    static auto run_once = []
    {
        const auto err = pthread_key_create(&thread_key, [](void *ts_env)
                                            {
            if (ts_env) {
                java_vm->DetachCurrentThread();
            } });
        if (err)
        {
            // Failed to create TSD key. Throw an exception if you want to.
        }
        return 0;
    }();

    // For the callback to actually be executed when a thread exits
    // we need to associate a non-NULL value with the key on that thread.
    // We can use the JNIEnv* as that value.
    const auto ts_env = pthread_getspecific(thread_key);
    if (!ts_env)
    {
        if (pthread_setspecific(thread_key, env))
        {
            // Failed to set thread-specific value for key. Throw an exception if you want to.
        }
    }
}

/**
 * Get a JNIEnv* valid for this thread, regardless of whether
 * we're on a native thread or a Java thread.
 * If the calling thread is not currently attached to the JVM
 * it will be attached, and then automatically detached when the
 * thread is destroyed.
 *
 * See  for detailed explanation
 */
JNIEnv *GetJniEnv()
{
    JNIEnv *env = nullptr;
    // We still call GetEnv first to detect if the thread already
    // is attached. This is done to avoid setting up a DetachCurrentThread
    // call on a Java thread.

    // g_vm is a global.
    auto get_env_result = java_vm->GetEnv((void **)&env, JNI_VERSION_1_6);
    if (get_env_result == JNI_EDETACHED)
    {
        if (java_vm->AttachCurrentThread(&env, NULL) == JNI_OK)
        {
            DeferThreadDetach(env);
        }
        else
        {
            // Failed to attach thread. Throw an exception if you want to.
        }
    }
    else if (get_env_result == JNI_EVERSION)
    {
        // Unsupported JNI version. Throw an exception if you want to.
    }
    return env;
}

void tempInstall(jsi::Runtime &rt)
{
    auto testFn = jsi::Function::createFromHostFunction(
        rt,
        jsi::PropNameID::forAscii(rt, "testFn"),
        0,
        [](jsi::Runtime &rt, const jsi::Value &thisValue, const jsi::Value *args, size_t count) -> jsi::Value
        {
            JNIEnv *jniEnv = GetJniEnv();

            java_class = jniEnv->GetObjectClass(java_object);
            jmethodID get = jniEnv->GetMethodID(java_class, "getSeed", "()Ljava/lang/String;");
            // jobject result = jniEnv->CallObjectMethod(java_object, get);
            // const char *str = jniEnv->GetStringUTFChars((jstring)result, NULL);
            // LOGW("ROPO GOT: %s", str);
            return {};
        });

    rt.global().setProperty(rt, "testFn", std::move(testFn));
}

extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *jvm, void *reserved)
{
    java_vm = jvm;

    LOGW("ROPO JNI_OnLoad CALLED");

    return JNI_VERSION_1_6;
}

extern "C" JNIEXPORT void JNICALL
Java_com_jsirnwalletcore_WalletCoreModule_initialize(JNIEnv *env, jobject thiz, jlong jsi)
{
    auto rt = reinterpret_cast<jsi::Runtime *>(jsi);
    // jclass clazz = env->GetObjectClass(thiz);

    // jmethodID getNameMID = env->GetMethodID(clazz, "getName", "()Ljava/lang/String;");
    // jstring jClassName = (jstring)env->CallObjectMethod(thiz, getNameMID);
    // std::string classString = jstringToString(env, jClassName);
    // LOGW("###########ROPO############");
    // LOGW("RETRIEVED STRING: %s", classString.c_str());
    // LOGW("###########ROPO############");

    // jmethodID retrieveMethodID = env->GetMethodID(clazz, "getSeed", "()Ljava/lang/String;");
    // if (retrieveMethodID == NULL)
    // {
    //     LOGW("ROPO methodID not found!");
    // }
    // else
    // {
    //     jstring myString = (jstring)env->CallObjectMethod(thiz, retrieveMethodID);
    //     std::string retrievedString = jstringToString(env, myString);
    //     LOGW("###########ROPO############");
    //     LOGW("RETRIEVED STRING: %s", retrievedString.c_str());
    //     LOGW("###########ROPO############");
    // }

    install(*rt);
    tempInstall(*rt);

    // env->GetJavaVM(&java_vm);
    java_object = env->NewGlobalRef(thiz);
}

在我的应用程序中,我调用了 testFn,基本上当它到达调用 env->getMethodID 的那一行时,出现一个错误,提示未找到该方法,然后应用程序立即崩溃。

04-10 04:04:11.707 22870 22916 F com.example: java_vm_ext.cc:579] JNI DETECTED ERROR IN APPLICATION: JNI NewObjectV called with pending exception java.lang.NoSuchMethodError: no non-static method "Ljava/lang/Class;.getSeed()Ljava/lang/String;"

知道哪里出了问题吗?

正如@user7860670 在评论中指出的那样,问题是我将本机方法声明为静态方法。

private static native void initialize(long jsiPtr);

而它应该是一个实例方法:

private native void initialize(long jsiPtr);

即使返回了正确的 class 名称,它的静态特性也会导致它崩溃。

太糟糕了,JNI 没有抛出 真实 有用的提示。