JNI学习

来源:互联网 发布:手机号码搜索软件 编辑:程序博客网 时间:2024/06/09 21:28

利用JDK javah命令生成JNI头文件

首先,先编译获得class文件。在android工程中,可以编写一个存放全部native方法的类,如下:

com.example.testpublic class NativeInterface {    public static native void test();    public static native int getNum();}

编译工程后,去build中的classes找出相应class文件。
这里写图片描述
然后,注意我们的文件的包名,java文件是存放在 com/example/test/下的,进入com目录的上一层目录,执行 javah com.example.test.NativeInterface,就会将该class文件转换为jni的头文件。

利用Javap 生成方法签名和属性签名。

首先,进入生成的.class文件目录,直接执行javap -s -p 类名。即可生成反编译的文件,并获得签名。
这里写图片描述
主要需要注意的是,这里反编译针对的是.class文件

JNI官方手册要点简记

先分享一个Jni学习手册,内容不错:
http://wiki.jikexueyuan.com/project/jni-ndk-developer-guide/

本地方法变量

JNI interface pointer是本地方法变量的第一个方法,第二个方法区别于静态还是非静态,如果是非静态方法则传入Java层的对象引用。在操作Java的数据时要使用 interface pointer进行。
C++ Jni程序:

extern "C" /* specify the C calling convention */  jdouble Java_pkg_Cls_f__ILjava_lang_String_2 (      JNIEnv *env,        /* interface pointer */      jobject obj,        /* "this" pointer */      jint i,             /* argument #1 */      jstring s)          /* argument #2 */ {      const char *str = env->GetStringUTFChars(s, 0);      ...      env->ReleaseStringUTFChars(s, str);      return ... } 

因为C++为了支持重载,代码编译后方法并会改变,添加 extern “C”{…}来以C风格命名,防止方法找不到的情况。

JNI全局引用和本地引用

在JNI中,local reference随着一个native method的返回而释放,global reference会一直存在,需要手动释放。
Java的对象会作为本地引用传递到native method中,在大多数情况下在本地方法返回后依靠虚拟机释放对象即可,然而某些时候还是要手动释放资源:JNI中的全局引用和本地引用,都是针对本地方法所持有的java对象的引用而言的。比如本地方法需要使用一个Java层中的对象,那么持有一个全局引用可以保证即便当前本地方法返回该对象也不会被JVM垃圾回收!这样,我们可以通过本地引用和全局引用,脱离JVM对该对象垃圾回收的控制,但是注意要在使用结束后手动释放!

  • 本地方法持有一个占用内存比较大的java对象的引用,在进行计算并返回之前,java对象所占用的内存不会被回收,即便已经不再被需要。所以我们应该在完成计算前,及时回收不必要的资源。
  • 本地方法中创建了很多本地引用,即使没有同时使用它们。比如遍历集合并创建相应的本地引用进行操作,却存储了额外不必要的本地引用。

使用域和方法

JNI允许native方法调用java方法。通过两个步骤:

jmethodID mid =      env->GetMethodID(cls, “f”, “(ILjava/lang/Sjdouble result = env->CallDoubleMethod(obj, mid, 10, str); 

注意,生成这个id的class可能被虚拟机卸载,如果卸载了那么这个id就不再有效。

变量

基本类型变量

这里写图片描述
上图展示了native方法和java方法的变量对照。

方法类型签名

JNI使用java的表达式生成的签名,如下表:
这里写图片描述
比如一个java 方法:

long f (int n, String s, int[] arr); 

生成签名时,首先先生成参数的签名,第一个参数是int类型即为I,第二个是String类型,没有对应的基本类型签名,所以采用L fully-qualified-class ;的格式,也就是写出String类型的包名/文件名即Ljava/lang/String;。注意左边的L和右边的’;’都是签名的一部分,然后是int类型数组为 [I ,最后返回long为 J
类型签名最后是:
(ILjava/lang/String;[I)J


利用本地方法生成Java对象。

Java代码:

package com.example.yy.ndkdemo;/** * Created by zxc on 2016/11/20. */public class JniObject {    int filedId;    public JniObject(int filedId) {        this.filedId = filedId;    }    public int getFiledId() {        return filedId;    }}

本地方法代码:

JNIEXPORT jobject JNICALL Java_com_example_yy_ndkdemo_JniInterface_createJniObject        (JNIEnv *env, jclass clazz){    jclass targetClass;    jmethodID mid;    jobject newObject;    //关键注意这里的参数是全限定名,从包名开始/隔开    targetClass = env->FindClass("com/example/yy/ndkdemo/JniObject");    mid = env->GetMethodID(targetClass, "<init>", "(I)V");    newObject = env->NewObject(targetClass, mid, 1000);    return newObject;}

本地方法获取数组及对象属性

JNIEXPORT void JNICALL Java_com_sogou_speech_utils_VadNative_vadDetect        (JNIEnv *env, jclass cls, jshortArray rawX,jobject javaVadRes) {    if (client_vad == NULL) return;   //获取short数组引用,NULL表示不关心是原数组指针还是在本地缓存区生成新缓存    jshort *rawInput = env->GetShortArrayElements(rawX, NULL);    client_vad->detect_speech(rawInput, seg_len, pack_id, vadRes);    jclass clazz;    //获取传入对象的class对象    clazz = env->GetObjectClass(javaVadRes);    if (clazz == NULL) return;    //获取对象属性值并设置。    jfieldID m_is_speech_found_id = env->GetFieldID(clazz, "m_is_speech_found", "Z");    env->SetBooleanField(javaVadRes, m_is_speech_found_id, (jboolean)vadRes.m_is_speech_found);    //删除本地局部引用。防止JVM表溢出    env->DeleteLocalRef(clazz);    //释放数组引用    env->ReleaseShortArrayElements(rawX, rawInput,0);}
0 0
原创粉丝点击