JNI是Java Native Interface的缩写,中文名为JAVA本地调用。它允许Java代码和其他语言写的代码进行交互。JNI一开始是为了本地已编译语言,尤其是C和C++而设计的,但是它并不妨碍你使用其他语言,只要调用约定受支持就可以了。Java是支持调用C/C++代码的,不过不能直接调用,因此需要一个中间层来进行转换、翻译,这就是JNI(Java Native Interface)的意思,JNI的作用就是粘合Java代码和C++代码。
静态注册和动态注册。区别是效率。静态注册,每次使用native方法时,都要去寻找;而动态注册,由于有张表的存在,因此查找效率高。静态注册多用于NDK开发,而动态注册多用于Framework开发。不管是静态注册方法,还是动态注册方法,都需要将c文件编译成平台所需要的库。
3.1静态注册原理:根据函数名来建立 java 方法与 JNI 函数的一一对应关系;在Java虚拟机加载so库时,如果发现含有上面两个宏定义的函数时就会链接到对应Java层的native方法,那么怎么知道对应Java中的哪个类的哪个native方法呢,我们仔细观察JNI函数名的构成其实是:以Java为前缀,并且用“_”下划线将包名、类名以及native方法名连接起来就是对应的JNI函数了。
其实就是:Java+包名+类名+方法名(native方法)
例如:Java_packagename_classname_methodname(JNIEnv *env,jclass/jobject,...)
3.2 静态注册方法步骤
1,在Java文件中定义native方法。
2,在cmd命令行模式中切换目录到定义native方法class文件(或者java文件)存放位置。
3,用javah 和javac命令生成包含native方法的.h头文件。
4,实现native方法,用ndk-build编译生成.so库。
3.3静态方法注册JNI的弊端
package com.example.client.jnitest;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
public class MainActivity extends AppCompatActivity {
// Used to load the 'native-lib' library on application startup.
static {
System.loadLibrary("native-lib");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Example of a call to a native method
final TextView tv = findViewById(R.id.sample_text);
Button testBt = findViewById(R.id.test_bt);
testBt.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
tv.setText(stringFromJNI());
}
});
}
/**
* A native method that is implemented by the 'native-lib' native library,
* which is packaged with this application.
*/
public native String stringFromJNI();
public void tip(){
Toast.makeText(this,"from_native",Toast.LENGTH_LONG).show();
}
}
实现功能是点击按钮,改变TextView字符,并弹Toast提示。
MainActivity中System.loadLibrary(“native-lib”),声明native方法,还有一个tip,方法是给native调用的。完成一个交互
package com.example.client.jnitest;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
public class MainActivity extends AppCompatActivity {
// Used to load the 'native-lib' library on application startup.
static {
System.loadLibrary("native-lib");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Example of a call to a native method
final TextView tv = findViewById(R.id.sample_text);
Button testBt = findViewById(R.id.test_bt);
testBt.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
tv.setText(stringFromJNI());
}
});
}
/**
* A native method that is implemented by the 'native-lib' native library,
* which is packaged with this application.
*/
public native String stringFromJNI();
public void tip(){
Toast.makeText(this,"from_native",Toast.LENGTH_LONG).show();
}
}
native这边我们实现native方法,静态注册需要在方法头部加JNIEXPORT JNICALL 等关键字。
#include
#include
#include
jmethodID getNameID;
jclass mainActivity;
void callJava(JNIEnv *env,jobject obj){
mainActivity= env->FindClass("com/example/client/jnitest/MainActivity");
getNameID=env->GetMethodID(mainActivity,"tip","()V");
env->CallVoidMethod(obj,getNameID);
}
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_client_jnitest_MainActivity_stringFromJNI(
JNIEnv* env,
jobject jobject1) {
callJava(env,jobject1);
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
1、 动态注册是在JNi层实现的,JAVA层不需要关心,因为在system.load时就会去调用JNI_OnLoad,有就注册,没有就不注册。动态注册的原理:JNI 允许我们提供一个函数映射表,注册给 JVM,这样 JVM 就可以用函数映射表来调用相应的函数, 而不必通过函数名来查找相关函数(这个查找效率很低,函数名超级长)流程更加清晰可控,效率更高.。
2、实现流程:
1、利用结构体 JNINativeMethod 数组记录 java 方法与 JNI 函数的对应关系.
2、实现 JNI_OnLoad 方法,在加载动态库后,执行动态注册.
3、调用 FindClass 方法,获取 java 对象.
4、调用 RegisterNatives 方法,传入 java 对象,以及 JNINativeMethod 数组,以及注册数目完成注册.
3、动态注册的关键字是两个:
1、JNI_OnLoad()方法,这个是载入Jni库后调用的第一个方法,在这里可以将方法对应表注册给JNI环境
2、JNINativeMethod结构,这个结构是将jni层的方法映射到Java端方法的关键,name:JNI层的方法名
s
ignature:Java层的方法签名 fnPtr:JNI层的函数指针,其定义如下:
typedefstruct{
constchar* name;
constchar* signature;
void* fnPtr;
}JNINativeMethod;
native_hello方法名随便定义,他是实现java层 stringFromJNI 方法的调用,它们之间的绑定借助于JNI_OnLoad方法,JNI_OnLoad会在java那边System.loadLibrary时候被调用,建立关联关系。
#include
#include
#include
jmethodID getNameID;
jclass mainActivity;
void callJava(JNIEnv *env,jobject obj){
mainActivity= env->FindClass("com/example/client/jnitest/MainActivity");
getNameID=env->GetMethodID(mainActivity,"tip","()V");
env->CallVoidMethod(obj,getNameID);
}
jstring native_hello(JNIEnv *env, jobject object) {
callJava(env,object);
return env->NewStringUTF("Hello from C++");
}
JNINativeMethod method[]={{"stringFromJNI","()Ljava/lang/String;",(void*)native_hello}};
jint registerNativeMeth(JNIEnv *env){
jclass cl=env->FindClass("com/example/client/jnitest/MainActivity");
if((env->RegisterNatives(cl,method,1))<0){
return -1;
}
return 0;
}
jint JNI_OnLoad(JavaVM* vm, void* reserved) {
JNIEnv *env = NULL;
if (vm->GetEnv((void **) &env, JNI_VERSION_1_4) != JNI_OK) {
return -1;
}
if (registerNativeMeth(env) != JNI_OK) {
return -1;
}
return JNI_VERSION_1_4;
}
另外关于jni调用,是结合函数dlopen. dlsym 加载so库,并查找函数指针调用。