1、JNI(Java Native Interface) Java本地接口,又叫Java原生接口。它允许Java调用C/C++的代码,同时也允许在C/C++中调用Java的代码。可以把JNI理解为一个桥梁,连接Java和底层
JNI的书写步骤如下:
a.编写带有native声明的方法的Java类
b.使用javac命令编译编写的Java类
c.使用java -jni ****来生成后缀名为.h的头文件
d.引入.h的头文件,使用其他语言(C、C++)实现本地方法
e.将本地方法编写的文件生成动态链接库
class HelloWorld
{
//native声明,用于生成c/c++代码
public native void sayHelloWorld();
//加载c/c++编译好的库
static
{
System.loadLibrary("Dll1");
}
public static void main(String[] args)
{
new HelloWorld().sayHelloWorld();
}
}
在Androidstudio Terminal直接运行
运行指令: javah -jni 包名.类名(直接放入java文件夹内不用包名)
javah -jni HelloWorld
得到HelloWorld.h文件
/* DO NOT EDIT THIS FILE - it is machine generated */
#include
/* Header for class HelloWorld */
#ifndef _Included_HelloWorld
#define _Included_HelloWorld
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: HelloWorld
* Method: sayHelloWorld
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_HelloWorld_sayHelloWorld
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
使用visual studio创建一个dll项目
引入HelloWorld.h实现jni接口
JNIEXPORT void JNICALL Java_HelloWorld_sayHelloWorld
(JNIEnv *, jobject){
printf("Hello World !");
return;
}
生成dll包,我这边叫Dll1.dll。放到java目录,就可以实现打印hello world了
2,为了使您能够在 Android 应用中使用 C 和 C++ 代码,并提供众多平台库(arm64-v8a x86等),需要、Android NDK 就是一套工具集合配置才能打出Android 平台适用的so。
1、android端这边的jni接口
public class JNITools {
static {
System.loadLibrary("jniyjn");
}
//加法
public static native int add(int a,int b);
//减法
public static native int sub(int a,int b);
//乘法
public static native int mul(int a,int b);
//除法
public static native int div(int a,int b);
}
2、在main下创建一个jni文件夹
2.1创建Android.mk文件
Android.mk文件是用来指定诸如编译生成so库名、引用的头文件目录、需要编译的.c/.cpp文件和.a静态库文件等。
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := jniyjn
LOCAL_SRC_FILES := jnitools.c
LOCAL_LDLIBS := -L$(SYSROOT)/usr/lib -llog
include $(BUILD_SHARED_LIBRARY)
3、创建一个c++文件,并实现注册jni接口,并实现接口的功能
jni开发中C/C++ 之所以需要头文件(.h),有两个用处,一个是在开发编译的时候,在各个编译单元(Compile Unit)之间共享同样的定义;一个是在发布程序库的时候,让使用者知道调用接口。本次使用中我们没有使用java -jni ****来生成后缀名为.h的头文件,所以需要注册.
3.1 静态注册:
通过 JNIEXPORT 和 JNICALL 两个宏定义声明,在虚拟机加载 so 时发现上面两个宏定义的函数时就会链接到对应的 native 方法。
3.2 动态注册:
先通过JNI重载JNI_OnLoad()实现本地方法,然后直接在Java中调用本地方法。(当前例子通过动态注册,更灵活)
jintools.c文件
#include
#include
#include
#include
jint addNumber(JNIEnv *env,jclass clazz,jint a,jint b);
jint subNumber(JNIEnv *env,jclass clazz,jint a,jint b);
jint mulNumber(JNIEnv *env,jclass clazz,jint a,jint b);
jint divNumber(JNIEnv *env,jclass clazz,jint a,jint b);
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved){
//打印日志,说明已经进来了
__android_log_print(ANDROID_LOG_DEBUG,"JNITag","enter jni_onload");
JNIEnv* env = NULL;
jint result = -1;
// 判断是否正确
if((*vm)->GetEnv(vm,(void**)&env,JNI_VERSION_1_6)!= JNI_OK){
return result;
}
//注册四个方法,注意签名
const JNINativeMethod method[]={
{"add","(II)I",(void*)addNumber},
{"sub","(II)I",(void*)subNumber},
{"mul","(II)I",(void*)mulNumber},
{"div","(II)I",(void*)divNumber}
};
//找到对应的JNITools类 com.example.myapplication
jclass jClassName=(*env)->FindClass(env,"com/example/jniyjn/JNITools");
//开始注册
jint ret = (*env)->RegisterNatives(env,jClassName,method, 4);
//如果注册失败,打印日志
if (ret != JNI_OK) {
__android_log_print(ANDROID_LOG_DEBUG, "JNITag", "jni_register Error");
return -1;
}
return JNI_VERSION_1_6;
}
jint addNumber(JNIEnv *env,jclass clazz,jint a,jint b){
return a+b;
}
jint subNumber(JNIEnv *env,jclass clazz,jint a,jint b){
return a-b;
}
jint mulNumber(JNIEnv *env,jclass clazz,jint a,jint b){
return a*b;
}
jint divNumber(JNIEnv *env,jclass clazz,jint a,jint b){
return a/b;
}
4、Android 端使用
4.1 app下的buildgradle引入
defaultConfig下加入
ndk{
moduleName "jniyjn"
abiFilters 'x86','armeabi-v7a','arm64-v8a'
ldLibs "log"
}
buildTypes下加入
externalNativeBuild {
ndkBuild {
path 'src/main/jni/Android.mk'
}
}
4.2 调用
int result = JNITools.add(a, b);
5、第三方so的使用
常见的开源第三方c++代码,可以使用linux配置打包,打出相应平台的so文件和一个jar包。这里基本上第三方平台会把jni接口写在jar中,native的实现在so中。Android使用时只要直接引入so和jar既可以直接调用。
一般的开源c++项目已经写好jni接口并实现,直接打出so和jar,android引入既可用。
对于开源c++项目没有实现jni接口的,又想通过JAVA程序调用C/C++动态库的话,则需要使用SWIG,Simplified Wrapper and Interface Generator,这是一个封装C/C++动态库供其他编程语言调用的神器。