简体   繁体   中英

Android JNI, call getMethodID crashes app

I'm trying to use the JNI in an Android app. My code correctly compiles and the application launches, however when I want to call a Java method (from the calling class) in my C++ code, the app promptly crashes saying it cannot find the method.

You will have to excuse the code, it is a bit messy because I've tried so many things, it is also mixed with React-Native stuff, but the JNI crash has nothing to do with it.

The java module:

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;
    }
  }
}

The 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 https://stackoverflow.com/a/30026231 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 https://stackoverflow.com/a/30026231 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);
}

In my app, I call the testFn , basically when it reaches the line that calls env->getMethodID, there is an error saying the method is not found and then the app promptly crashes.

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;"

Any idea what might be wrong?

As pointed out by @user7860670 in the comments, the problem was that I declared the native method as static.

private static native void initialize(long jsiPtr);

Whereas it should be an instance method:

private native void initialize(long jsiPtr);

Even if the correct class name was being returned, the static nature of it caused it to crash.

Big oof that the JNI throws no real helpful hint.

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