在上一篇里记录了一个个人认为较简单的jni例子,接下来我写一下jni流程,个人观点,也是参考网上资料后写的一些观点吧,如有错误,可指正,谢谢。
以上一篇记录里的代码为举例,在java里
static {
System.load("/system/jni/libjnicall.so");
}
就是加载so库,这个是制定路径的加载,另外一种方法是System.loadLibrary,各位可自己百度差别,我就不细说了。当运行这里加载时,就会执行 JNI_OnLoad 函数(搜索到网上这样写)。这就是为什么一开始编译库文件跑起来会出错的原因。网上说即使没有这个 JNI_OnLoad 函数,也会返回最老的JNI版本(1.1版),但测试结果就是会运行报错,所以加上这个函数会返回1.4版(个人猜测)。通过返回jni版本,就能正确调用这个库文件了。定义jni里C函数:
jint Java_com_example_jnicall_MainActivity_add(JNIEnv* env, jobject this, jint add1, jint add2)
返回的类型是jint,就是对应java里的int类型,函数名会比较长,因为要固定包名,前面Java的J一定要大写(网上说的),后面包名用下横杠替换 . ,由于我是直接在Activity里加载库和定义native接口,所以包名后跟MainActivity,最后就是native函数名了。这些都是以下横线隔开,具体原因可百度,好像说是系统对 . 有特殊用处,因此用下横线替换。参数前两个:JNIEnv* env, jobject this 是固定的可不理会,具体功能也可百度自己查。然后跟两个jint型参数add1和add2就是在java里所调用的方法:
add(1,2);
所传的1和2。
这次尝试把c文件改成cpp文件来编译,个人认为是可以使用C++语言来写jni了。
现在开始,首先是把jni_call.c文件名改为jni_call.cpp,tools.c文件名改为tools.cpp。修改Android.mk文件如下:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := libtools
LOCAL_MODULE_TAGS := optional
LOCAL_SRC_FILES := tools.cpp
include $(BUILD_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := libjnicall
LOCAL_MODULE_TAGS := optional
LOCAL_SRC_FILES := jni_call.cpp
LOCAL_STATIC_LIBRARIES := libtools
LOCAL_MODULE_PATH := $(LOCAL_PATH)/out/lib
include $(BUILD_SHARED_LIBRARY)
其中,LOCAL_SRC_FILES := tools.cpp是把编译的文件名从tools.c改为tools.cpp。LOCAL_SRC_FILES := jni_call.cpp是从jni_call.c改为jni_call.cpp。
添加了一个LOCAL_MODULE_PATH := $(LOCAL_PATH)/out/lib ,是让编译出来的so库文件放到当前目录的out/lib/文件下,这样就不用进入system目录下,看个人习惯而已。
jni_call.cpp文件修改如下
#include "tools.h"
#include <jni.h>
#include "JNIHelp.h"
jint Java_com_example_jnicall_MainActivity_add(JNIEnv* env, jobject thiz, jint add1, jint add2){
return add(add1, add2);
}
#ifdef __cplusplus
extern "C" {
#endif
jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
JNIEnv *env;
if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) {
return -1;
}
return JNI_VERSION_1_4;
}
#ifdef __cplusplus
} // extern "C"
#endif
要把jint Java_com_example_jnicall_MainActivity_add(JNIEnv* env, jobject thiz, jint add1, jint add2) 当中的第二个参数 jobject this 改为 jobject thiz(可对照以前的代码)
因为在C++里面,this有特殊用处,所以以后看网上资料,可从这里做判断是使用C语言还是C++(个人观点)。
编译目录:mmm packages/apps/jni_test/
会报错:packages/apps/jni_test/jni_call.cpp:16:11: error: base operand of '->' has non-pointer type 'JavaVM {aka _JavaVM}'
这是因为改成了C++语言,传递的参数会有变化。
错误出现在这一句:
(*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK
各位可自己百度GetEnv的C和C++调用上是有区别的。
我个人认为,C语言使用 (*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4)
C++使用 vm->GetEnv((void**) &env, JNI_VERSION_1_4)
要注意,GetEnv的第一个参数也是不一样的,C++是没有第一个参数vm的。
还要用extern "C"把Java_com_example_jnicall_MainActivity_add函数也括进去。
jni_call.cpp文件修改如下:
#include "tools.h"
#include <jni.h>
#include "JNIHelp.h"
#ifdef __cplusplus
extern "C" {
#endif
jint Java_com_example_jnicall_MainActivity_add(JNIEnv* env, jobject thiz, jint add1, jint add2){
return add(add1, add2);
}
jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
JNIEnv *env;
if ( (vm->GetEnv( (void**) &env, JNI_VERSION_1_4)) != JNI_OK) {
return -1;
}
return JNI_VERSION_1_4;
}
#ifdef __cplusplus
} // extern "C"
#endif
这时编译通过了:Install: packages/apps/jni_test/out/lib/libjnicall.so
把libjnicall.so重新push进/system/jni/ 里面(如果涉及到权限问题,无法push进去,可以自己修改路径地址,只要在java的System.load方法里参数地址对应上就行)。
运行com.example.jnicall apk,可以正常运行,就表明用C++方法成功了。
尝试一下也可以如下修改:
#include "tools.h"
#include <jni.h>
#include "JNIHelp.h"
#ifdef __cplusplus
extern "C" {
#endif
jint Java_com_example_jnicall_MainActivity_add(JNIEnv* env, jobject thiz, jint add1, jint add2){
return add(add1, add2);
}
#ifdef __cplusplus
} // extern "C"
#endif
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
JNIEnv *env;
if ( (vm->GetEnv( (void**) &env, JNI_VERSION_1_4)) != JNI_OK) {
return -1;
}
return JNI_VERSION_1_4;
}
在函数 JNI_OnLoad 前加 JNIEXPORT 这个关键字,编译也能通过,而 Java_com_example_jnicall_MainActivity_add 函数仍然要用 extern "C" 括起来。
JNIEXPORT 的功能各位可自己百度,这里使用这个关键字是为了下次改用另外一种 jni 方法注册而准备的。
工程代码:http://download.csdn.net/detail/u013820413/6996101