JNI的概念
JNI的全称是Java Native Interface。他提供了一种让运行在JVM中的Java代码与C/C++代码交互的方式。应用层和JVM厂商都遵从JNI的规范,这样应用层只需要写一套调用Native层的代码,不需要修改代码兼容不同的JVM实现或版本。Java代码通常是用UTF-16编码的,C/C++的通常采用UTF-8,这种编码方式的转换JNI已经为我们实现了。
JNI的用法
普通java调用
- 在Java类已中声明native接口函数,例如:在Hello.java中添加native函数声明
public native static void helloNative();
- 用 javac 命令将.java源文件编译成.class字节码文件
- 用 javah 命令,根据class字节码文件生成.h头文件
- 创建C/C++文件,实现.h文件中的接口
- 用gcc命令将C/C++文件编译成.so文件
- 在Hello.java中调用
System.loadLibrary("name")
加载动态库文件
Android调用
由于使用javah命令得到的.h文件里函数的规则是固定的,即:Java_类全路径_方法名,因此我们可以不通过javah命令生成头文件。
- 在AndroidStudio里下载配置好NDK
- 创建一个Android项目,在需要调用的native方法的类中声明所需的native方法。
- 在外部(非Android项目目录)新建一个jni目录,新建test.h、test.cpp、Android.mk、Application.mk目录。其中test.h就是按照javah生成的头文件形式创建的。例如:
//test.h
#ifndef __TEST__
#define __TEST__
#include <jni.h>
#ifdef __cplusplus
extern "C" {
#endif
JNIEXPORT void JNICALL Java_com_hl_TestHello_helloJNI(JNIEnv *env, jobject thiz);
#ifdef __cplusplus
}
#endif
#endif
- 在test.cpp中include test.h文件,实现native接口。
- 在Android.mk中配置好编译项
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := test-jni
LOCAL_SRC_FILES := test.cpp
LOCAL_LDLIBS := -L$(SYSROOT)/usr/lib -llog
#LOCAL_LDFLAGS += -ljnigraphics
include $(BUILD_SHARED_LIBRARY)
- 在Application.mk中配置编译的目标平台
APP_ABI := all
APP_STL :=c++_shared
- 在从终端进入jni目录下,执行
ndk-build
命令。编译成功后,相应的SO文件在与jni同级目录下的libs文件夹下面。 - 将SO文件拷贝到安卓项目中main/jniLibs下
- 在调用native方法的Java类中,加载so库:
System.loadLibrary("test-jni");
JNI调试
AndroidStudio为开发者提供了一套调试C/C++的工具。这套工具非常强大,能让我们修改完native层的代码之后,立刻编译成SO运行起来。官方教程https://developer.android.com/studio/debug/?hl=zh-cn。
JNI使用注意
-
什么时候要主动调用Release方法释放资源?
答:释放的资源主要包括引用和变量。
引用资源释放:JNI 规范中定义了三种引用:局部引用(Local Reference)、全局引用(Global Reference)、弱全局引用(Weak Global Reference)。局部引用:通过 NewLocalRef 和各种 JNI 接口创建(FindClass、NewObject、GetObjectClass和NewCharArray等)。会阻止 GC 回收所引用的对象,不在本地函数中跨函数使用,不能跨线前使用。函数返回后局部引用所引用的对象会被JVM 自动释放,或调用 DeleteLocalRef 释放。全局引用:调用 NewGlobalRef 基于局部引用创建,会阻 GC 回收所引用的对象。可以跨方法、跨线程使用。JVM 不会自动释放,必须调用 DeleteGlobalRef 手动释放。弱全局引用:调用 NewWeakGlobalRef 基于局部引用或全局引用创建,不会阻止 GC 回收所引用的对象,可以跨方法、跨线程使用。引用不会自动释放,在 JVM 认为应该回收它的时候(比如内存紧张的时候)进行回收而被释放。或调用DeleteWeakGlobalRef 手动释放。
变量释放:(1)通过(*env)->GetStringUTFChars获取的,要通过(*env)->ReleaseStringUTFChars释放。
(2)通过调用GetIntArrayElements等获取数组的方法的,需要调用对应的释放函数释放资源。 -
什么时候需要注意检测处理异常?
答:使用JNIEnv调用其函数,比如FindClass、GetFiledID等方法时,都会有可以返回为NULL,导致接下来的程序出现异常,因此在这些地方我们都需要进行异常检测处理。
参考文章
Oracle JNI Guide
使用 Java Native Interface 的最佳实践
Google AndroidDeveloper JNI Tips