内容纲要

jni注册方法

动态注册

代码

java

public class TextJni {

    static {
        System.loadLibrary("textjni_lib");
    }

    native int text(String message);

    static native int static_text(String message);
}

c

#include <jni.h>
#include <string>
#include <android/log.h>

jint native_text(JNIEnv *env, jobject jobject1, jstring msg) {
    const char *p_msg = env->GetStringUTFChars(msg, JNI_FALSE);
    __android_log_print(ANDROID_LOG_INFO, "mmm", "method = %s, msg = %s", __FUNCTION__, p_msg);

    return 0;
}

jint native_staic_text(JNIEnv *env, jobject jclass1, jstring meg) {
    const char *p_msg = env->GetStringUTFChars(meg, JNI_FALSE);
    __android_log_print(ANDROID_LOG_INFO, "mmm", "method = %s, msg = %s", __FUNCTION__, p_msg);

    return 0;
}

static const JNINativeMethod nativeMethod[] = {
        {"text",        "(Ljava/lang/String;)I", (void *) native_text},
        {"static_text", "(Ljava/lang/String;)I", (void *) native_staic_text}
};

static int registNativeMethod(JNIEnv *env) {
    int result = -1;

    jclass class_text = env->FindClass("com.text.ndk1.TextJni");
    if (env->RegisterNatives(class_text, nativeMethod,
                             sizeof(nativeMethod) / sizeof(nativeMethod[0])) == JNI_OK) {
        result = 0;
    }
    return result;
}

jint JNI_OnLoad(JavaVM *vm, void *reserved) {
    JNIEnv *env = NULL;
    int result = -1;

    if (vm->GetEnv((void **) &env, JNI_VERSION_1_1) == JNI_OK) {
        if (registNativeMethod(env) == JNI_OK) {
            result = JNI_VERSION_1_6;
        }
        return result;
    }
}

总结

首先调用JNI_OnLoadJNI_OnLoad里面调用registNativeMethod

registNativeMethod中,调用RegisterNatives对java中声明的native与c中的方法进行绑定动态注册:

env->RegisterNatives(class_text, nativeMethod, sizeof(nativeMethod) / sizeof(nativeMethod[0])

其中nativeMethod数组为:

{"text", "(Ljava/lang/String;)I", (void *) native_text}, {"static_text", "(Ljava/lang/String;)I", (void *) native_staic_text}

原理

在system.load时会去调用JNI_OnLoad,有就注册,没有就不注册。

动态注册的原理:JNI 允许我们提供一个函数映射表,注册给 JVM,这样 JVM 就可以用函数映射表来调用相应的函数, 而不必通过函数名来查找相关函数(这个查找效率很低,函数名超级长)流程更加清晰可控,效率更高。

Java层通过System.loadLibrary()方法可以加载一个动态库,此时虚拟机会调用jni库中的JNI_OnLoad()函数。

jint JNI_OnLoad(JavaVM* vm, void* reserved);

返回值代表,动态库需要的jni版本,如果虚拟机不能识别这个版本,那么就不可以加载这个动态库

目前的返回值有,JNI_VERSION_1_1, JNI_VERSION_1_2, JNI_VERSION_1_4, JNI_VERSION_1_6。

如果动态库没有提供 JNI_OnLoad()函数会默认使用JNI_VERSION_1_1版本,但是这个版本太老,很多新函数没有,最好返回版本。

实现流程:

  1. 利用结构体 JNINativeMethod 数组记录 java 方法与 JNI 函数的对应关系。
  2. 实现 JNI_OnLoad 方法,在加载动态库后,执行动态注册。
  3. 调用 FindClass 方法,获取 java 对象。
  4. 调用 RegisterNatives 方法,传入 java 对象,以及 JNINativeMethod 数组,以及注册数目完成注册。

注册函数:JNI_Onload()

JNI_OnLoad()函数经常用来做一些初始化操作,动态注册就是在这里进行的

动态注册通过_JNIEnvRegisterNatives()函数来完成

函数原型为

jint RegisterNatives(JNIEnv *env, jclass clazz, 
        const JNINativeMethod *methods, jint nMethods);
  • 第一个参数:JNIEnv *指针
  • 第二个参数:代表一个java类
  • 第三个参数:代表JNINativeMethod结构体数组,JNINativeMethod定义了java层函数和native层函数的映射关系
  • 第四个参数:代表第三个参数methods数组的大小

返回值 0代表成功,负值代表失败

JNINativeMethod结构体
typedef struct {
    const char* name;
    const char* signature;
    void*       fnPtr;
} JNINativeMethod;
  • name:代表java的native方法的名字
  • signature:表示方法的签名
  • fnPtr:是一个函数指针,指向jni层的一个函数,也就是java层和native层建立联系的函数
signature签名
签名符号 C/C++ java
V void void
Z jboolean boolean
I jint int
J jlong long
D jdouble double
F jfloat float
B jbyte byte
C jchar char
S jshort short
[Z jbooleanArray boolean[]
[I jintArray int[]
[J jlongArray long[]
[D jdoubleArray double[]
[F jfloatArray float[]
[B jbyteArray byte[]
[C jcharArray char[]
[S jshortArray short[]
特殊的String
Ljava/lang/String; jstring String
L完整包名加类名; jobject class

举个例子:
传入的java参数有两个 分别是 int 和 long[] 函数返回值为 String
即函数的定义为:String getString(int a ,long[] b)
签名就应该是 :"(I[J)Ljava/lang/String;"(不要漏掉英文分号)

静态注册

AS很牛,写native很方便,不详说了

package com.example.wenzhe.myjni;
/**
 * Created by wenzhe on 16-1-27.
 */
public class JniTest {
public native int getRandomNum();
public native String stringFromJNI();

static {
    System.loadLibrary("HelloJni");
    }
}
extern "C" JNIEXPORT jstring JNICALL
Java_com_text_ndk1_MainActivity_stringFromJNI(
        JNIEnv *env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

区别总结

静态注册

  • 优点: 理解和使用方式简单, 属于傻瓜式操作, 使用相关工具按流程操作就行, 出错率低
  • 缺点: 当需要更改类名,包名或者方法时, 需要按照之前方法重新生成头文件, 灵活性不高

动态注册

  • 优点: 灵活性高, 更改类名,包名或方法时, 只需对更改模块进行少量修改, 效率高
  • 缺点: 对新手来说稍微有点难理解, 同时会由于搞错签名, 方法, 导致注册失败

参考

jni注册

https://blog.csdn.net/qq_37858386/article/details/103765111

https://www.jianshu.com/p/770eab3a27f0?u_atoken=48d8b7ea-7d48-459c-bd81-4cdad3bf1f86&u_asession=01rowO5fHxz9B1z7v3N0Kc2P7Q1eLnHPBw6wzaEcnP7fq-JQifYT679kEVAmZ8XJxwX0KNBwm7Lovlpxjd_P_q4JsKWYrT3W_NKPr8w6oU7K-PwUpw3yKG5lgwqjUOiJUSPpcarp92QKzyJKyYjREPlmBkFo3NEHBv0PZUm6pbxQU&u_asig=05I1UluhUtlBU6IPgFB9X73mpJvXannJNXcFr0F76r3iY0XK_R0CYG_-cXaEeO9MXZpkiHNSB_M9VqZI5a82nUHtsX1Qa8le6e9-XvNZrj7NZ0H0qDw6PUY9TjU8ftoeEUIyl-Z7FTCQTTFPRU9377AWlcYa5xQfr4eUuy7u_kvEH9JS7q8ZD7Xtz2Ly-b0kmuyAKRFSVJkkdwVUnyHAIJzatkv8_bFalYiIdgC5ZBYgeLQ0709UdScF9cj4jIBx3vWPRPQyB_SKrj-61LB_f61u3h9VXwMyh6PgyDIVSG1W-OOxV2Fv_K9pSUYXgTSVvPUIxISThzVR5Fww8w6sx0lZa7KgCslADZ1Z4mWd2_xCeBkddaggpECApr6ISHWLS4mWspDxyAEEo4kbsryBKb9Q&u_aref=Ahgq15czZI95YFS4mmfLRo1yU8s%3D