JNI&NDK开发最佳实践(五):本地方法的静态注册与动
2019-07-06 本文已影响8人
taoyyyy
前言
JVM查找native方法有两种方式:
- 按照JNI规范的命名规则,即静态注册。
- 调用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 <jni.h>#include <string>
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(),在这个函数中一般是做一些初始化相关操作, 我们可以在这个方法里面注册函数。
动态注册整体流程
- 在Java端声明native方法。
- 在C中实现JNI_Onload()方法,在其中进行方法注册。
- 声明并初始化JNINativeMethod类型的结构体数组,在其中将Java 方法和 C/C++方法通过签名信息一一对应起来。
- 通过env->FindClass(className)找到声明native方法的类。
- 通过env->RegisterNatives(clazz,getMethods,methodsNum) 动态注册函数。
实例代码如下:
// jni头文件 #include <jni.h>
#include <cassert>#include <cstdlib>#include <iostream>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<void**>(&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工具,帮助快速生成方法签名)
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开发最佳实践(九):调试篇