No JNI_OnLoad问题解决

本文详细介绍了Android应用中JNI的工作原理及加载过程。包括如何通过Java层调用C/C++库,JNI_OnLoad函数的作用及其内部实现,以及如何注册本地方法等关键技术点。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

报错问题如下:

04-29 13:53:12.184: D/dalvikvm(361): Trying to load lib /data/data/com.conowen.helloworld/lib/libHelloWorld.so 0x44edea98

04-29 13:53:12.204: D/dalvikvm(361): Added shared lib /data/data/com.conowen.helloworld/lib/libHelloWorld.so 0x44edea98

04-29 13:53:12.204: D/dalvikvm(361): No JNI_OnLoad found in /data/data/com.conowen.helloworld/lib/libHelloWorld.so 0x44edea98, skipping init


原理描述:

Android的应用层的类都是以Java写的,这些Java类编译为Dex文件之后,必须靠Dalvik虚拟机( Virtual Machine)来执行。而C&C++的函数并不是在Dalvik虚拟机中运行的,所以效率和速度要比在Dalvik虚拟机中运行得快很多。假如在执行java程序时,需要载入C&C++函数时,那么加载是怎么样一个过程呢?

1.  java通知Dalvik虚拟机去加载本地C/C++库,调用函数:System.loadLibrary("Name");

2. Dalvik虚拟机成功加载库之后,就会自动地寻找库里面的JNI_OnLoad函数,所以本地方法要实现JNI_OnLoad函数,有一些版本并不需要实现这函数,加载的时候可以调用系统默认的JNI_OnLoad函数。也就是如果你的库里面没有写明JNI_OnLoad()函数,VM会默认该库使用最老的JNI 1.1版本。但是新版的JNI做了很多的扩充,也优化了一些内容,如果需要使用JNI的新版功能,就必须在JNI_OnLoad()函数声明JNI的版本。

(1)告诉Dalvik虚拟机此C库使用哪一个JNI版本当没有JNI_OnLoad()函数并且你又调用了新扩展的jni函数时,Android调试信息会做出如下提示(No JNI_OnLoad found)。

#include<android/log.h>
#define LOG    "TEST-L" //自定义的LOG的标识  
#define LOGD(...)  __android_log_print(ANDROID_LOG_DEBUG,LOG,__VA_ARGS__) // 定义LOGD类型  
#define LOGI(...)  __android_log_print(ANDROID_LOG_INFO,LOG,__VA_ARGS__) // 定义LOGI类型  
#define LOGW(...)  __android_log_print(ANDROID_LOG_WARN,LOG,__VA_ARGS__) // 定义LOGW类型  
#define LOGE(...)  __android_log_print(ANDROID_LOG_ERROR,LOG,__VA_ARGS__) // 定义LOGE类型  
#define LOGF(...)  __android_log_print(ANDROID_LOG_FATAL,LOG,__VA_ARGS__) // 定义LOGF类型


JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {  
    JNIEnv* env = NULL;
    jint result = -1;  
	
    if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {  
        LOGE("jni_load failed\n");  
        return result;  
    }  
	
    result = JNI_VERSION_1_4;  
  
    return result;  
} 


(2)因为Dalvik虚拟机加载C库时,第一件事是调用JNI_OnLoad()函数,所以我们可以在JNI_OnLoad()里面进行一些初始化工作,如注册JNI函数等等。注册本地函数,可以加快java层调用本地函数的效率,但是这种方法仅仅适应于在android源码下编译。以下是C++版本的加载函数:

jint JNI_OnLoad(JavaVM* vm, void* reserved) {  
    JNIEnv* env = NULL;//定义JNI Env  
    jint result = -1;  
    /*JavaVM::GetEnv 原型为 jint (*GetEnv)(JavaVM*, void**, jint);   
     */  
    if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {  
        LOGE(ANDROID_LOG_ERROR, TAG, "GetEnv failed!");  
        return result;  
    }  
  
    LOGI(ANDROID_LOG_INFO, TAG, "loading . . .");  
  
/*开始注册 
 * 传入参数是JNI env 
 * 以register_android_test_myclass_myffunc(env)为例说明 
 */  
      
    if(register_android_test_myclass_myffunc(env) != JNI_OK) {  
        LOGE(ANDROID_LOG_ERROR, TAG, 
		"can't load register_android_test_myclass_myffunc");  
        goto end;  
    }  
  
    LOGI(ANDROID_LOG_INFO, TAG, "loaded");  
  
    result = JNI_VERSION_1_4;  
  
    return result;  
}  
static const char* const kClassPathName = "com/test/myclass"; 

static JNINativeMethod gMethods[] = {  
    {"setParam",       "(Ljava/lang/String;)V",        (void *)com_test_myclass_setParam},  
    {"prepare",             "()V",                     (void *)com_test_myclass_prepare},  
};

int register_android_test_myclass_myffunc(JNIEnv *env) {  
    return jniRegisterNativeMethods(env, kClassPathName, gMethods, 
			sizeof(gMethods) / sizeof(gMethods[0]));  
    /*跳到OnLoad.cpp文件中的 
     * jint jniRegisterNativeMethods(JNIEnv* env, 
                             const char* className, 
                             const JNINativeMethod* gMethods, 
                             int numMethods) 
                              
     */  
}



注意:

其中AndroidRuntime::registerNativeMethods是在头文件android_runtime/AndroidRuntime.h中定义,使用时得

且得在Android.mk文件中加上:

LOCAL_SHARED_LIBRARIES += \

         libandroid_runtime 

GetEnv()函数返回的  Jni 环境对每个线程来说是不同的   

由于Dalvik虚拟机通常是Multi-threading的。每一个线程调用JNI_OnLoad()时

所用的JNI Env是不同的,因此我们必须在每次进入函数时都要通过vm->GetEnv重新获取


 


(3)与JNI_OnLoad()函数相对应的函数JNI_OnUnload(),当虚拟机释放该C库时,则会调用JNI_OnUnload()函数来进行清理动作。

/*
 *如果该函数没有实现,系统会调用默认的函数
 *如果不知道怎么用,最好不要自己实现改函数
 *因为你不知道系统回收了什么资源
 */
void JNI_OnUnload(JavaVM* vm, void* reserved){  
     LOGI("call JNI_OnUnload ~~!!");
} 


<think>我们正在讨论JNI中的JNINativeMethod结构体,特别是在Android C++中的使用示例。 根据用户提供的引用,我们知道: - JNI位于应用层和框架层之间,部分源代码在frameworks/base目录下(引用[3])。 - JNI代码会被编译成共享库,放在/system/lib目录下(引用[3])。 - 用户可能想要一个JNINativeMethod结构体的具体例子。 JNINativeMethod结构体用于将Java中的native方法名与C/C++函数实现进行映射。在Android开发中,我们经常使用它来注册本地方法。 结构体定义(通常在jni.h中): ```c typedef struct { const char* name; // Java中native方法的名字 const char* signature; // Java方法的签名(描述参数和返回值类型) void* fnPtr; // 指向实现该方法的C/C++函数的指针 } JNINativeMethod; ``` 在Android C++代码中,我们通常会定义一个JNINativeMethod数组,然后使用JNI的RegisterNatives函数将这个数组中的方法注册到对应的Java类中。 下面是一个简单的示例: 假设我们有一个Java类: ```java package com.example.jni; public class NativeExample { public static native void nativeMethod1(int param); public static native String nativeMethod2(String input); } ``` 在C++中,我们需要实现这两个native方法,然后定义JNINativeMethod数组: ```cpp #include <jni.h> // 引用[1]中提到包含jni.h // 方法1的实现 void Java_com_example_jni_NativeExample_nativeMethod1(JNIEnv *env, jclass clazz, jint param) { // 实现逻辑 } // 方法2的实现 jstring Java_com_example_jni_NativeExample_nativeMethod2(JNIEnv *env, jclass clazz, jstring input) { // 实现逻辑,比如将输入的字符串转换为大写 const char *str = env->GetStringUTFChars(input, nullptr); // 注意:这里省略了错误检查 char result[100]; // 假设一个足够大的缓冲区 // 转换为大写(简单示例,不考虑多字节字符) for (int i=0; str[i]!='\0'; i++) { result[i] = toupper(str[i]); } result[strlen(str)] = '\0'; env->ReleaseStringUTFChars(input, str); return env->NewStringUTF(result); } // 定义JNINativeMethod数组 static JNINativeMethod methods[] = { // 第一个方法 {"nativeMethod1", "(I)V", (void*)Java_com_example_jni_NativeExample_nativeMethod1}, // 第二个方法 {"nativeMethod2", "(Ljava/lang/String;)Ljava/lang/String;", (void*)Java_com_example_jni_NativeExample_nativeMethod2} }; // 注册函数(通常在JNI_OnLoad中调用) jint JNI_OnLoad(JavaVM* vm, void* reserved) { JNIEnv* env; if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) { return JNI_ERR; } // 找到要注册的Java类 jclass clazz = env->FindClass("com/example/jni/NativeExample"); if (clazz == nullptr) { return JNI_ERR; } // 注册本地方法 if (env->RegisterNatives(clazz, methods, sizeof(methods)/sizeof(methods[0])) < 0) { return JNI_ERR; } return JNI_VERSION_1_6; } ``` 注意: 1. 方法名在Java层是简单的名字(如"nativeMethod1"),但在C++中,我们通常使用完整的方法名(包括包名和类名)来避免冲突。但是,在JNINativeMethod结构体中,我们只需要提供Java中的方法名(即"nativeMethod1")和对应的签名。 2. 签名描述了方法的参数和返回值类型。例如: - "(I)V" 表示参数是int,返回void。 - "(Ljava/lang/String;)Ljava/lang/String;" 表示参数是String,返回值也是String。 3. 在实现中,我们也可以使用短名(即不按照完整包名类名的方式命名函数),但需要在注册时指定。上面的例子使用了长名,但注册时使用的是短名。实际上,在JNINativeMethod中,我们使用短名,而函数指针可以是任意函数,只要函数签名与JNI要求的匹配(即参数包含JNIEnv*, jclass/jobject, 然后是Java方法的参数)。但是,为了避免手动处理JNI函数签名,通常还是按照长名规则命名,这样JVM默认会按照长名查找。但通过RegisterNatives,我们可以使用任意函数,只要签名正确。 另一种做法是使用短名函数,然后通过JNINativeMethod数组来关联。例如: 我们可以将函数命名为: ```cpp void native_method1(JNIEnv *env, jclass clazz, jint param) { // ... } ``` 然后在JNINativeMethod数组中: ```cpp static JNINativeMethod methods[] = { {"nativeMethod1", "(I)V", (void*)native_method1}, // ... }; ``` 这样,在注册之后,当Java调用nativeMethod1时,就会调用到native_method1函数。 总结:在Android C++中,JNINativeMethod结构体数组是连接Java native方法和C/C++实现函数的桥梁。通过RegisterNatives注册,可以避免使用默认的长名查找方式,提高效率并允许更灵活的函数命名。 相关问题: 1. 如何获取Java方法的签名? 2. 在JNI中,JNI_OnLoad函数的作用是什么? 3. JNINativeMethod结构体中的函数指针类型为什么是void*?它可以是任意类型的函数指针吗? 4. 在Android中,除了使用RegisterNatives,还有哪些方式可以关联Java native方法和本地实现?</think>### JNI JNINativeMethod 结构体示例(Android C++) 在 Android JNI 开发中,`JNINativeMethod` 结构体用于将 Java 层的 native 方法与 C/C++ 层的实现函数进行映射。其定义位于 `<jni.h>` 中,结构如下: ```cpp typedef struct { const char* name; // Java 方法名 const char* signature; // JNI 方法签名 void* fnPtr; // C/C++ 函数指针 } JNINativeMethod; ``` #### 完整示例 假设 Java 类 `com.example.NativeHelper` 声明了 native 方法: ```java public class NativeHelper { public static native int add(int a, int b); // 方法1 public native String reverseString(String input); // 方法2 } ``` 对应的 C++ 实现与注册逻辑: ```cpp #include <jni.h> #include <string> #include <algorithm> // 1. 实现方法1:静态方法 jint native_add(JNIEnv* env, jclass clazz, jint a, jint b) { return a + b; // 加法实现 } // 2. 实现方法2:实例方法 jstring native_reverseString(JNIEnv* env, jobject obj, jstring input) { const char* str = env->GetStringUTFChars(input, nullptr); std::string result(str); std::reverse(result.begin(), result.end()); env->ReleaseStringUTFChars(input, str); return env->NewStringUTF(result.c_str()); } // 3. 定义映射表 static JNINativeMethod methods[] = { // Java方法名 | 签名 | C++函数指针 {"add", "(II)I", (void*)native_add}, {"reverseString", "(Ljava/lang/String;)Ljava/lang/String;", (void*)native_reverseString} }; // 4. 注册方法(在 JNI_OnLoad 中调用) jint JNI_OnLoad(JavaVM* vm, void* reserved) { JNIEnv* env; if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) { return JNI_ERR; } jclass clazz = env->FindClass("com/example/NativeHelper"); env->RegisterNatives(clazz, methods, sizeof(methods)/sizeof(methods[0])); return JNI_VERSION_1_6; } ``` #### 关键说明 1. **方法签名规则**: - `(II)I` → 两个 `int` 参数,返回 `int` - `(Ljava/lang/String;)Ljava/lang/String;` → 一个 `String` 参数,返回 `String` - [JNI 类型签名文档](https://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/types.html) 2. **函数参数约定**: - 静态方法:`(JNIEnv*, jclass, ...)` - 实例方法:`(JNIEnv*, jobject, ...)` 3. **注册过程**: - 在 `JNI_OnLoad` 中通过 `RegisterNatives()` 绑定映射表 - 避免手动查找每个方法,提升性能[^3] 4. **实际应用位置**: - 代码通常位于 Android Framework 的 `frameworks/base/` 目录 - 编译为 `.so` 库放入 `/system/lib`[^3] #### 注意事项 1. **签名一致性**:签名错误会导致 `NoSuchMethodError` 2. **线程安全**:确保在 `JNI_OnLoad` 主线程注册 3. **函数名约定**:C++ 函数名无需匹配 Java 方法名(由映射表关联) ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值