1、,JNI入门,时间:2010-08-22 作者:尧俊利/y00166693,目录,1. JNI示意图,JAVA代码,Native层,C代码,代码互调,1. 了解JNI,了解JNI JNI(Java Native Interface)是 SUN定义的一套标准接口,如Dalvik, Apache Harmony项目.等Java虚拟机,都会实现JNI接口,供本地(C/C+)应用与Java VM互调。 JNI是一套双向的接口,允许Java与本地代码间的互操作。,1. 碰到的问题,使用JNI会碰到的问题 在本地代码中new一个Java对象后期望在本地代码中维护此对象的引用,如何避免被GC? 对Local
2、Ref / GlobalRef管理不善,会引发Table Overflow Exception,导致应用崩溃。 从JNI调用Java的过程不是很直观,往往几行Java代码能搞定的事情,用JNI实现却要几百行。 也许还有新的问题需要我们去发现解决,2. Hello JNI,准备工作就绪,本地代码 实现定义,编译该类,HelloJni.java,2. HelloJni.java,HelloJni.java,public class HelloJni extends Activitypublic void onCreate(Bundle savedInstanceState)super.onCrea
3、te(savedInstanceState);TextView tv = new TextView(this);tv.setText( stringFromJNI() );setContentView(tv);/ 定义的一个native方法,并加载动态库public native String stringFromJNI();static System.loadLibrary(“hello-jni“ ); ,2. javah -jni,本地代码HelloJni.java 利用Eclipse工具,Build工程即可 javah -jni 产生头文件 到达工程下面的bin目录(在cmd里) 文件在
4、工程下的目录 /src/com/package/hellojni/HelloJni.java javah -jni com.package.hellojni.HelloJni 这个时候会在bin目录里面产生一个.h文件 提取需要的函数定义原型放入hello-jni.c文件,2. Hello-jni.c,实现hello-jni.c,#include #include JNIEXPORT jstring JNICALL Java_com_example_hellojni_HelloJni_stringFromJNI( JNIEnv* env, jobject this ) return (*env
5、)-NewStringUTF(env, “Hello from JNI !“); JNIEnv是JNI的核心数据,指向JNIEnv结构的指针。 jobject的意义取决于该方法是静态还是实例方法。当本地方法作为一个实例方法时,第二个参数相当于对象本身,即this. 当本地方法作为一个静态方法时,指向所在类。 jni.h文件必须被包含,该文件定义了JNI所有的函数声明和数据类型。 JNIEXPORT和JNICALL是可忽略的JNI关键字,其实是一些宏,想了解的可以找jni_md.h文件查看。我们目前使用的jni.h定义它们为空。,2. Android.mk,Android.mk # 工程库地址
6、LOCAL_PATH := $(call my-dir)#清除一些不用的文件 include $(CLEAR_VARS)#生成库文件名称 LOCAL_MODULE := hello-jni #需要 编译的文件 LOCAL_SRC_FILES := hello-jni.c#生成共享库 include $(BUILD_SHARED_LIBRARY),3. 类型映射关系,Java boolean byte char short int long float double String other,映射关系,Native jboolean jbyte jchar jshort jint jlong j
7、float jdouble jstring jobject,3. 字符串函数(1),3. 字符串函数(2),3. 选择字符串函数策略,3. 字符串函数策略解析,如果JDK为1.1或1.2 你只能使用Get/ReleaseStringChars和Get/ReleaseStringUTFChars。 对于小尺寸字串的操作 首选Get/SetStringRegion和Get/SetStringUTFRegion,因为栈上空间分配,开销要小的多。 而且没有内存分配,就不会有out-of-memory exception。 GetStringCritical必须非常小心使用 必须确保不分配新对象,因为可
8、能使JavaVM执行GC,由此可能发生死锁。 不执行任何阻塞系统的操作,以避免发生死锁。,jfieldID fid = (*env)- GetFieldID(env, cls, “s“, “Ljava/lang/String;“);,jclass cls = (*env)- GetObjectClass(env, obj);,jstring jstr = (*env)- GetObjectField(env, obj, fid);,3. Field,jclass,jfieldID,jstring,Fields示例: Java: class c String s; public native v
9、oid accessField(); C: void Java_c_accessField(JNIEnv *env, jobject obj) ,3. 签名,由于签名非常容易出错,对于错误的签名,编译器帮不上忙 在这里推荐大家用命令来获取签名后拷贝使用 cmd找到变化的*.class文件所在目录地址后 执行:javap -s -private 类名,jmethodID mid = (*env)- GetMethodID(env, cls, “callback“, “()V“);,jclass cls = (*env)- GetObjectClass(env, obj);,(*env)-Call
10、VoidMethod(env, obj, mid);,3. Method,jclass,jmethodID,call it,Fields示例: Java: class c String s; public void callback() C: void Java_c_accessField(JNIEnv *env, jobject obj) ,3. 缓存提升性能,3. 局部静态变量缓存,JNIEXPORT void JNICALL Java_InstanceFieldAccess_accessField(JNIEnv *env, jobject obj) static jfieldID fid
11、_s = NULL; /* cached field ID for s */jclass cls = (*env)-GetObjectClass(env, obj);jstring jstr;const char *str;if (fid_s = NULL) fid_s = (*env)-GetFieldID(env, cls, “s“, “Ljava/lang/String;“);if (fid_s = NULL) return; /* exception already thrown */jstr = (*env)-GetObjectField(env, obj, fid_s);str =
12、 (*env)-GetStringUTFChars(env, jstr, NULL);if (str = NULL) return; /* out of memory */(*env)-ReleaseStringUTFChars(env, jstr, str);jstr = (*env)-NewStringUTF(env, “123“);if (jstr = NULL) return; /* out of memory */ ,3. 初始化类时缓存(Java),class InstanceMethodCall private static native void initIDs();priva
13、te native void nativeMethod();private void callback() System.out.println(“In Java“);public static void main(String args) InstanceMethodCall c = new InstanceMethodCall();c.nativeMethod();static System.loadLibrary(“InstanceMethodCall“);initIDs(); ,3. 初始化类时缓存(Native),jmethodID MID_InstanceMethodCall_ca
14、llback; JNIEXPORT void JNICALL Java_InstanceMethodCall_initIDs(JNIEnv *env, jclass cls) MID_InstanceMethodCall_callback = (*env)-GetMethodID(env, cls, “callback“, “()V“); ,3. 缓存方式比较,static方式 第一次使用缓存的方式,每次都有与NULL的判断,并且可能有一个无害的竞争条件。 同时要保证类没有被卸载。,比较,类初始化方式 需要在Java代码主动调用JNI作缓存。,3. 改进缓存方式,可以在你的项目中加一套Hash
15、表;2. 封装FindClass,GetMethodID,GetFieldID等函数;3. 查询的所有操作,都对Hash表操作:如首次FindClass一个类,这时可以把一个类的所有成员缓存到Hash表中,用名字+签名做键值。资料提示:所做项目引入了这个优化,项目的执行效率有100倍的提高;当时还做过两个权衡:1. 用一个Hash表,还是每个类一个Hash表2. 首次FindClass类时,一次缓存所有的成员,还是用时缓存最终做的选择是:为了降低冲突,每个类一个Hash表,并且一次缓存一个类的所有成员。,3. Reference,Local Reference 只能在当前线程的native m
16、ethod中使用 如果不及时释放引用,就可能引起referenceTable overflow 因为这个引用表大小是有限的,一个引用占据一个位置 释放方式 Native method返回,Java VM自动释放 用DeleteLocalRef主动释放 Global Reference 在程序员主动释放前都是有效,防止被GC 可在多线程中使用 通过DeleteGlobalRef释放 Weak Global Reference 程序员主动释放,但是不保证引用不被GC 使用前需要是否被释放了 通过DeleteWeakGlobalRef释放,3. 管理引用策略,(*env)- PushLocalFra
17、me (env, 0);,(*env)- PopLocalFrame (env, result/NULL);,提示:SUN公司推荐使用这个策略来管理引用,使用时需要注意的是Push/PopLocalFrame必须配对使用,忘记调用PopLocalFrame可能会使VM崩溃。,4. 异常检查函数,异常检查函数 (*env)-ExceptionCheck(env) 用这个函数可以检查出来是否有异常,没有返回零 返回发生的异常 (*env)-ExceptionOccurred(env) 返回的对象会占用一个引用 打印调用堆栈 (*env)-ExceptionDescribe(env) 清空异常 (*
18、env)-ExceptionClear(env),附录,如果是C+代码 Native层函数都要以extern “C” 开头 调用的函数传的参数通常都少一个env 调用静态方法 CallStaticMethod 调用被子类覆盖的父类方法 CallNonvirtualMethod 调用实例方法 CallMethod 数组 NewArray 分配一个Type数组 Get/ReleaseArrayElement 返回/释放Java数组的一个拷贝 SetArrayRegion 设置数组一个区域的值 Get/SetObjectArrayElement 对象数组访问 NewObjectArray 分配一个对象数组,调用的是NewGlobalRef构造一个对象的,Thank You !,