C/C++ 互相通过的接口,本地的C/C++代码可以调用Java代码
JNI是本地编程接口,NDK快速开发C(C++)动态库,将so和Java应用一起打包成apk
将复杂的逻辑和算法通过本地代码(C/C++)
反汇编.so动态库分析程序的逻辑要复杂
设备提供C接口,而Java不能直接与C进行交互,先在Java层定义发送短信等 native方法,用javah命令将定义Java native接口的class字节码文件生成.h文件,最后用设备的C接口实现java的native方法,编译成 .dll或 .so动态库
打开一个网页,播放视频或打开窗口,C++实现麻烦,用Android应用层提供的API变得容易。Cocos2d-x封装
应用程序需要调用硬件的特定功能,只能通过C/C++封装的JNI接口来提供上层应用
厂商封装好JNI接口,了解JNI与Java通讯原理
C和C++ 复用以前用C/C++写的大量代码
JNI程序受系统环境影响,C/C++编译的模块或代码依赖系统提供的库函数和本地库,不同的操作系统,有自己的本地库和CPU指令集,各个平台的C/C++规范和标准库函数实现方式也有所区别
2.Java源代码编程成class字节码文件
3.用javah -jni 生成 .h头文件 -jni表示将class中用native声明的函数
4.用本地代码实现 .h 文件中的函数
5.本地代码编译成动态库 Windows:*.dll Linux:*.so Mac OSx:*.jnilib
6.拷贝动态库至 java.library.path 本地库搜索目录下,运行Java程序
HelloWorld.java
public class HelloWord{
public calss HelloWorld{
public static native String sayHello(String name);
public static void main(String[] args){
String tet = sayHello("yang");
System.out.println(text);
}
}
staic{
System.loadLibrary("HelloWorld");
}
}
用本地代码实现 .h 文件中的函数
将C/C++代码编译成本地动态库文件,libHelloWorld.so
一般在类的静态代码块中加载动态库最合适
方式1:只需要动态库名字,不需要lib前缀,也不要.so, .dll, .jnilib后缀
System.loadLibrary("HelloWorld"); java会去java.library.path系统指定目录
方式2:指定动态库的绝对路径,需要前缀后缀
System.load("/User/yangxin/Desktop/libHelloWorld.jnilib");
javah -classpath ./bin//classpath 类搜索路径
javac src/com/HelloWorld.java -d ./bin
-d 表示编译后的class文件放在指定目录下,放在src同级的bin目录
没有将动态库拷贝到本地搜索目录下,执行 java 命令,添加系统属性 java.library.path指定动态库目录 java -Djava.library.path=/Users/yangxin/Desktop
指定系统属性,Linux环境下可设置 LD_LIBRARY_PATH 环境变量指定库的搜索目录
gcc -I$JAVA_HOME/include -I$JAVA_HOME/include/linux -fPIC -shared
-I 包含编译JNI必要的头文件
-fPIC 编译成位置无关独立代码
-shared 编译成到动态库
-o 编译后动态库生成的路径和文件名
JNIEXPORT jstring JNICALL Java_com_study_jnilearn_HelloWorld_sayHello
(JNIEnv *, jclass, jstring)
{
const char *c_str = NULL;
char buff[128] = {0};
c_str = (*env)->GetStringUTFChars(env, j_str, NULL);
if(c_str == NULL){
printf();
return NULL;
}
(*env)->ReleaseStringUTFChars(env, j_str, c_str);
printf("Java Str:%s\n", c_str);
sprintf(buff, "hello %s", c_str);
return (*env)->NewStringUTF(env, buff);
}
JVM查找native方法
调用System.loadLibrary接口 加载实现了native方法的动态库才能正常访问
JVM查找native方法
按照JNI规范的命名法则
调用JNI提供的 RegisterNatives 函数,注册到JVM
编译动态库时,用-I包含了两个头文件目录,一个jni.h头文件,一个跨平台头文件目录,用于定义与平台相关的宏,其中用于标识 JNIEXPORT JNICALL
在Windows中编译dll动态库规定,如果动态库中函数要被外部调用,在函数声明中添加 __declspec(dllexport)标识,在Linux/Unix这两个宏可以省略。由于各自的编译器所产生的可执行文件格式不一样。
javah工具生成函数原型的头文件命名规则: Java_类全路径_方法名
JNIEXPORT jstring JNICALL Java_com_study_jnilearn_HelloWorld_sayHello(JNIEnv *, jclass, jstring);
第一个参数:JNIEnv * 定义任意native函数的第一个参数(调用JNI的RegisterNatives函数注册的函数),指向JVM函数表的指针,函数表每一个入口指向一个JNI函数
第二个参数:Java中调用 native 方法的实例或Class对象,如果这个native方法是实例方法,则参数是 jobject,如果是静态方法,则是 jclass
第三个参数:Java对应JNI中的数据类型,Java中String类型对应JNI中的jstring
熟悉命名规则后,不通过javah生成相应的java native方法的函数原型,只需要按照命名规则编写相应的函数原型和实现。
com.study.jni.Utils 计算加法native的add
public native int add(int, int);
JNIEXPORT jint JNICALL Java_com_study_jni_Utils_add(JNIEnv *, jobject, jint,jint);
Java层函数名如果有一个"_",转换成JNI后变成了"_l"
JNIEXPORT void JNICALL Java_android_media_MediaScanner_native_linit
JNIEXPORT void JNICALL Java_android_media_MedaiScanner_processFile