JNI学习(二)之实例分析

来源:互联网 发布:汉高祖知乎 编辑:程序博客网 时间:2024/06/02 04:37

在上一篇我们讲解JNI技术时,说过通过这种技术可以做到以下两点:

  • Java程序中的函数可以调用Native语言写的函数,Native一般指的是C/C++编写的函数。
  • Native程序中的函数可以调用Java层的函数,也就是在C/C++程序中可以调用Java的函数。

本篇我们就来演示一下这两种情况的实现。

Java调用C/C++

其实JNI学习的第一篇文章设置编译环境实现的例子就是Java调用C++。
回顾一下流程吧:
1.Java中申明Native方法:

public class TestJNI {    public native int add(int x, int y);}

2.把装有native方法的类, 编出class文件。
3.将目标class文件编译成.h文件。

/* DO NOT EDIT THIS FILE - it is machine generated */#include <jni.h>/* Header for class com_hx_testjni_TestJNI */#ifndef _Included_com_hx_testjni_TestJNI#define _Included_com_hx_testjni_TestJNI#ifdef __cplusplusextern "C" {#endif/* * Class:     com_hx_testjni_TestJNI * Method:    add * Signature: (II)I */JNIEXPORT jint JNICALL Java_com_hx_testjni_TestJNI_add  (JNIEnv *, jobject, jint, jint);#ifdef __cplusplus}#endif#endif

4.针对.h编写c/c++逻辑。
c++代码:

#include <stdio.h>#include "com_hx_testjni_TestJNI.h"JNIEXPORT jint JNICALL Java_com_hx_testjni_TestJNI_add(JNIEnv *env, jobject obj, jint x, jint y) {    return x + y;}

很多人在最终成功编译so并载入so后, 在java层调用native方法时会出现java.lang.UnsatisfiedLinkError这个异常. 原因是就在第二行, 这里c和c++是有些区别的, 如果用c实现的话, 只需要#include <\jni.h>即可, 但是如果用c++实现, 那么必须要include你刚刚生成的.h文件, 而不是jni.h. 虽然编译可以通过,但是调用时你会发现报了java.lang.UnsatisfiedLinkError这个异常. 原因就是java层没找到对应的方法. 还有就是c和c++语法上的一些小区别, 但这些错误是可以在编译so期间发现的。
c代码:

#include <stdio.h>//#include "com_hx_testjni_TestJNI.h"#include <jni.h>JNIEXPORT jint JNICALL Java_com_hx_testjni_TestJNI_add(JNIEnv *env, jobject obj, jint x, jint y) {    return x + y;}

4.编写Android.mk文件

LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS)   LOCAL_MODULE    := TestJNI//LOCAL_SRC_FILES := com_hx_testjni_TestJNI.cppLOCAL_SRC_FILES := com_hx_testjni_TestJNI.c  include $(BUILD_SHARED_LIBRARY)

5.生成libTestJNI.so文件
6.java层loadLibrary调用native方法

static {    System.loadLibrary("TestJNI");}...TestJNI jni = new TestJNI();sum = jni.add(x, y);...

至此,Java调用C/C++编码流程介绍完毕。

C/C++调用Java

我们需要在JNI的C代码调用Java代码。实现原理:使用JNI提供的反射接口来反射得到Java方法,进行调用。
这里的例子是在上面例子的基础上扩展而成的,代码流程是Java通过Native方法sayHello调用到C++里面,再从C++反射调用Java中的signTest方法。
1.TestJNI.java中申明Native方法sayHello和写自己的方法signTest:

public class TestJNI {    public String name = "Test";        public int number = 100;      public native int add(int x, int y);     public native void sayHello();    public int signTest(int i,Date date,int[] arr){            MainActivity.showlog("Sign Test");        MainActivity.showlog("(Java)i="+i);             return 0;        }   }

2.编译生成TestJNI.class文件和com_hx_testjni_TestJNI.h头文件

/* DO NOT EDIT THIS FILE - it is machine generated */#include <jni.h>/* Header for class com_hx_testjni_TestJNI */#ifndef _Included_com_hx_testjni_TestJNI#define _Included_com_hx_testjni_TestJNI#ifdef __cplusplusextern "C" {#endif/* * Class:     com_hx_testjni_TestJNI * Method:    add * Signature: (II)I */JNIEXPORT jint JNICALL Java_com_hx_testjni_TestJNI_add  (JNIEnv *, jobject, jint, jint);/* * Class:     com_hx_testjni_TestJNI * Method:    sayHello * Signature: ()V */JNIEXPORT void JNICALL Java_com_hx_testjni_TestJNI_sayHello  (JNIEnv *, jobject);#ifdef __cplusplus}#endif#endif

3.c++实现com_hx_testjni_TestJNI.cpp文件

#include <stdio.h>#include "com_hx_testjni_TestJNI.h"#include <android/log.h>#define LOG_TAG "HuaXun"#define LOGI(fmt, args...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, fmt, ##args)#define LOGD(fmt, args...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, fmt, ##args)#define LOGE(fmt, args...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, fmt, ##args)JNIEXPORT jint JNICALL Java_com_hx_testjni_TestJNI_add(JNIEnv *env, jobject obj,        jint x, jint y) {    LOGI("(Native)x--->%d", x);    LOGI("(Native)y--->%d", y);    LOGI("(Native)sum--->%d", x + y);    return x + y;}JNIEXPORT void JNICALL Java_com_hx_testjni_TestJNI_sayHello(JNIEnv * env, jobject obj) {    LOGI("Hello Native Test !");    //因为sayHello不是静态函数,所以传进来的就是调用这个函数的对象    //否则就传入一个jclass对象表示native()方法所在的类    jclass native_clazz = env->GetObjectClass(obj);    //通过反射得到jmethodID    jmethodID methodID_func = env->GetMethodID(native_clazz,"signTest","(ILjava/util/Date;[I)I");    //调用Java方法signTest    env->CallIntMethod(obj, methodID_func, 1L, NULL, NULL);    //每次调用完后要删除local reference,释放内存    env->DeleteLocalRef(obj);    //通过反射得到jfieldID    jfieldID fieldID_name = env->GetFieldID(native_clazz,"name","Ljava/lang/String;");    jfieldID fieldID_num = env->GetFieldID(native_clazz,"number","I");    //得到name属性    jobject name = env->GetObjectField(obj, fieldID_name);    //得到number属性    jint number= env->GetIntField(obj,fieldID_num);//  LOGI("(Native)name--->%s", name);    LOGI("(Native)number--->%d", number);    //修改number属性的值    env->SetIntField(obj,fieldID_num,18880L);    number= env->GetIntField(obj,fieldID_num);    LOGI("(Native)number after modify--->%d", number);}

这里有一点值得我们看一下:

//通过反射得到jmethodIDjmethodID methodID_func = env->GetMethodID(native_clazz,"signTest","(ILjava/util/Date;[I)I");

需要提供三个参数,第一个是获取的jclass对象,第二个是Java文件中的函数名,第三个是函数签名。函数签名的格式可以见上一篇博客,这里我们可以通过javap获取函数签名。

javap -p -s [class文件]

这里写图片描述

4.编写Android.mk文件

LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS)  LOCAL_MODULE    := TestJNILOCAL_SRC_FILES := com_hx_testjni_TestJNI.cppLOCAL_LDLIBS    := -lm -llog include $(BUILD_SHARED_LIBRARY)

5.生成libTestJNI.so文件
6.java层loadLibrary调用native方法

static {    System.loadLibrary("TestJNI");}...TestJNI jni = new TestJNI();jni.sayHello();...

安装APK,运行看Log输出:
这里写图片描述

可以看到C++已经调用到Java的signTest方法,并将i值(i=1)传上来了。而且C++访问到了Java的name和number两个属性,并成功修改number属性值。

Demo下载地址

0 0