JNI 简介

JNI简介

http://www.jianshu.com/p/aba734d5b5cd

函数注册

实例
java

1
2
3
4
5
6
7
8
public class AndroidJni {
static{
System.loadLibrary("main");
}

public native void dynamicLog();
public native void staticLog();
}

C++

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <jni.h>
#define LOG_TAG "main.cpp"
#include "mylog.h"
static void nativeDynamicLog(JNIEnv *evn, jobject obj){
LOGE("hell main");
}

JNIEXPORT void JNICALL Java_com_github_songnick_jni_AndroidJni_staticLog (JNIEnv *env, jobject obj)
{
LOGE("static register log ");
}

JNINativeMethod nativeMethod[] = {{"dynamicLog", "()V", (void*)nativeDynamicLog},};

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *jvm, void *reserved) {
JNIEnv *env;
if (jvm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
return -1;
}
LOGE("JNI_OnLoad comming");
jclass clz = env->FindClass("com/github/songnick/jni/AndroidJni");

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

return JNI_VERSION_1_4;
}

要完成的任务是将java中声明的函数和C++中定义的函数链接起来。

静态注册native函数

模版:
JINEXPORT+返回类型+JNICALL+以_连接的java类作为函数名(JNIEnv* evn, jobject obj){}

JNIEXPORT和JNICALL关键字,这两个关键字是两个宏定义,他主要的作用就是说明该函数为JNI函数。java虚拟机加载so时会将检查到的带宏定义的函数,根据函数名链接到对应类的对应native函数。

javah -d jni (package)

动态注册native函数

当我们使用System.loadLibarary()方法加载so库的时候,Java虚拟机就会找到这个函数并调用该函数,因此可以在该函数中做一些初始化的动作,其实这个函数就是相当于Activity中的onCreate()方法。该函数前面有三个关键字,分别是JNIEXPORT、JNICALL和jint,其中JNIEXPORT和JNICALL是两个宏定义,用于指定该函数是JNI函数。jint是JNI定义的数据类型,因为Java层和C/C++的数据类型或者对象不能直接相互的引用或者使用,JNI层定义了自己的数据类型,用于衔接Java层和JNI层,至于这些数据类型我们在后面介绍。这里的jint对应Java的int数据类型,该函数返回的int表示当前使用的JNI的版本,其实类似于Android系统的API版本一样,不同的JNI版本中定义的一些不同的JNI函数。

so加载

System.loadLibarary()

1
2
3
public static void loadLibrary(String libName) {
Runtime.getRuntime().loadLibrary(libName, VMStack.getCallingClassLoader());
}

loadlibrary()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
void loadLibrary(String libraryName, ClassLoader loader) {
if (loader != null) {
String filename = loader.findLibrary(libraryName);
if (filename == null) {
throw new UnsatisfiedLinkError("Couldn't load " + libraryName + ": " +
"findLibrary returned null");
}
String error = nativeLoad(filename, loader);
if (error != null) {
throw new UnsatisfiedLinkError(error);
}
return;
}

String filename = System.mapLibraryName(libraryName);
List<String> candidates = new ArrayList<String>();
String lastError = null;
for (String directory : mLibPaths) {
String candidate = directory + filename;
candidates.add(candidate);
if (new File(candidate).exists()) {
String error = nativeLoad(candidate, loader);
if (error == null) {
return; // We successfully loaded the library. Job done.
}
lastError = error;
}
}

if (lastError != null) {
throw new UnsatisfiedLinkError(lastError);
}
throw new UnsatisfiedLinkError("Library " + libraryName + " not found; tried " + candidates);
}

http://blog.sina.com.cn/s/blog_4c0706560102vc6o.html
loadLibrary -> nativeLoad(即Dalvik_java_lang_Runtime_nativeLoad) -> dvmLoadNativeCode -> dlopen\dlsym

最终通过dlopen函数完成的,只是本地库加载完后紧接着会查找库中是否有JNI_OnLoad函数,如果有会执行一下。

dvmLoadNativeCode()这个函数还真的是有点复杂,那就挑那些跟我们的JNI比较紧密相关的逻辑来看吧。可以认为这个函数做了下面的这样一些事情:

  • 调用dlopen() 打开一个so文件,创建一个handle。
  • 调用dlsym()函数,查找到so文件中的JNI_OnLoad()这个函数的函数指针。
  • 执行上一步找到的那个JNI_OnLoad()函数。

http://blog.csdn.net/dj0379/article/details/53350038

注册函数

先看JNI_OnLoad方法,这是注册native函数的地方。

  • 首先判断jni版本是不是JNI_VERSION_1_4
  • FindClass方法找到我们对应生命native函数的类,返回一个jclass
  • RegisterNatives 注册native函数,我这里用这个三个参数的方法,第一个表示对应jclass,第二个表示JNINativeMethod的数组,第三个,这个就是我们先前命名的宏。

JNINativeMethod数组

对应结构体

1
2
3
4
5
typedef struct {
const char* name;//Java层native方法的名字
const char* signature;//Java层native方法的描述符
void* fnPtr;//对应JNI函数的指针
} JNINativeMethod;
1
JNINativeMethod nativeMethod[] = {{"dynamicLog", "()V", (void*)nativeDynamicLog},};
  • 第一个参数对应的native方法名
  • 第二个参数对应 native方法的描述,我们通过javap -s class文件路径来获取。
  • 第三个参数对应的是c++代码里对应的实现

RegisterNatives函数

  • 第一个参数是Java层对应包含native方法的对象(这里就是AndroidJni对象),通过调用JNIEnv对应的函数获取class对象(FindClass函数的参数为需要获取class对象的类描述符)
  • 第二个参数是JNINativeMethod结构体指针,这里的JNINativeMethod结构体是描述Java层native方法的,也就是之前定义的数组
  • 第三个参数为注册native方法的数量。一般会动态注册多个native方法,首先会定义一个JNINativeMethod数组,然后将该数组指针作为RegisterNative函数的参数传入

描述符
对应smail语法

JNIEnv
http://blog.csdn.net/banketree/article/details/40535325

java和native之间的数据传递

http://blog.csdn.net/qinjuning/article/details/7607214