知识点整理
一.JNI原理
JNI的全称是Java Native Interface,通过JNI技术,我们可以方便地在Java程序中调用Native函数,也可以在Native程序中回调Java方法。 对于Android系统来说, JNI是连接Java世界与Native世界的桥梁。它非常基础而又重要,许多系统模块内部都运用了到了它。因此我们每一个从事Android系统开发的程序员都需要熟练掌握JNI。
1.1JNI原理:
Java语言的执行环境是Java虚拟机(JVM),JVM其实是主机环境中的一个进程,每个JVM虚拟机都在本地环境中有一个JavaVM结构体,该结构体在创建Java虚拟机时被返回,在JNI环境中创建JVM的函数为 JNI_CreateJavaVM 。
_JNI_IMPORT_OR_EXPORT_ jint JNICALL
JNI_CreateJavaVM(JavaVM **pvm, void **penv, void *args);
1.1.1 JavaVM
JavaVM是Java虚拟机在JNI层的代表,JNI全局仅仅有一个,可以跨线程使用,JavaVM中封装的函数指针主要是对JVM操作的接口。
JNI_OnLoad()函数相对应的有JNI_OnUnload()函数,当虚拟机释放的该C库的时候,则会调用JNI_OnUnload()函数来进行善后清除工作。一般动态注册会用到。
struct JNIInvokeInterface_;
struct JavaVM_;
#ifdef __cplusplus
typedef JavaVM_ JavaVM;
#else
typedef const struct JNIInvokeInterface_ *JavaVM;
#endif
struct JavaVM_ {
const struct JNIInvokeInterface_ *functions;
#ifdef __cplusplus
jint DestroyJavaVM() { //销毁JAVAVM
return functions->DestroyJavaVM(this);
}
jint AttachCurrentThread(void **penv, void *args) { //获取线程相关的JNIENV
return functions->AttachCurrentThread(this, penv, args);
}
jint DetachCurrentThread() { //释放线程
return functions->DetachCurrentThread(this);
}
jint GetEnv(void **penv, jint version) { // 获取Env
return functions->GetEnv(this, penv, version);
}
jint AttachCurrentThreadAsDaemon(void **penv, void *args) {
return functions->AttachCurrentThreadAsDaemon(this, penv, args);
}
#endif
};
_JNI_IMPORT_OR_EXPORT_ jint JNICALL
JNI_GetDefaultJavaVMInitArgs(void *args);
_JNI_IMPORT_OR_EXPORT_ jint JNICALL
JNI_CreateJavaVM(JavaVM **pvm, void **penv, void *args);
_JNI_IMPORT_OR_EXPORT_ jint JNICALL
JNI_GetCreatedJavaVMs(JavaVM **, jsize, jsize *);
/* Defined by native libraries. */
JNIEXPORT jint JNICALL
JNI_OnLoad(JavaVM *vm, void *reserved); //动态注册 加载JNI库,自动调用
JNIEXPORT void JNICALL
JNI_OnUnload(JavaVM *vm, void *reserved);//释放JNI库,自动调用
1.1.2 JNIEnv
JNIEnv是线程相关的,即在每一个线程中都有一个JNIEnv指针,每个JNIEnv都是线程专有的,其他线程不能使用本线程中的JNIEnv,即线程A不能调用线程B的JNIEnv。所以JNIEnv不能跨线程。
实际上代表了java环境,通过JNIEnv*指针,就可以对java端,进行操作,例如:创建java类中的对象,调用java中方法,获取java对象中的属性,等。
作用:
- 调用Java 函数:JNIEnv代表了Java执行环境,能够使用JNIEnv调用Java端的代码
- 操作Java代码:Java对象传入JNI层就是jobject对象,需要使用JNIEnv来操作这个Java对象
获取: 从JavaVM获得,这里面又分为C与C++
- C 中 JNIInvokeInterface:JNIInvokeInterface是C语言环境中的JavaVM结构体,调用 (AttachCurrentThread)(JavaVM, JNIEnv*, void) 方法,能够获得JNIEnv结构体
- C++中 _JavaVM:_JavaVM是C++中JavaVM结构体,调用jint AttachCurrentThread(JNIEnv** p_env, void* thr_args) 方法,能够获取JNIEnv结构体;
释放:
- C 中释放:调用JavaVM结构体JNIInvokeInterface中的(DetachCurrentThread)(JavaVM)方法,能够释放本线程的JNIEnv
- C++ 中释放:调用JavaVM结构体_JavaVM中的jint DetachCurrentThread(){ return functions->DetachCurrentThread(this); } 方法,就可以释放 本线程的JNIEnv
jni.h中定义:
struct JNINativeInterface_;
struct JNIEnv_;
#ifdef __cplusplus
typedef JNIEnv_ JNIEnv;
#else
typedef const struct JNINativeInterface_ *JNIEnv;
#endif
通用例子:
通过JavaVM对象 获取JNIEnv 对象,然后释放该线程
JavaVM *JavaVM;
static JNIEnv * getJNIEnv(bool *multi_thread) {
JNIEnv *sCallbackEnv;
if(JavaVM == NULL)
{
return NULL;
}
if(JavaVM->GetEnv(reinterpret_cast<void**>(&sCallbackEnv), JNI_VERSION_1_6) != JNI_OK) {
if(JavaVM->AttachCurrentThread(&sCallbackEnv, NULL) < 0) { //获取线程相关
*multi_thread = false;
return NULL;
}
*multi_thread = true;
}
else {
*multi_thread = false;
}
return sCallbackEnv;
}
------------------------------------------------------------------------------------------
bool multi_thread = true;
JNIEnv *sCallbackEnv = getJNIEnv(&multi_thread);
if(multi_thread) {
JavaVM->DetachCurrentThread(); //释放
}
注意:
C++:
void fun(JNIEnv *env) env->
C中:
void fun(JNIEnv *env) (*env)->
1.1.3 参数 jobject jclass
java层的代码调用c/c++代码,在JNI函数映射,每个jni的函数,函数参数存在。
jobject jclass 代表的是java层的对象或者类
1.如果native方法不是static的话,这个obj就表示native方法类的实例
2.如果natvie方法是static的话,这个obj就代表这个native方法的类class对象的实例,(static方法不需要类的实例,所以就代表这个类的class对象)
例如:
java层:
private native static void classInitNative() //静态对应JNI层,参数为jclass clazz
private native void initNative(); //非静态对应JNI层,参数 jobject obj
JNI层:
static void classInitNative(JNIEnv* env, jclass clazz)
static bool initNative(JNIEnv* env, jobject obj)
1.1.3 jfieldId, jmethodID
jfieldId, jmethodID表示的是java层对象的属性和方法
通过jfieldId, jmethodID实现c/c++ 回调java代码
1.1.4 Java和C/C++ 中的基本类型的映射关系:
JNI是接口语言,因而,会有一个中间的转型过程,在这个过程中,有一个非常重要的也是非常关键的类型对接方
式,这个方式便是,数据类型的转变,下表给出了相关的对于的数据格式。
java类型 | jni类型 | 描述 | 签名 |
---|---|---|---|
boolean | jboolean | unsigned 8 bits | Z |
byte | jbyte | signed 8 bits | B |
char | jchar | unsigned 16 bits | C |
short | jshort | signed 16 bits | S |
int | jint | signed 32 bits | I |
long | jlong | signed 64 bitsJ | |
float | jfloat | signed 32bit | F |
double | jdouble | signed 64 bits | D |
Class | jclass | class类对象 | |
String | jstring | 字符串对象 | L/java/lang/String |
Object | jobject | 任何java对象 | |
byte[] | jbyteArray | byte数组 | [B |
void | V |
这个表都是JNI开发中 java 和 JNI之间数据的适配。
注意:
-
类都是 以L开头, 包名是以“/”分割, “;” 结束
例如 String str JNI:Ljava/lang/String; -
数组是以“]” 开始
例如long array[] JNI:[J -
基本类型之间没有分割
例如:void fun(int a,double b,byte[] c) JNI: (ID[B)V
1.1.5 java层签名生成方式:
javap -s -p xxx.class
-p: 所有的类和成员函数
2.JNI 动态注册(常用)与静态注册
2.1 静态注册(一般很少用 )
步骤:
- 编写java类,带有包名com.example.JNI里面 JniTest.java ,在java文件中声明Native方法(即需要调用的本地方法)
- 在com.example.JNI里面命令行下输入 javac -d . JniTest.java 生成包名下的JniTest.class文件
- 通过 javah -classpath . -jni com.example.JNI.JniTest(全类名)生成 xxx_JniTest.h 头文件
- 编写xxx_JniTest.c 源文件,并拷贝xxx_JniTest.h 下的函数,并实现这些函数,且在其中添加jni.h头文件;
- 编写 cmakelist.txt 文件,编译生成动态/静态链接库
注意:java文件要是带有包名,要在包名外面执行javac 或者 javah
全类名:指的是在包名的外部执行 javah -classpath . -jni com.example.iauto.JniTest
通过javah xx生成c/c++接口,命名规范:java_包名_类名_函数名
JNIExport jstring JNICALL Java_com_example_hellojni_MainActivity_stringFromJNI( JNIEnv* env,jobject thiz )
jstring 返回值类型
Java_com_example_hellojni 包名
MainActivity 类名
stringFromJNI 方法名
Java load 和 loadlibrary方法的区别 :
1.相同点:
两个方法都是用来装载dll文件 或者 so ,不论是JNI库文件还是非JNI库文件。
本地方法在被调用时都需要通过这两发方法之一将其加载至内存。
2 不同点
a. System.load(String filename) ,参数为dll文件的绝对路径,可以是任意路径。
System.load("D:\\java\\Test.dll");
b. System.loadLibrary(String libname) 参数为dll文件名,不包含库文件的扩展名。
System. loadLibrary ("Test");
注意:System.loadLibrary(String libname)和java.library.path变量对应的路经相关,此方法加载的文件必须是在java.library.path这一jvm变量所指向的路径中。
可以通过如下方法来获得该变量的值:
System.getProperty("java.library.path")
2.2 动态注册(常用)
举例:
java层:
java层代码非常简单只有两点:
(1)加载JNI库, 通常加载JNI库的操作放在一段static代码中,调用System.loadLibrary方法即可
public class JNITestController {
static {
System.loadLibrary("jnitest_jni"); //在java环境变量中寻找
}
private static native void InitNative(); //static 对应的方法参数为jclass
private native void fun(int num1,int num2); //非static 对应方法参数为jobject
}
JNI层:
1.实现 JNI_OnLoad 方法
2.FindClass 方法获取java对象
3.调用RegisterNatives方法,传入java对象,以及JNINative数组,以及注册数目完成。
static void InitNative(JNIEnv* env, jclass clazz) //jclass 对应java为静态方法
{
env->GetJavaVM(&javaVM);
std::cout<<"InitNative"<<std::endl;
}
static void fun(JNIEnv* env, jobject object,jint num1,jint num2) //jobject object 非静态方法
{
std::cout<<"fun:"<<num1 <<" "<< num2 <<std::endl;
}
static JNINativeMethod methods[] = {
{"InitNative","()V",(void *)InitNative}, //注意 () 之间是没有空格的
{"fun","(II)V",(void *)fun}
};
jint registerJNI(JNIEnv *env,jclass clazz)
{
return env->RegisterNatives(clazz, methods,
sizeof(methods)/sizeof(methods[0]));
}
}
//步骤:
//1.检查JNI 版本
//2.注册
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *jvm, void *reserved)
{
JNIEnv *env;
jint result = -1;
jclass clazz;
if(jvm->GetEnv((void**)&env,JNI_VERSION_1_6))
{
std::cout<<"JNI version mismatch error"<<std::endl;
return JNI_ERR;
}
clazz = env->FindClass("JNIControl"); //java 中的类
result = nutshell::registerJNI(env,clazz);
if(result<0) {
std::cout<<"JNI registerJNI error"<<std::endl;
return JNI_ERR;
}
return JNI_VERSION_1_6;
}
3 JNI常用方法
一般使用 JNIEnv里面的方法 一定要注意 C: (*env)-> C++:env->
- 基本类型
从java层到jni,jni到java 不变,可以直接用。 - string类型:
从java到jni,jstring 需要转成const char *,需要释放
从jni到java,const char * 需要转成jstring,需要释放 - 对象:
一般用parcel
3.1 获取JAVAM
通过JEnv获取
JAVAVM m_JavaVM;
env->GetJavaVM(&m_JavaVM);
3.2获取JNIENV版本信息
两种方式:
1.通过javavm获取
jvm->GetEnv((void **)&env, JNI_VERSION_1_6)
2.通过线程获取
jvm->AttachCurrentThread((void **)&env,NULL)
m_JavaVM->DetachCurrentThread()
3.3 获取JNI版本信息
jint GetVersion(JNIEnv *env);
3.4 注册java中的nativie方法
JNI_OnLoad里面获取java层对象class,在进行注册
java层BtService 类中native方法
public static native void NativeInit();
public native void JavaToNativeTest(int taskID);
public native void cleanNative();
jni层
static const JNINativeMethod methods[] = {
{"NativeInit","()V",(void *)NativeInit},
{"JavaToNativeTest","(I)V",(void *)JavaToNativeTest},
{"cleanNative","()V",(void *)cleanNative}
}
jclass Clazz= env->FindClass("com/iauto/service/BtService");
int ret = env->RegisterNatives(Clazz,methods,sizeof(methods)/sizeof(methods[0]));