JNI&NDK开发最佳实践(五):本地方法的静态注册与动态注册

前言

JVM查找native方法有两种方式:

  1. 按照JNI规范的命名规则,即静态注册。
  2. 调用JNI提供的RegisterNatives函数,将本地函数注册到JVM中,即动态注册。

静态注册

在java文件中声明了如下本地方法


package com.example.taoying.testndkapp;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    // Used to load the 'native-lib' library on application startup.
    static {
        System.loadLibrary("native-lib");//加载so库,待加载的so库的名称为libnative-lib.so。
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // Example of a call to a native method
        TextView tv = findViewById(R.id.sample_text);
        tv.setText(stringFromJNIwithp("11"));
    }

    /**
     * A native method that is implemented by the 'native-lib' native library,
     * which is packaged with this application.
     */
    public native String stringFromJNI();//本地方法用native修饰


    public native String stringFromJNIwithp(String a);
}

C中的具体实现

#include #include 

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


extern "C" JNIEXPORT jstring JNICALL Java_com_example_taoying_testndkapp_MainActivity_stringFromJNIwithp
        (JNIEnv * env, jobject /* this */, jstring jstring1){
    std::string hello = "stringFromJNIwithp";
    return env->NewStringUTF(hello.c_str());
}
  • 可以发现静态注册下函数命名规则为:JNIEXPORT 返回值类型 JNICALL Java_类全路径_方法名
  • 可以通过命令“javah java文件路径 ”生成java类对应的头文件(如javah com.ryg.JNITest),里面有所有本地方法静态注册对应的方法名以及方法签名。
  • JNIEXPORT和JNICALL作用在于说明该函数是JNI函数,在Java虚拟机加载的时候会按上述规则去链接对应的native方法。

动态注册

JNI_OnLoad ()

我们在调用 System.loadLibrary的时候,会在C/C++文件中回调一个名为 JNI_OnLoad ()的函数,类似于我们启一个Acitivity首先会回调其onCreate(),在这个函数中一般是做一些初始化相关操作, 我们可以在这个方法里面注册函数。

动态注册整体流程

  1. 在Java端声明native方法。
  2. 在C中实现JNI_Onload()方法,在其中进行方法注册。
  3. 声明并初始化JNINativeMethod类型的结构体数组,在其中将Java 方法和 C/C++方法通过签名信息一一对应起来。
  4. 通过env->FindClass(className)找到声明native方法的类。
  5. 通过env->RegisterNatives(clazz,getMethods,methodsNum) 动态注册函数。
    实例代码如下:

// jni头文件 #include 
 
#include #include #include using namespace std;
 
 
//native 方法实现jint get_random_num(){
    return rand();
}
/*需要注册的函数列表,放在JNINativeMethod 类型的数组中,
以后如果需要增加函数,只需在这里添加就行了
参数:
1.java中用native关键字声明的函数名
2.签名(传进来参数类型和返回值类型的说明) 
3.C/C++中对应函数的函数名(地址)
*/static JNINativeMethod getMethods[] = {
        {"getRandomNum","()I",(void*)get_random_num},
};
//此函数通过调用RegisterNatives方法来注册我们的函数static int registerNativeMethods(JNIEnv* env, const char* className,JNINativeMethod* getMethods,int methodsNum){
    jclass clazz;
    //找到声明native方法的类
    clazz = env->FindClass(className);
    if(clazz == NULL){
        return JNI_FALSE;
    }
   //注册函数 参数:java类 所要注册的函数数组 注册函数的个数
    if(env->RegisterNatives(clazz,getMethods,methodsNum) < 0){
        return JNI_FALSE;
    }
    return JNI_TRUE;
}
 
static int registerNatives(JNIEnv* env){
    //指定类的路径,通过FindClass 方法来找到对应的类
    const char* className  = "com/example/wenzhe/myjni/JniTest";
    return registerNativeMethods(env,className,getMethods, sizeof(getMethods)/ sizeof(getMethods[0]));
}
//回调函数JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved){
    JNIEnv* env = NULL;
   //获取JNIEnv
    if (vm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6) != JNI_OK) {
        return -1;
    }
    assert(env != NULL);
    //注册函数 registerNatives ->registerNativeMethods ->env->RegisterNatives
    if(!registerNatives(env)){
        return -1;
    }
    //返回jni 的版本 
    return JNI_VERSION_1_6;
}
  • "javap -s"命令可以帮助我们生成方法签名(也可以在AS中配置External Tools工具,帮助快速生成方法签名)
    JNI&NDK开发最佳实践(五):本地方法的静态注册与动态注册_第1张图片
    javap生成方法签名.png

静态注册与动态注册的优缺点

  • 静态注册
    优点: 理解和使用方式简单, 属于傻瓜式操作, 使用相关工具按流程操作就行, 出错率低。
    缺点: 当需要更改类名,包名或者方法时, 需要按照之前方法重新生成头文件, 灵活性不高。
  • 动态注册
    优点: 灵活性高, 更改类名,包名或方法时, 只需对更改模块进行少量修改, 效率高。
    缺点: 对新手来说稍微有点难理解, 同时会由于搞错签名, 方法, 导致注册失败。

更多JNI&NDK系列文章,参见:
JNI&NDK开发最佳实践(一):开篇
JNI&NDK开发最佳实践(二):CMake实现调用已有C/C++文件中的本地方法
JNI&NDK开发最佳实践(三):CMake实现调用已有so库中的本地方法
JNI&NDK开发最佳实践(四):JNI数据类型及与Java数据类型的映射关系
JNI&NDK开发最佳实践(五):本地方法的静态注册与动态注册
JNI&NDK开发最佳实践(六):JNI实现本地方法时的数据类型转换
JNI&NDK开发最佳实践(七):JNI之本地方法与java互调
JNI&NDK开发最佳实践(八):JNI局部引用、全局引用和弱全局引用
JNI&NDK开发最佳实践(九):调试篇

你可能感兴趣的:(JNI&NDK开发最佳实践(五):本地方法的静态注册与动态注册)