1、JNI(Java Native Interface) Java本地接口,又叫Java原生接口。它允許Java調(diào)用C/C++的代碼,同時(shí)也允許在C/C++中調(diào)用Java的代碼??梢园袹NI理解為一個(gè)橋梁,連接Java和底層
JNI的書寫步驟如下:
a.編寫帶有native聲明的方法的Java類
b.使用javac命令編譯編寫的Java類
c.使用java -jni ****來生成后綴名為.h的頭文件
d.引入.h的頭文件,使用其他語言(C、C++)實(shí)現(xiàn)本地方法
e.將本地方法編寫的文件生成動(dòng)態(tài)鏈接庫
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直接運(yùn)行
運(yùn)行指令: javah -jni 包名.類名(直接放入java文件夾內(nèi)不用包名)
javah -jni HelloWorld
得到HelloWorld.h文件
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* 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創(chuàng)建一個(gè)dll項(xiàng)目
引入HelloWorld.h實(shí)現(xiàn)jni接口
JNIEXPORT void JNICALL Java_HelloWorld_sayHelloWorld
(JNIEnv *, jobject){
printf("Hello World !");
return;
}
生成dll包,我這邊叫Dll1.dll。放到j(luò)ava目錄,就可以實(shí)現(xiàn)打印hello world了
2,為了使您能夠在 Android 應(yīng)用中使用 C 和 C++ 代碼,并提供眾多平臺(tái)庫(arm64-v8a x86等),需要、Android NDK 就是一套工具集合配置才能打出Android 平臺(tái)適用的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下創(chuàng)建一個(gè)jni文件夾
2.1創(chuàng)建Android.mk文件
Android.mk文件是用來指定諸如編譯生成so庫名、引用的頭文件目錄、需要編譯的.c/.cpp文件和.a靜態(tài)庫文件等。
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、創(chuàng)建一個(gè)c++文件,并實(shí)現(xiàn)注冊jni接口,并實(shí)現(xiàn)接口的功能
jni開發(fā)中C/C++ 之所以需要頭文件(.h),有兩個(gè)用處,一個(gè)是在開發(fā)編譯的時(shí)候,在各個(gè)編譯單元(Compile Unit)之間共享同樣的定義;一個(gè)是在發(fā)布程序庫的時(shí)候,讓使用者知道調(diào)用接口。本次使用中我們沒有使用java -jni ****來生成后綴名為.h的頭文件,所以需要注冊.
3.1 靜態(tài)注冊:
通過 JNIEXPORT 和 JNICALL 兩個(gè)宏定義聲明,在虛擬機(jī)加載 so 時(shí)發(fā)現(xiàn)上面兩個(gè)宏定義的函數(shù)時(shí)就會(huì)鏈接到對(duì)應(yīng)的 native 方法。
3.2 動(dòng)態(tài)注冊:
先通過JNI重載JNI_OnLoad()實(shí)現(xiàn)本地方法,然后直接在Java中調(diào)用本地方法。(當(dāng)前例子通過動(dòng)態(tài)注冊,更靈活)
jintools.c文件
#include <jni.h>
#include <android/log.h>
#include <stdio.h>
#include <stdlib.h>
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){
//打印日志,說明已經(jīng)進(jìn)來了
__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;
}
//注冊四個(gè)方法,注意簽名
const JNINativeMethod method[]={
{"add","(II)I",(void*)addNumber},
{"sub","(II)I",(void*)subNumber},
{"mul","(II)I",(void*)mulNumber},
{"div","(II)I",(void*)divNumber}
};
//找到對(duì)應(yīng)的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 調(diào)用
int result = JNITools.add(a, b);
5、第三方so的使用
常見的開源第三方c++代碼,可以使用linux配置打包,打出相應(yīng)平臺(tái)的so文件和一個(gè)jar包。這里基本上第三方平臺(tái)會(huì)把jni接口寫在jar中,native的實(shí)現(xiàn)在so中。Android使用時(shí)只要直接引入so和jar既可以直接調(diào)用。
一般的開源c++項(xiàng)目已經(jīng)寫好jni接口并實(shí)現(xiàn),直接打出so和jar,android引入既可用。
對(duì)于開源c++項(xiàng)目沒有實(shí)現(xiàn)jni接口的,又想通過JAVA程序調(diào)用C/C++動(dòng)態(tài)庫的話,則需要使用SWIG,Simplified Wrapper and Interface Generator,這是一個(gè)封裝C/C++動(dòng)態(tài)庫供其他編程語言調(diào)用的神器。