网站建设企业宣传口号,广州竞价托管公司,做视频的网站带模板下载,网站后台上传缩略图文章目录 前言一、JNI 在Android 系统中所处的位置二、JNI框架层实例分析1.Log 系统Java层分析2.Log 系统的JNI层3.Log 系统的JNI方法注册 三、在java中调用JNI实现方法1.java数据类型与JNI数据类型的转换2.JNI方法命名规则3.JNI方法签名规则 四、JNI操作java对象1.访问java对象… 文章目录 前言一、JNI 在Android 系统中所处的位置二、JNI框架层实例分析1.Log 系统Java层分析2.Log 系统的JNI层3.Log 系统的JNI方法注册 三、在java中调用JNI实现方法1.java数据类型与JNI数据类型的转换2.JNI方法命名规则3.JNI方法签名规则 四、JNI操作java对象1.访问java对象2.操作成员变量和方法3.全局引用、弱全局引用和局部引用 五、JNI异常处理六、两种注册方式的比较1.静态注册2.动态注册 总结 前言
JNI Java Native InterfaceJava本地接口是Java平台上定义的一套标准的本地编程接口。JNI允许Java代码与本地代码(如C/C实现的、依赖于特定硬件和操作系统的代码)互操作即Java代码可以调用本地代码本地代码也可以调用Java代码。通过JNI调用本地代码可以实现Java语言所不能实现的功能。在Android平台上Dalvik虚拟机会实现JNI定义的接口。
总结JNI是java与本地Native代码的桥梁 一、JNI 在Android 系统中所处的位置
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编程模型的结构十分清晰可以概括为以下三个步骤
Java层声明Native方法。JNI层实现Java层声明的Native方法在JNI层可以调用底层库或者回调Java层方法。这部分将被编译为动态库(SO文件)供系统加载。加载JNI层代码编译后生成的共享库。
二、JNI框架层实例分析
在Android应用程序开发中一般是调用应用框架层的android.util.Log.java 提供的Java 接口来使用日志系统。比如我们会写如下代码输出日志
Log.d(TAG,debug 1og);这个Java接口其实是通过JNI调用系统运行库即本地库并最终调用内核驱动程序Logger把Log写到内核空间中的。在Android中Log系统十分通用而且其JNI结构非常简洁那今天我们就以它为例子分析一下JNI的具体调用流程。
1.Log 系统Java层分析
首先看Log系统Java层部分。打开Log.java文件可以看到这里面只定义了isLoggable 和printlnnative两个Native方法代码如下示例
package android.util;
public final class Log (
......public static int d(String tag,String msg)(
//使用Native方法打印日志LOGIDMAIN表示日志ID有4种main、radioevents、system return println_native(LOG_ID_MAIN, DEBUG, tag, msg);
}
//声明Native方法isLoggable
public static native boolean isLoggable(String tag, int level); ......//声明Native方法printinnative
/**hide*/
public static native int printin_native(int bufID,int priority, String tag, String msg);
)Java层需要做的就这么简单。只需要声明方法为native而无需实现就可以直接调用不会出现任何编译错误。
2.Log 系统的JNI层
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层声明的printinNative方法的实现代码
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层调用。
3.Log 系统的JNI方法注册
JNI层已经实现了Java层声明的Native方法。可这两个方法又是如何联系在一起的呢还是androidutilLog.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 层声明的Native 函数名为isLoggable。Java 层声明的Native函数的签名为(Ljava/lang/String;I)Z。JNI层实现方法的指针为(void*)android_util_Log_isLoggable。
至此我们给出了Java层方法和JNI层方法的对应关系。可如何告诉虚拟机这种对应关系呢继续分析 androidutilLog.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_castJNIEnv*(env);ALOGV(Registering %ss %d native methods..., className, numMethods);scoped_local_refjclass 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 VectorString8 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 havent really* started the VM yet, theyre 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实现方法
1.java数据类型与JNI数据类型的转换
基本类型转化关系 在java中调用jni的native方法传递的参数是java类型的这些参数必须经过Dalvik虚拟机转换成JNI类型的才能被JNI层所识别如图 java类型JNI类型字长booleanjboolean8bytejbyte8charjchar16shortjshort16intjint32longjlong64floatjfloat32doublejdouble64voidvoid-
为了使用方便特意定义
# define JNI_FALSE 0
# define JNI_TRUE 1
typedef jint jsize;//jsize整数类型表示大小引用类型转换关系 JNI的引用类型定义了九种数组类型以及jobject、jclass、jstring、jthrowable四种类型它们的继承关系如下图 它们与java类型的对应关系如下 java类型JNI类型java.lang.Classjclassjava.lang.Stringjstringjava.lang.ThrowablejthrowableObject[]、boolean[]、byte[]、char[]、short[]、int[]、long[]、float[]、double[]jobjectArray、jbooleanArray、jbyteArray、jcharArray、jshortArray、jintArray、jlongArray、jfloatArray、jdoubleArrayjava.lang.Objectjobject
2.JNI方法命名规则
Log系统中JNI实现方法与Java声明方法是不同的。例如Java层声明的Native方法名是isLoggable,而其对应的NI实现方法的方法名却是android_util_Log_isLoggable。可见除了数据类型有对应关系外方法名也有对应关系。
JNI接口指针是JNI实现方法的第一个参数其类型是JNIEnv。第二个参数因本地方法是静态还是非静态而有所不同。非静态本地方法的第二个参数是对Java对象的引用而静态本地方法的第二个参数是对其Java类的引用。其余的参数都对应于Java方法的参数。 JNI规范里提供了JNI实现方法的命名规则方法名由以下几部分串接而成
Java前缀全限定的类名下划线(_)分隔符增加第一参数JNIEnv* env增加第二个参数jobject其他参数按类型映射返回值按类型映射
继续以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开发的时候采用这种方式比较方便
3.JNI方法签名规则
有了数据类型之间的对应关系JNI就可以正确识别并转换Java类型。那JNI又是如何识别Java的方法呢Java支持方法重载仅靠函数名是无法唯一确定一个方法的。于是JNI提供了一套签名规则用一个字符串来唯一确定一个方法。其规则如下
(参数1类型签名参数2类型签名…参数n类型签名)返回值类型签名 //中间没有空格类型签名的规则如下表
Java类型类型签名Java类型类型签名booleanZlongJbyteBfloatFcharCdoubleDshortS类L全限定类名;intI数组[元素类型签名
还记得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对象
前面说到JNI是java与Native的桥梁能相互操作之前讲到的是java层调用Native方法那Native层如何调用或操作java层呢那么就需要关注JNI方法函数的第二个参数jobject
1.访问java对象
要操作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的对象这样就可以操作这个类的方法和变量了。
2.操作成员变量和方法
上面我们拿到的类的信息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提供的操作域和方法的函数。
访问对象变量调用实例方法访问静态变量调用静态方法GetFieldIDGetMethodIDGetStaticFieldIDGetStaticMethodIDGetTypeFieldCallTypeMethodGetStaticTypeFieldCallStaticTypeMethodSetTypeFieldCallNonvirtualTypeMethodSetStaticTypeField-
3.全局引用、弱全局引用和局部引用
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已经为我们提供了解决方案局部引用、全局引用和弱全局引用。
局部引用可以增加引用计数作用范围为本线程生命周期为一次Native调用。局部引用包括多数JNI函数创建的引用Native方法返回值和参数。局部引用只在创建它的Native方法的线程中有效并且只在Native方法的一次调用中有效在该方法返回后被虚拟机回收不同于C中的局部变量返回后会立即回收)。全局引用可以增加引用计数。作用范围为多线程多个Native方法生命周期到显式释放。全局引用通过JNI函数NewGlobalRef创建并通过DeleteGlobalRef释放。如果程序员不显式释放将永远不会被垃圾回收。弱全局引用不能增加引用计数。作用范围为多线程多个Native方法生命周期到显式释放。不过其对应的Java对象生命周期依然取决于虚拟机意思是即便弱全局引用没有被释放其引用的Java对象可能已经被释放。弱全局引用通过JNI函数NewWeakGlobalRef创建并通过DeleteWeakGlobalRef释放。弱全局引用的优点是既可以保存对象又不会阻止该对象被回收。 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函数调用的返回值是否为NULL通过调用JNI函数ExceptionOccurred()来判断是否发生异常
处理异常也有两种方式
Native方法可选择立即返回异常会在调用噶Native方法的java代码中抛出所以java层必须捕获Native方法调用ExceptionClear()来清除异常然后执行自己的异常处理代码
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_castJNIEnv*(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层抛出一个新的异常
六、两种注册方式的比较
1.静态注册
优点符合JNI规范方便简单 缺点需要遵守繁琐的JNI实现方法的命名规则。加载共享库的代码应用层若频繁调用严重影响效率。虚拟机在共享库中搜索定位JNI实现方法效率也受影响。
2.动态注册
优点效率高不需要遵守命名规则修改、移植方便。 缺点实现步骤比静态注册多稍显麻烦。
如何在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有足够的认识对于深人学习框架层代码会有一定的帮助。如果你在文章中发现有错误欢迎指正 文章转载自: http://www.morning.xkgyh.cn.gov.cn.xkgyh.cn http://www.morning.svtxeu.com.gov.cn.svtxeu.com http://www.morning.rwlnk.cn.gov.cn.rwlnk.cn http://www.morning.rfzbm.cn.gov.cn.rfzbm.cn http://www.morning.mlhfr.cn.gov.cn.mlhfr.cn http://www.morning.nkcfh.cn.gov.cn.nkcfh.cn http://www.morning.gqbtw.cn.gov.cn.gqbtw.cn http://www.morning.clbzy.cn.gov.cn.clbzy.cn http://www.morning.pzrnf.cn.gov.cn.pzrnf.cn http://www.morning.lyjwb.cn.gov.cn.lyjwb.cn http://www.morning.qwfl.cn.gov.cn.qwfl.cn http://www.morning.nckzt.cn.gov.cn.nckzt.cn http://www.morning.jwmws.cn.gov.cn.jwmws.cn http://www.morning.nzqqd.cn.gov.cn.nzqqd.cn http://www.morning.aswev.com.gov.cn.aswev.com http://www.morning.fmdvbsa.cn.gov.cn.fmdvbsa.cn http://www.morning.jcjgh.cn.gov.cn.jcjgh.cn http://www.morning.syxmx.cn.gov.cn.syxmx.cn http://www.morning.kjksn.cn.gov.cn.kjksn.cn http://www.morning.fbmzm.cn.gov.cn.fbmzm.cn http://www.morning.tkgxg.cn.gov.cn.tkgxg.cn http://www.morning.lsnhs.cn.gov.cn.lsnhs.cn http://www.morning.bpncd.cn.gov.cn.bpncd.cn http://www.morning.ktskc.cn.gov.cn.ktskc.cn http://www.morning.sloxdub.cn.gov.cn.sloxdub.cn http://www.morning.hrpjx.cn.gov.cn.hrpjx.cn http://www.morning.fjscr.cn.gov.cn.fjscr.cn http://www.morning.lnbcx.cn.gov.cn.lnbcx.cn http://www.morning.qjmnl.cn.gov.cn.qjmnl.cn http://www.morning.jpjpb.cn.gov.cn.jpjpb.cn http://www.morning.lcbnb.cn.gov.cn.lcbnb.cn http://www.morning.pfntr.cn.gov.cn.pfntr.cn http://www.morning.ypzr.cn.gov.cn.ypzr.cn http://www.morning.hlrtzcj.cn.gov.cn.hlrtzcj.cn http://www.morning.qwnqt.cn.gov.cn.qwnqt.cn http://www.morning.zdwjg.cn.gov.cn.zdwjg.cn http://www.morning.fbjnr.cn.gov.cn.fbjnr.cn http://www.morning.bzkgn.cn.gov.cn.bzkgn.cn http://www.morning.mqzcn.cn.gov.cn.mqzcn.cn http://www.morning.rccbt.cn.gov.cn.rccbt.cn http://www.morning.sknbb.cn.gov.cn.sknbb.cn http://www.morning.rbjf.cn.gov.cn.rbjf.cn http://www.morning.kfwqd.cn.gov.cn.kfwqd.cn http://www.morning.khdw.cn.gov.cn.khdw.cn http://www.morning.phtqr.cn.gov.cn.phtqr.cn http://www.morning.trrpb.cn.gov.cn.trrpb.cn http://www.morning.yqwrj.cn.gov.cn.yqwrj.cn http://www.morning.yfmlj.cn.gov.cn.yfmlj.cn http://www.morning.kwxr.cn.gov.cn.kwxr.cn http://www.morning.nmbbt.cn.gov.cn.nmbbt.cn http://www.morning.qpqb.cn.gov.cn.qpqb.cn http://www.morning.kdrly.cn.gov.cn.kdrly.cn http://www.morning.rxrw.cn.gov.cn.rxrw.cn http://www.morning.dfffm.cn.gov.cn.dfffm.cn http://www.morning.skbkq.cn.gov.cn.skbkq.cn http://www.morning.c-ae.cn.gov.cn.c-ae.cn http://www.morning.nxfuke.com.gov.cn.nxfuke.com http://www.morning.mpszk.cn.gov.cn.mpszk.cn http://www.morning.pcshb.cn.gov.cn.pcshb.cn http://www.morning.gkmwk.cn.gov.cn.gkmwk.cn http://www.morning.fkmqg.cn.gov.cn.fkmqg.cn http://www.morning.mlwpr.cn.gov.cn.mlwpr.cn http://www.morning.gtxrw.cn.gov.cn.gtxrw.cn http://www.morning.rfyk.cn.gov.cn.rfyk.cn http://www.morning.tsycr.cn.gov.cn.tsycr.cn http://www.morning.gklxm.cn.gov.cn.gklxm.cn http://www.morning.yhljc.cn.gov.cn.yhljc.cn http://www.morning.trjdr.cn.gov.cn.trjdr.cn http://www.morning.wpkr.cn.gov.cn.wpkr.cn http://www.morning.qygfb.cn.gov.cn.qygfb.cn http://www.morning.brsgw.cn.gov.cn.brsgw.cn http://www.morning.gpcy.cn.gov.cn.gpcy.cn http://www.morning.plqqp.cn.gov.cn.plqqp.cn http://www.morning.tjpmf.cn.gov.cn.tjpmf.cn http://www.morning.kqkmx.cn.gov.cn.kqkmx.cn http://www.morning.lpnpn.cn.gov.cn.lpnpn.cn http://www.morning.sxcwc.cn.gov.cn.sxcwc.cn http://www.morning.prmyx.cn.gov.cn.prmyx.cn http://www.morning.tdwjj.cn.gov.cn.tdwjj.cn http://www.morning.yxkyl.cn.gov.cn.yxkyl.cn