在上一篇我们讲解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 __cplusplus
extern "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.cpp
LOCAL_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 __cplusplus
extern "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);
}
这里有一点值得我们看一下:
//通过反射得到jmethodID
jmethodID 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 := TestJNI
LOCAL_SRC_FILES := com_hx_testjni_TestJNI.cpp
LOCAL_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属性值。