赞
踩
JNI (Java Native Interface,Java本地接口)是Java平台上定义的一套标准的本地编程接口。JNI允许Java代码与本地代码(如C/C++实现的、依赖于特定硬件和操作系统的代码)互操作,即Java代码可以调用本地代码,本地代码也可以调用Java代码。通过JNI调用本地代码,可以实现Java语言所不能实现的功能。在Android平台上,Dalvik虚拟机会实现JNI定义的接口。
总结:JNI是java与本地Native代码的桥梁
Android采用分层的体系结构:上层的应用层和应用框架层主要使用Java语言开发;下层则运行一个Linux内核,并在内核之上集成了各种核心库和第三方库,以提供系统运行所需的服务,这部分是用C和C++语言开发。连接这两部分的纽带就是JNI。
从图中可以看出,JNI可以直接调用本地代码库,并可以通过Dalvik虚拟机实现与应用层和应用框架层之间的交互。Android JNI部分的代码主要位于Android体系结构中的上面两层:
□应用层:采用NDK开发,主要使用标准JNI编程模型实现。
□应用框架层:Android定义了一套JNI编程模型,使用函数注册方式弥补了标准JNI编程模型的不足。
Android 应用框架层JNI部按照模块组织,不同的模块将被编译为不同的共享库,分别为上层提供不同的服务。
NDK与JNI的区别:NDK是为便于开发基于JNI的应用而提供的一套开发和编译工具集;而JNI则是一套编程接口,可以运用在应用层,也可以运用在应用框架层,以实现Java代码与本地代码的互操作。
JNI编程模型的结构十分清晰,可以概括为以下三个步骤:
在Android应用程序开发中,一般是调用应用框架层的android.util.Log.java 提供的Java 接口来使用日志系统。比如我们会写如下代码输出日志:
Log.d(TAG,"debug 1og");
这个Java接口其实是通过JNI调用系统运行库(即本地库)并最终调用内核驱动程序Logger把Log写到内核空间中的。在Android中,Log系统十分通用,而且其JNI结构非常简洁,那今天我们就以它为例子分析一下JNI的具体调用流程。
首先看Log系统Java层部分。打开Log.java文件,可以看到这里面只定义了isLoggable 和println_native两个Native方法,代码如下(示例):
package android.util; public final class Log ( ...... public static int d(String tag,String msg)( //使用Native方法打印日志.LOG_ID_MAIN表示日志ID,有4种:main、radio.events、system return println_native(LOG_ID_MAIN, DEBUG, tag, msg); } //声明Native方法isLoggable public static native boolean isLoggable(String tag, int level); ...... //声明Native方法printin_native /**@hide*/ public static native int printin_native(int bufID,int priority, String tag, String msg); )
Java层需要做的就这么简单。只需要声明方法为native而无需实现,就可以直接调用,不会出现任何编译错误。
JNI层是实现Java层方法最关键的部分。对于Log类,其对应的JNI文件是 android_util_Log.cpp。代码如下(示例):
#include "jni.h” //符合JNI规范的头文件,必须包含进来#include "JNIHelp.h" //Android为更好地支持JNI提供的头文件 #include "utils/misc.h" #include "android_runtime/AndroidRuntime.h" /*这里便是Java层声明的isLoggable方法的实现代码。 *JNI方法增加了JNIEnv和jobject两个参数,其余参数和返回值只是将Java参数映射成JNI *的数据类型,然后通过调用本地库和JNIEnv提供的JNI函数处理数据,最后返回给Java层*/ static jboolean isLoggable(const char* tag, jint level) { return __android_log_is_loggable(level, tag, ANDROID_LOG_INFO); } static jboolean android_util_Log_isLoggable(JNIEnv* env, jobject clazz, jstring tag, jint level) { if (tag == NULL) { return false; } //这里调用了JNI函数 const char* chars = env->GetStringUTFChars(tag, NULL); if (!chars) { return false; } jboolean result = false; if ((strlen(chars)+sizeof(LOG_NAMESPACE)) > PROPERTY_KEY_MAX) { char buf2[200]; snprintf(buf2, sizeof(buf2), "Log tag \"%s\" exceeds limit of %zu characters\n", chars, PROPERTY_KEY_MAX - sizeof(LOG_NAMESPACE)); jniThrowException(env, "java/lang/IllegalArgumentException", buf2); } else { //这里调用了本地库函数 result = isLoggable(chars, level); } env->ReleaseStringUTFChars(tag, chars); return result; } //以下是Java层声明的printin_Native方法的实现代码 static jint android_util_Log_println_native(JNIEnv* env, jobject clazz, jint bufID, jint priority, jstring tagObj, jstring msgObj) { const char* tag = NULL; const char* msg = NULL; if (msgObj == NULL) { jniThrowNullPointerException(env, "println needs a message"); return -1; } if (bufID < 0 || bufID >= LOG_ID_MAX) { jniThrowNullPointerException(env, "bad bufID"); return -1; } if (tagObj != NULL) tag = env->GetStringUTFChars(tagObj, NULL);//这里调用了JNI函数 msg = env->GetStringUTFChars(msgObj, NULL); int res = __android_log_buf_write(bufID, (android_LogPriority)priority, tag, msg); if (tag != NULL) env->ReleaseStringUTFChars(tagObj, tag);//这里调用了JNI函数释放资源 env->ReleaseStringUTFChars(msgObj, msg); return res; }
从这里以看出,JNI层的实现方法只是根据一定的规则与Java层声明的方法做了一个映射,然后可以通过使用本地库函数或JNIEnv提供的JNI函数响应Java层调用。
JNI层已经实现了Java层声明的Native方法。可这两个方法又是如何联系在一起的呢?还是android_util_Log.cpp中的源码:
/*
* JNI registration.
*/
static JNINativeMethod gMethods[] = {
/* name, signature, funcPtr */
{ "isLoggable", "(Ljava/lang/String;I)Z", (void*) android_util_Log_isLoggable },
{ "println_native", "(IILjava/lang/String;Ljava/lang/String;)I", (void*) android_util_Log_println_native },
};
这里定义了一个数组gMethods,用来存储JNINativeMethod类型的数据。 可以在jni.h文件中找到JNINativeMethod的定义:
typedef struct {
const char* name;
const char* signature;
void* fnPtr;
} JNINativeMethod;
可见,JNINativeMethod是一个结构体类型,保存了声明函数和实现函数的一一对应关系。
看一下gMethods中的第一句:
( "isLoggable", "(Ljava/lang/String;I)2", (void*) android_util_Log_isLoggable }
至此,我们给出了Java层方法和JNI层方法的对应关系。可如何告诉虚拟机这种对应关系呢?继续分析 android_util_Log.cpp源码。定位到以下部分:
int register_android_util_Log(JNIEnv* env)
{
jclass clazz = FindClassOrDie(env, "android/util/Log");
levels.verbose = env->GetStaticIntField(clazz, GetStaticFieldIDOrDie(env, clazz, "VERBOSE", "I"));
levels.debug = env->GetStaticIntField(clazz, GetStaticFieldIDOrDie(env, clazz, "DEBUG", "I"));
levels.info = env->GetStaticIntField(clazz, GetStaticFieldIDOrDie(env, clazz, "INFO", "I"));
levels.warn = env->GetStaticIntField(clazz, GetStaticFieldIDOrDie(env, clazz, "WARN", "I"));
levels.error = env->GetStaticIntField(clazz, GetStaticFieldIDOrDie(env, clazz, "ERROR", "I"));
levels.assert = env->GetStaticIntField(clazz, GetStaticFieldIDOrDie(env, clazz, "ASSERT", "I"));
return RegisterMethodsOrDie(env, "android/util/Log", gMethods, NELEM(gMethods));
}
这个函数最后调用了 AndroidRuntime::registerNativeMethods,并将gMethods数组、Java 层类名以及一个JNIEnv类型的指针一同传给 registerNativeMethods。接着我们打开AndroidRuntime.cpp定位到registerNativeMethods函数:
/*
* Register native methods using JNI.
*/
/*static*/ int AndroidRuntime::registerNativeMethods(JNIEnv* env,
const char* className, const JNINativeMethod* gMethods, int numMethods)
{
return jniRegisterNativeMethods(env, className, gMethods, numMethods);
}
接着打开JNIHelp.cpp
extern "C" int jniRegisterNativeMethods(C_JNIEnv* env, const char* className, const JNINativeMethod* gMethods, int numMethods) { JNIEnv* e = reinterpret_cast<JNIEnv*>(env); ALOGV("Registering %s's %d native methods...", className, numMethods); scoped_local_ref<jclass> c(env, findClass(env, className)); if (c.get() == NULL) { char* msg; asprintf(&msg, "Native registration unable to find class '%s'; aborting...", className); e->FatalError(msg); } if ((*env)->RegisterNatives(e, c.get(), gMethods, numMethods) < 0) { char* msg; asprintf(&msg, "RegisterNatives failed for '%s'; aborting...", className); e->FatalError(msg); } return 0; }
这里最终调用了JNIEnv的RegisterNatives方法,将gMethods中存储的关联信息传递给虚拟机,在jni.h中找到RegisterNatives:
jint (*RegisterNatives)(JNIEnv*, jclass, const JNINativeMethod*,jint);
其作用是向jclass指定的类注册本地方法,这样细腻及就得到了java层与jni层的对应关系,就可以实现java与C/C++相互操作了。
到现在整个流程基本清晰了,最后来看看这个register_android_util_Log注册方法在哪里调用的呢?
原来,在Android系统启动的时候,由init进程启动Zygote的时候,会创建虚拟机,然后注册系统的jni方法,主要的代码:
void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote) { ... ... if (startVm(&mJavaVM, &env, zygote) != 0) { return; } onVmCreated(env); /* * Register android functions. */ if (startReg(env) < 0) { ALOGE("Unable to register all android natives\n"); return; } ... ...
其中调用startReg:
/*static*/ int AndroidRuntime::startReg(JNIEnv* env) { /* * This hook causes all future threads created in this process to be * attached to the JavaVM. (This needs to go away in favor of JNI * Attach calls.) */ androidSetCreateThreadFunc((android_create_thread_fn) javaCreateThreadEtc); ALOGV("--- registering native functions ---\n"); /* * Every "register" function calls one or more things that return * a local reference (e.g. FindClass). Because we haven't really * started the VM yet, they're all getting stored in the base frame * and never released. Use Push/Pop to manage the storage. */ env->PushLocalFrame(200); if (register_jni_procs(gRegJNI, NELEM(gRegJNI), env) < 0) { env->PopLocalFrame(NULL); return -1; } env->PopLocalFrame(NULL); //createJavaThread("fubar", quickTest, (void*) "hello"); return 0; }
再调用register_jni_procs,其中该函数有个gRegJNI参数,定位它定义的地方看一下:
static const RegJNIRec gRegJNI[] = {
REG_JNI(register_com_android_internal_os_RuntimeInit),
... ...
REG_JNI(register_android_util_Log),
... ...
果然,其中包含的就是register_android_util_Log函数,这样,就在系统启动的时候就将Log系统的jni注册了
JNIEnv是一个指针,指向一个线程相关的结构,该结构维护了一个数组,该数组每个元素指向一个JNI函数,通过JNIEnv操作JNI函数,访问虚拟机,进而操作java对象
上面讲述的就是传说中的JNI动态注册方式,在Android的框架层主要就是以这种方式进行JNI的注册
java类型 | JNI类型 | 字长 |
---|---|---|
boolean | jboolean | 8 |
byte | jbyte | 8 |
char | jchar | 16 |
short | jshort | 16 |
int | jint | 32 |
long | jlong | 64 |
float | jfloat | 32 |
double | jdouble | 64 |
void | void | - |
为了使用方便,特意定义:
# define JNI_FALSE 0
# define JNI_TRUE 1
typedef jint jsize;//jsize整数类型表示大小
java类型 | JNI类型 |
---|---|
java.lang.Class | jclass |
java.lang.String | jstring |
java.lang.Throwable | jthrowable |
Object[]、boolean[]、byte[]、char[]、short[]、int[]、long[]、float[]、double[] | jobjectArray、jbooleanArray、jbyteArray、jcharArray、jshortArray、jintArray、jlongArray、jfloatArray、jdoubleArray |
java.lang.Object | jobject |
Log系统中,JNI实现方法与Java声明方法是不同的。例如,Java层声明的Native方法名是isLoggable,而其对应的NI实现方法的方法名却是android_util_Log_isLoggable。可见,除了数据类型有对应关系外,方法名也有对应关系。
JNI接口指针是JNI实现方法的第一个参数,其类型是JNIEnv。第二个参数因本地方法是静态还是非静态而有所不同。非静态本地方法的第二个参数是对Java对象的引用,而静态本地方法的第二个参数是对其Java类的引用。其余的参数都对应于Java方法的参数。
JNI规范里提供了JNI实现方法的命名规则,方法名由以下几部分串接而成:
继续以Log系统为例讲解。Java部分方法声明如下:
public static native boolean isLoggable(String tag,int level);
JNI部分方法实现如下:
static jboolean android_util_Log_isLoggable(JNIEnv*env,jobject clazz,jstring tag,jint level).....)
从Log系统的NI实现方法看到,Android并没有严格遵守JNI的方法命名规范。
Android在框架层采用函数注册的方式,建立Java层声明方法与JNI层实现方法之间的对应关系,可以不遵守上述命名规则。
严格按照这里的命名规则进行注册即静态注册,一般在进行NDK开发的时候采用这种方式比较方便
有了数据类型之间的对应关系,JNI就可以正确识别并转换Java类型。那JNI又是如何识别Java的方法呢?Java支持方法重载,仅靠函数名是无法唯一确定一个方法的。于是JNI提供了一套签名规则,用一个字符串来唯一确定一个方法。其规则如下:
(参数1类型签名参数2类型签名…参数n类型签名)返回值类型签名 //中间没有空格
类型签名的规则如下表:
Java类型 | 类型签名 | Java类型 | 类型签名 |
---|---|---|---|
boolean | Z | long | J |
byte | B | float | F |
char | C | double | D |
short | S | 类 | L全限定类名; |
int | I | 数组 | [元素类型签名 |
还记得gMethods中的代码吧:
static JNINativeMethod gMethods[] = {
/* name, signature, funcPtr */
{ "isLoggable", "(Ljava/lang/String;I)Z", (void*) android_util_Log_isLoggable },
{ "println_native", "(IILjava/lang/String;Ljava/lang/String;)I", (void*) android_util_Log_println_native },
};
现在应该明白了isLoggable有两个参数,一个是String类型,一个是int类型,返回值是boolean。
前面说到JNI是java与Native的桥梁,能相互操作,之前讲到的是java层调用Native方法,那Native层如何调用或操作java层呢?那么就需要关注JNI方法函数的第二个参数jobject
要操作jobject,就是要访问这个对象并操作它的变量和方法,JNI提供的类或对象操作函数有很多,常用的有两个:FindClass和GetObjectClass,在C和C++中分别有不同的函数原型。
C++中的函数原型如下:
jclass FindClass(const char* name); //查找类信息
jclass GetObjectClass(jobject obj); //返回对象的类
C中的函数原型如下:
jclass (*Findclass)(JNIEnv,const char);
jclass (*GetObjectClass)(JNIEnv*, jobject);
我们可以看看Log系统是怎么操作Java对象的。打开android_util_Log.cpp,定位到register_android_util_Log函数:
int register_android_util_Log(JNIEnv* env){
jclass clazz = env->FindClass("android/util/Log");
... ...
}
通过给FindClass传入要查找类的全限定类名(以“/”分隔路径)即可,之后方法返回一个jclass的对象,这样就可以操作这个类的方法和变量了。
上面我们拿到的类的信息clazz,通过这个就能操作它的变量和方法了
levels.debug = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "DEBUG", "I"));
其中DEBUG是要访问的Java域的名字,I是该Java域的类型签名。GetStaticFieldID的函数原型如下:
jfieldID GetStaticFieldID(jclass clazz, const char* name, const char* sig)
该函数返回了一个jfieldID,代表Java成员变量。最后将该jfieldID传给GetStaticIntField方法,得到Java层的成员变量DEBUG的值,即3。可以打开Log.java看看变量DEBUG的值是不是3。
下表中列出了JNI提供的操作域和方法的函数。
访问对象变量 | 调用实例方法 | 访问静态变量 | 调用静态方法 |
---|---|---|---|
GetFieldID | GetMethodID | GetStaticFieldID | GetStaticMethodID |
Get<Type>Field | Call<Type>Method | GetStatic<Type>Field | CallStatic<Type>Method |
Set<Type>Field | CallNonvirtual<Type>Method | SetStatic<Type>Field | - |
Java对象的生命周期由虚拟机管理,虚拟机内部维护一个对象的引用计数,如果一个对象的引用计数为0,这个对象将被垃圾回收器回收并释放内存。这里就有一个问题,如果Java对象中使用了Native方法,那会对对象的生命周期产生什么影响呢?回答这个问题前,先看Log系统的例子。代码如下:
//static jobject clazz_ref1 = NULL;
static jboolean android_util_Log_isLoggable(JNIEnv* env, jobject clazz,jstring tag,jint level){
//clazz_ref1 = clazz;
//static jobject clazz_ref2 = NULL;
//clazz_ref2 = clazz;
if ((strlen(chars)+sizeof(LOG_NAMESPACE)) > PROPERTY _KEY_MAX)(
//异常处理代码
... ...
}else{
result = isLoggable(chars, level);
}
}
上面注释掉的代码用两种方式引用java对象,这种形式的写法虚拟机能正确的为此对象添加引用计数吗???答案是不行,虚拟机不能正确的增加其计数,clazz对象就有可能被回收,那么clazz_ref1或clazz_ref2就可能引用野指针。
幸运的是,JNIEnv已经为我们提供了解决方案:局部引用、全局引用和弱全局引用。
IsSameObject函数用来判断弱引用对应的对象是否已经被回收,方法是用弱全局引用和NULL进行比较,如果返回JNI_TRUE,则说明弱全局引用指向的对象已经被回收。
//static joject g_clazz_ref = NULL;
static jboolean android_util_Log_isLoggable(JNIEnv* env, jobject clazz,jstring tag,jint level){
//g_class_ref = env->NewGlobalRef(clazz);
if ((strlen(chars)+sizeof(LOG_NAMESPACE)) > PROPERTY _KEY_MAX)(
//异常处理代码
... ...
}else{
result = isLoggable(chars, level);
}
}
//在不使用该类的时候显式删除
env->DeleteGlobalRef(g_clazz_ref)
JNI提供两种检查异常的方法:
处理异常也有两种方式:
JNI提供的检查和处理异常的函数如下表所示。
JNI异常处理函数 | 功能描述 |
---|---|
Throw | 抛出现有异常 |
ThrowNew | 抛出新的异常 |
ExceptionOccurred | 判断是否发生异常,并获得异常的引用 |
ExceptionCheck | 判断是否发生异常 |
ExceptionDescribe | 异常堆栈信息 |
ExceptionClear | 清除一个未处理的异常 |
FatalError | 严重错误,退出 |
注意异常出现后,Native相关代码必须先检查清除异常,然后才能进行其他的JNI函数调用。当有异常未被清除时,只有以下JNI异常处理函数可被安全地调用:ExceptionOccurred()、ExceptionDescribe()、ExceptionClear()
接下来我们回到源码分析一波:
static jboolean android_util_Log_isLoggable(JNIEnv* env, jobject clazz, jstring tag, jint level) { if (tag == NULL) { return false; } const char* chars = env->GetStringUTFChars(tag, NULL); if (!chars) { return false; } jboolean result = false; if ((strlen(chars)+sizeof(LOG_NAMESPACE)) > PROPERTY_KEY_MAX) { char buf2[200]; snprintf(buf2, sizeof(buf2), "Log tag \"%s\" exceeds limit of %zu characters\n", chars, PROPERTY_KEY_MAX - sizeof(LOG_NAMESPACE)); jniThrowException(env, "java/lang/IllegalArgumentException", buf2); } else { result = isLoggable(chars, level); } env->ReleaseStringUTFChars(tag, chars); return result; }
找到其中的jniThrowException函数定义:
extern "C" int jniThrowException(C_JNIEnv* c_env, const char* className, const char* msg) { JNIEnv* env = reinterpret_cast<JNIEnv*>(c_env); jclass exceptionClass = env->FindClass(className); if (exceptionClass == NULL) { ALOGD("Unable to find exception class %s", className); /* ClassNotFoundException now pending */ return -1; } if (env->ThrowNew(exceptionClass, msg) != JNI_OK) { ALOGD("Failed throwing '%s' '%s'", className, msg); /* an exception, most likely OOM, will now be pending */ return -1; } env->DeleteLocalRef(exceptionClass); return 0; }
会看到调用ThrowNew向调用它的Java层抛出一个新的异常
优点:符合JNI规范,方便简单
缺点:需要遵守繁琐的JNI实现方法的命名规则。加载共享库的代码,应用层若频繁调用严重影响效率。虚拟机在共享库中搜索定位JNI实现方法效率也受影响。
优点:效率高,不需要遵守命名规则,修改、移植方便。
缺点:实现步骤比静态注册多,稍显麻烦。
如何在NDK开发中使用动态注册呢?上代码:
static JNINativeMethod gmethods[] = { {"show", "()Ljava/lang/String;”, (void*)Java_com_hision_jni_AppJniActivity_show} }; static int registerNativeMethods(JNIEnv* env, const char* className,JNINativeMethod* gMethods, int numMethods){ jclass clazz; clazz = (*env)->FindClass(env, className); if (clazz == NULL) { return JNI_FALSE; } //调用JNIEnv提供的注册函数向虚拟机注册 if ((*env)->RegisterNatives(env, clazz, gMethods, numMethods) < 0){ return JNI_FALSE; } return JNI_TRUE; } static int registerNatives(JNIEnv* env){ if (!registerNativeMethods(env, "com/hision/jni/AppJniActivity", methods, sizeof(methods) / sizeof(methods[0]))) { return JNI_FALSE; } return JNI_TRUE; } /*虚拟机执行System.loadLibrary("app_jni")后,进入libapp_jni.so后 *会首先执行这个方法,所以我们在这里做注册的动作*/ jint JNI_OnLoad (JavavM* vm, void* reserved){ jint result = -1; JNIEnv* env = NULL; if ((*vm)->GetEnv(vm, (void **) &env, JNI_VERSION_1_4)){ goto fail; } //最终调用(*env)->RegisterNatives,这跟Log系统是一样的 if (registerNatives(env) != JNI_TRUE) { goto fail; } result = JNI_VERSION_1_4; fail: return result; }
本文以Log系统的JNI实例为引线,贯穿了JNI技术的主要方面,如果你真的认真读完本文你会对JNI有足够的认识,对于深人学习框架层代码会有一定的帮助。如果你在文章中发现有错误,欢迎指正!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。