JNI(Java Native Interface,Java本地接口),
用于打通Java层与Native(C/C++
)层。
这不是Android系统所独有的,而是Java所有。
众所周知,Java语言是跨平台的语言,
而这跨平台的背后都是依靠Java虚拟机,
虚拟机采用C/C++
编写,适配各个系统,
通过JNI为上层Java提供各种服务,保证跨平台性。
这样就产生了一个问题,
Java世界的代码要怎么使用Native世界的代码呢,
这就需要一个桥梁来将它们连接在一起,
而JNI
就是这个桥梁。
JNI方法注册分为静态注册和动态注册,其中静态注册多用于NDK开发,而动态注册多用于Framework开发。
Java世界对应的是.java类文件,也就是我们应用开发中直接调用的类。JNI层对应的是libxxx.so或者libxxx.a,so是动态库,a是静态库。这些库完成了实际的调用的功能。
静态库 和 动态库 区别
静态库
-
全称是静态链接库(Static Library),后缀是 .a,例如 libmedia.a
-
调用静态库的程序在编译时会将静态库全部编译到目标代码中,运行环境中不再需要静态库,并且静态库文件体积较大
-
调用静态库时,如果对静态库中的函数内容进行改变,不仅需要重新编译静态库,还需要对调用静态库的程序重新编译,将静态库编译到目标代码中。
动态库
-
全称是动态链接库(Shared Library),后缀是 .so,例如 libmedia.so
-
调用动态库的程序在编译时不能将动态库编译到目标代码中,程序执行到相关函数时才会链接该动态库对应的函数,所以运行环境中必须提供动态库,并且动态库文件体积较小
-
调用动态库时,如果对动态库中的函数内容进行改变,只需要重新编译动态库,不需要对调用动态库的程序重新编译,即不需要干预目标代码,直接用新的动态库替换掉旧的动态库即可
静态注册
java层:
1 | package com.example; |
native层:
头文件 com_example_MainActivity.h
1 |
|
方法被声明格式为Java_包名_类名_方法名,其中JNIEnv * 是一个指向全部JNI方法的指针,该指针只在创建它的线程有效,不能跨线程传递。 jclass是JNI的数据类型,对应Java的java.lang.Class实例。jobject同样也是JNI的数据类型,对应于Java的Object。
源文件 com_example_MainActivity.cpp
1 |
|
其中可以使用Java命令快速查看
javac xxx.java 用于生成java字节码文件
javah -classpath xxx.类名 用于生成C/C++头文件
javap -s xxx.类名 用于查看java方法名和对应的方法签名
动态注册
JNI中有一种结构用来记录Java的Native方法和JNI方法的关联关系,它就是JNINativeMethod,它在jni.h中被定义:
1 | typedef struct { |
重写注册入口函数
1 | static void native_init(JNIEnv* env,jclass clazz){ |
类名路径不要用'.'而是'/' 。例如 com/example/MainActivity,否则虚拟机找不到会报错
重写JNI_OnLoad
so加载时会先调用JNI_OnLoad函数,重写该函数,在里面实现动态注册JNI方法。
1 | JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) |
如果 java 方法是 static,则代表的是 class 对象
总结
静态注册
编写不方便,JNI 方法名字必须遵循规则且名字很长;
编写过程步骤多,不方便;
程序运行效率低,因为初次调用native函数时需要根据根据函数名在JNI层中搜索对应的本地函数,然后建立对应关系,这个过程比较耗时;
动态注册
流程更加清晰可控;
编写方便,可动态配置;
效率更高
JNI开发上手难度比较大一点,最好是学过C/C++,有一定基础的,否则你会遇到很多坑。
没有基础的同学先去学习下C/C++再来上手JNI开发。