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 没有抛出 真实 有用的提示。
我正在尝试在 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 没有抛出 真实 有用的提示。