Dalvik虚拟机JNI方法的注册过程分析

在前面一文中,我们分析了Dalvik虚拟机的运行过程。从中可以知道,Dalvik虚拟机在调用一个成员函数的时候,如果发现该成员函数是一个JNI方法,那么就会直接跳到它的地址去执行。也就是说,JNI方法是直接在本地操作系统上执行的,而不是由Dalvik虚拟机解释器执行。由此也可看出,JNI方法是Android应用程序与本地操作系统直接进行通信的一个手段。在本文中,我们就详细分析JNI方法的注册过程。

老罗的新浪微博:,欢迎关注!

在Android系统中,JNI方法是以C/C++语言来实现的,然后编译在一个SO文件里面。这个JNI方法在能够被调用之前,首先要加载到当前应用程序进程的地址空间来,如下所示:

package shy.luo.jni;public class ClassWithJni {……static {System.loadLibrary("nanosleep");}……private native int nanosleep(long seconds, long nanoseconds);……}

上述代码假设ClassWithJni类有一个JNI方法nanosleep,它实现在一个名称为libnanosleep.so的文件中,因此,在该JNI方法能够被调用之前,我们首先要将它加载到当前应用程序进程来,这是通过调用System类的静态成员函数loadLibrary来实现的。

JNI方法nanosleep的实现如下所示:

#include "jni.h"#include "JNIHelp.h"#include <time.h>static jint shy_luo_jni_ClassWithJni_nanosleep(JNIEnv* env, jobject clazz, jlong seconds, jlong nanoseconds) {struct timespec req;req.tv_sec = seconds;req.tv_nsec = nanoseconds;return nanosleep(&req, NULL);}static const JNINativeMethod method_table[] = {{"nanosleep", "(JJ)I", (void*)shy_luo_jni_ClassWithJni_nanosleep},};extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved) { JNIEnv* env = NULL;jint result = -1;if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {return result;}jniRegisterNativeMethods(env, "shy/luo/jni/ClassWithJni", method_table, NELEM(method_table));return JNI_VERSION_1_4;}

假设上述函数经过编译之后,就位于一个名称为libnanosleep.so的文件。

当libnanosleep.so文件被加载的时候,函数JNI_OnLoad就会被调用。在函数JNI_OnLoad中,参数vm描述的是当前进程中的Dalvik虚拟机,通过调用它的成员函数GetEnv就可以获得一个JNIEnv对象。有了这个JNIEnv对象之后,我们就可以调用另外一个函数jniRegisterNativeMethods来向当前进程中的Dalvik虚拟机注册一个JNI方法shy_luo_jni_ClassWithJni_nanosleep。这个JNI方法即为shy.luo.jni.ClassWithJni类的成员函数nanasleep的实现。

JNI方法shy_luo_jni_ClassWithJni_nanosleep要做的事情实际上就是通过系统调用nanosleep来使得当前进程进入睡眠状态,直至seconds秒nanoseconds纳秒之后再唤醒。使用系统调用nanosleep来使得当前进程进入睡眠状态的好处它的时间精度可以达到纳秒级,但是这个系统调用有两个地方是需要注意的:

1. 如果进程在睡眠的过程中接收到信号,那么它就会提前被唤醒,这时候系统调用nanosleep的返回值为-1,并且错误代码errno被设置为EINTR。

2. 如果CPU的时钟中断精度达不到纳秒级别,那么nanosleep的睡眠精度也达不到纳秒级,也就是说,当前进程不一定能在指定的纳秒之后被唤醒,会有一定的延时。

不过,JNI方法shy_luo_jni_ClassWithJni_nanosleep的实现不是我们的重点,我们的重点是分析它注册到Dalvik虚拟机的过程。

前面提到,JNI方法shy_luo_jni_ClassWithJni_nanosleep是libnanosleep.so文件加载的时候被注册到Dalvik虚拟机的,因此,我们就从libnanosleep.so文件的加载开始,分析JNI方法shy_luo_jni_ClassWithJni_nanosleep注册到Dalvik虚拟机的过程,也就是从System类的静态成员函数loadLibrary开始分析一个JNI方法注册到Dalvik虚拟机的过程,如图1所示:

图1 JNI方法注册到Dalvik虚拟机的过程

这个过程可以分为12个步骤,接下来我们就详细分析每一个步骤。

Step 1. System.loadLibrary

public final class System {……public static void loadLibrary(String libName) {SecurityManager smngr = System.getSecurityManager();if (smngr != null) {smngr.checkLink(libName);}Runtime.getRuntime().loadLibrary(libName, VMStack.getCallingClassLoader());}……}

这个函数定义在文件libcore/luni/src/main/java/java/lang/System.java中。

System类的成员函数loadLibrary首先调用SecurityManager类的成员函数checkLink来进行安全检查,即检查名称为libName的so文件是否允许加载。注意,这是Java的安全代码检查机制,而不是Android系统的安全检查机制,而且Android系统没有使用它来进行安全检查。因此,这个检查总是能通过的。

System类的成员函数loadLibrary接下来就再通过运行时类Runtime的成员函数loadLibrary来加载名称为libName的so文件,接下来我们就继续分析它的实现。

Step 2.Runtime.loadLibrary

public class Runtime {……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);}……} 这个函数定义在文件libcore/luni/src/main/java/java/lang/Runtime.java中。会得到最大的满足,因为它填补了你的空虚。

Dalvik虚拟机JNI方法的注册过程分析

相关文章:

你感兴趣的文章:

标签云: