此开发环境:eclipse,android-ndk-r10,附有ndk资源,亲测OK
JNI实现步骤:
1、编写带有native声明的方法的java类。
2、使用javah+java类全类名生成.h文件。
3、使用C/C++实现本地方法。
4、将C/C++编写的文件生成动态连接库。
5、编写Java调用代码、进行测试。
下面我们按照上述步骤进行开发:
1、编写带有native声明的方法的java类。
创建Android Project:TestJNI,新建带有native声明的方法的TestJNI类,添加加载c库的代码,其中“testJNI”为so的文件名
我的代码如下:
注意System.loadLibrary("TestJNI");调用的时候,eclipse生成的时libTestJNI,调用的名字却是TestJNI,真他妈坑。
2、使用javah+java全类名生成.h文件。
我们都知道在Eclipse中新建类后,Eclipse会帮助我们在项目下自动建立去对应的class文件,位于项目目录下的\bin\classes中。头文件可以根据.class文件通过javah编译生成。
进入到工程下bin\classes目录下,执行cmd,输入:javah com.example.testjni.TestJNI。(一般这个命令会提示找不到类文件,则可输入如下命令,把文件路径写进来:javah E:\Hello\TestJNI\bin\classes com.example.testjni.TestJNI,这样就可以了)
就会在该目录下生成头文件即com_example_testjni_TestJNI.h,头文件中定义了与上层的访问接口Java_com_example_testjni_TestJNI_getString以及Java_com_example_testjni_TestJNI_getInt,方法名是按照”Java_包名_类名_方法名“来命名的,如下图:
3、使用C/C++实现本地方法。
3.1、处理JNI
右击Project->Android Tools->Add Native Support,这里注意一点,如果没有添加NDK路径的话,就会无法到下面这一步,所以如果弹出的框出现这种提示:
我们进行一下添加NDK路径操作:Window->Preferences->Android->NDK,选择NDK所在的目录,点击OK即可。
添加好了NDK的路径,在此进行我们之前的操作,在弹出的框里输入so的文件名,也就是在TestJNI类中写的so文件名,注意这里要统一!切记!
完成后工程自动生成jni文件夹,jni文件夹下自动生成了testJNI.cpp以及Android.mk,如下图:
3.3、将.h文件复制到jni目录下
此时可能会出现如下报错:
- Type '*****' could not be resolved
Project Properties -> C/C++ General -> Path and Symbols选择include标签
Add ->$Android_NDK_HOME/platforms/android-14/arch-arm/usr/include即可。
注意:这里Android_NDK_HOME是环境变量的名字,我的用这个名字是错的,我是直接写的jni.h文件的路径;
3.3、编写C/C++(这里为C++代码)
- #include <jni.h>
- extern "C"
- JNIEXPORT jstring JNICALL Java_com_example_testjni_TestJNI_getString
- (JNIEnv *env, jobject obj){
- return env->NewStringUTF("HelloJNI!");
- }
- JNIEXPORT jint JNICALL Java_com_example_testjni_TestJNI_getInt
- (JNIEnv *env, jobject obj){
- return 1;
- }
JNIEXPORT、JNICALL 、JNIEnv和jobject都是JNI标准中所定义的类型或者宏,它们的含义如下:
JNIEXPORT和JNICALL:他们是JNI中所定义的宏,可以在jni.h这个头文件中查询到;
JNIEnv*:表示一个指向JNI环境的指针,可以通过它来访问JNI提供的接口方法。
jobject:表示Java对象中的this;
注意:JNI调用C或者C++存在区别:
1、JNI是由C语言定义接口的,JNI通过函数名找函数入口,执行函数里的内容。这和函数用什么语言生成的并没有关系。只要保证函数名称符合JNI的协议。而使用C++要注意的是C++默认生成的函数名称和你写在源文件中的名称并不相同,因为C++要处理函数重载,会在函数名称中加上参数信息,这称为name mangling。解决方法是定义函数时在前面加上extern "C"修饰,告诉编译器这函数要被C调用(当然,其实是JNI)。本质没有什么区别,一个是JNI调C,一个是JNI调C,C再掉C++,这样理解就简单了~
2、在C的定义中,env是一个两级指针,而在C++的定义中,env是个一级指针
C形式需要对env指针进行双重deferencing,而且须将env作为第一个参数传给jni函数,举个简单的例子:
对于XXX.c:JNI 函数调用由“(*env)->”作前缀,目的是为了取出函数指针所引用的值。
- return (*env)->NewStringUTF(env,"HelloJNI!");
对于XXX.cpp:JNIEnv 类拥有处理函数指针查找的内联成员函数。
- return env->NewStringUTF("HelloJNI!");
可以发现,C或者C++的实现很类似,但是它们对env的操作方式有所不同,因此使用C和C++来实现同一个JNI方法,它们的区别主要集中在对env的操作上,其他都是类似的。
4、将C/C++编写的文件生成动态连接库
4.1、编写Android.mk文件以及Application.mkAndroid.mk 文件是Android 的 makefile文件,下面逐句解释
LOCAL_PATH - 编译时的目录
$(call 目录,目录….) 目录引入操作符
如该目录下有个文件夹名称 src,则可以这样写 $(call src),那么就会得到 src 目录的完整路径
include $(CLEAR_VARS) -清除之前的一些系统变量
LOCAL_MODULE - 编译生成的目标对象即so的文件名
LOCAL_SRC_FILES - 编译的源文件,C/C++文件
LOCAL_C_INCLUDES - 需要包含的头文件目录
LOCAL_SHARED_LIBRARIES - 链接时需要的外部库
LOCAL_PRELINK_MODULE - 是否需要prelink处理
include$(BUILD_SHARED_LIBRARY) - 指明要编译成动态库
如果想在多个平台下生成so文件,还需要添加Application.mk文件,如果没有该文件,则只会生成armeabi目录下的so文件。
在jni目录下新建Application.mk文件,该文件内容很简单:
- APP_ABI := all
4.2、使用NDK编译生成so库
进入到jni目录下,执行cmd,输入:
- ndk-build
刷新Project,在libs目录下,生成多平台下的so文件,如下图:
5、编写Java调用代码、进行测试。
- TestJNI testJNI=new TestJNI();
- Log.e("TAG", testJNI.getString());
在编译build生成so文件时遇到以下错误:
Multiple markers at this line
- extraneous closing brace ('}')- non-ASCII characters are not allowed outside of literals and
identifiers
- source file is not valid UTF-8
- null character ignored [-Wnull-character]
- expected unqualified-id
- unknown type name 'b'
搞了两天却是ndk的问题...