JNI开发,可以实现Java与c/c++互相调用。
1.NDK开发中的静态库、动态库就是Linux中的概念
静态库:所有函数库编进目标代码,如果静态函数库改变类,则整个应用程序需要重新编译,名称对应libxxx.a
动态库:动态库不会编入目标代码,所以动态库改变不影响应用程序,名称对应libxxx.so
2.JNI注册方式有2种:静态注册、动态注册
静态注册:流程繁琐,c源文件中函数名要与java native接口一一对应(下文都是此注册方式);
动态注册:在c源文件中使用JNINativeMethod数组来声明java native与c中函数对应关系,还需要写一个JNI_OnLoad函数(android aosp源码中大量使用此注册方式)
1. Java call C
- 在java文件中声明native接口;
- 生成头文件(javac -h 命令);
使用此命令生成头文件 javac -h . JNI.java
- 拷贝.h文件到jni代码目录,并实现.c源文件;
- 配置CMakeLists.txt编译生成动态库(.so)/静态库(.a)。
demo工程如下:
- JavaCallCJNI.java内定义native接口;
- src/main/jni目录下进行c代码实现
CMakeList.txt内容如下:
cmake_minimum_required(VERSION 3.4.1)
add_library(
# 生成的lib名称
java_call_c
# 动态库
SHARED
# 源文件
src/main/jni/java_call_c.c)
find_library(
log-lib
log )
target_link_libraries(
java_call_c
${log-lib})
build.gradle文件中,在android节点下配置cmake内容。
externalNativeBuild{
cmake{
path "CMakeLists.txt"
}
}
so文件生成目录:build/intermediates/cmake
2. C call Java
- 在java文件中声明native接口;
- 生成头文件(javac -h 命令);
- 拷贝.h文件, 实现c源文件(反射调用java方法,javap -s命令看方法签名);
- 配置CMakeLists.txt编译生成动态库(.so)/静态库(.a)。
C调用java代码,工程结构与第1节基本一样,不同到是在c中用反射的方式调用java方法,CCallJavaJNI.java代码示例。
public class CCallJavaJNI {
/**
* java调用callbackAdd, c在此方法内部反射调用java的 add方法
*/
public native void callbackAdd();
public native void callbackGetMessage();
public int add(int x, int y) {
System.out.println("Invoke add: x= " + x + ", y= " + y);
return x + y;
}
public void getMessage(String s) {
System.out.println("Invoke getMessage: s= " + s);
}
}
在c_call_java.c中,用反射的方式调用java中的add方法,方法签名用javap -s命令查看。
JNIEXPORT void JNICALL Java_com_example_ccalljava_CCallJavaJNI_callbackAdd
(JNIEnv *env, jobject jobj) {
// 1.得到字节码
// jclass (*FindClass)(JNIEnv*, const char*);
jclass jclazz = (*env)->FindClass(env, "com/example/ccalljava/CCallJavaJNI");
// 2.获取方法
// jmethodID (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);
jmethodID jmid = (*env)->GetMethodID(env, jclazz, "add", "(II)I");
// 3.对象实例化
// jobject (*AllocObject)(JNIEnv*, jclass);
jobject jo = (*env)->AllocObject(env, jclazz);
// 4.调用方法
// jint (*CallIntMethod)(JNIEnv*, jobject, jmethodID, ...);
jint value = (*env)->CallIntMethod(env, jo, jmid, 10, 20);
};
3. 总结
3.1 生成.h文件
假如在JNI.java中定义了native方法,需要根据此.java文件生成对应的.h头文件,然后实现.c文件。
进入JNI.java文件目录,执行如下指令。(早期JDK版本有javah指令,后面JDK版本都是集成在javac指令集中)
javac -h . JNI.java
此命令先自动生成.class,然后再生成.h文件,如下所示
3.2 查看.class文件方法签名
C代码调用Java代码,需要用到反射,反射调用方法需要知道方法签名。
用javap命令,如下查看.class文件的方法签名。
javap -s JNI.class
示例查看CCallJavaJNI.class的方法签名,descriptor内容即为方法签名。
zhengweideMacBook-Pro:ccalljava zhengwei$ javap -s CCallJavaJNI.class
Compiled from "CCallJavaJNI.java"
public class com.example.ccalljava.CCallJavaJNI {
public com.example.ccalljava.CCallJavaJNI();
descriptor: ()V
public native void callbackAdd();
descriptor: ()V
public native void callbackGetMessage();
descriptor: ()V
public int add(int, int);
descriptor: (II)I
public void getMessage(java.lang.String);
descriptor: (Ljava/lang/String;)V
}
示例工程地址
git@github.com:justinworkshop/JniDemo.git