轉載自搜狗測試公眾號,本人學習使用,侵權刪
最近小編在做公司輸入法項目中java與native交互部分的測試,先簡單學習了java代碼調用native代碼的實現原理,本次與大家一起分享jni協議,了解java關聯C/C++代碼的調用原則。
JNI是Java Native Interface的縮寫,能夠提供API實現Java和Native語言(主要是C/C++)的通信,JNI提供兩種方式實現Java對native代碼的調用:靜態關聯和動態關聯。
靜態關聯
靜態關聯的實現過程是通過經過特定規則命名的jni函數名來遍歷java和jni函數之間的關聯。具體分三步實現:
1、java代碼中聲明native函數;
2、通過javah生成native函數的jni形式;
3、在jni代碼中實現native函數。
示例如下:
1、實現一段java代碼JNIUtils.java:
package com.example.administrator.myapplication;
public class JNIUtils {
static{ System.loadLibrary("native-lib"); }
public static native String sayHiFromJNI(); }
JNIUtils.java代碼包名為com.example.administrator.myapplication,聲明了native函數名為sayHiFromJNI()。
2、通過javah生成native函數的jni形式
在代碼的src/main/java目錄下通過terminal端輸入命令:javah -d ../jni com.example.administrator.myapplication.JNIUtils。通過Javah命令能夠生成java類對應的頭文件,命令-d表示生成一個目錄,習慣上我們會將jni相關代碼存放在java同級目錄下的jni文件夾中(../jni),最后的com.example.administrator.myapplication.JNIUtils就是我們的JNIUtils完整類名了。
執行后jni目錄下會生成一個com.example.administrator.myapplication.JNIUtils.h文件,如下:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_example_administrator_myapplication_JNIUtils */
#ifndef _Included_com_example_administrator_myapplication_JNIUtils
#define _Included_com_example_administrator_myapplication_JNIUtils
#ifdef __cplusplus
extern "C" {
#endif/* * Class: com_example_administrator_myapplication_JNIUtils * Method: sayHiFromJNI * Signature: ()Ljava/lang/String; */
JNIEXPORT jstring JNICALL Java_com_example_administrator_myapplication_JNIUtils_sayHiFromJNI (JNIEnv *, jclass);
#ifdef __cplusplus
}
#endif
#endif
3、在jni代碼中實現native函數
在jni目錄中新建cpp文件,命名JNIHi.cpp,在cpp文件中include "com_example_administrator_myapplication_JNIUtils.h"實現native函數的功能即可,在JNIUtils.java文件中我們定義了public static native String sayHiFromJNI();函數,因此在JNIHi.cpp中需要實現具體邏輯。
代碼如下:
#include "com_example_administrator_myapplication_JNIUtils.h"
JNIEXPORT jstring JNICALL Java_com_example_administrator_myapplication_JNIUtils_sayHiFromJNI
(JNIEnv *env, jclass jclass){
return env->NewStringUTF("Hi From JNI!!!");
}
如此便實現了JNIUtils.java代碼中對C++代碼JNIHi.cpp中函數的調用。
動態關聯
靜態關聯的方法簡單易學,但是是不是有人覺得函數名這么長,規范是否太繁瑣,那么我們還有更簡單的方式:動態關聯。
動態方式的主要實現原理是通過RegisterNatives函數把C/C++中的方法映射到Java中。
1、編寫java代碼JNIUtils.java,與靜態關聯相同
package com.example.administrator.myapplication;
public class JNIUtils {
static{ System.loadLibrary("native-lib"); }
public static native String sayHiFromJNI(); }
上述函數中我們使用System.loadLibrary("native-lib")方法加載so庫的時候,Java虛擬機就會找到JNI_OnLoad函數並調用,該函數前面有三個關鍵字分別是JNIEXPORT,JNICALL ,jint。其中JNIEXPORT和JNICALL是兩個宏定義,用於指定該函數是JNI函數,通過該函數能夠實現java與native的動態關聯,以代碼示例。
2、編寫native關聯代碼JNIHi.cpp
代碼示例:
#include <jni.h>
#include <stdio.h>
#include<android/log.h>
#include <stdlib.h>
using namespace std;
#ifdef __cplusplus
extern "C" {
#endif
static const char *className = "com/example/administrator/myapplication/JNIUtils";
JNIEXPORT jstring JNICALL sayHiFromJNI(JNIEnv *env,jobject obj) {
return env->NewStringUTF("Hi From JNI!!!"); }
static JNINativeMethod gJni_Methods_table[] = { {"sayHiFromJNI", "()Ljava/lang/String;", (void*)sayHiFromJNI}, };
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved){ JNIEnv* env = NULL; jint result = -1;
if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
return result; } jclass clazz = (env)->FindClass( className);
if (clazz == NULL){
return -1; }
if ((env)->RegisterNatives(clazz, gJni_Methods_table, sizeof(gJni_Methods_table) / sizeof(gJni_Methods_table[0])) < 0) {
return -1; }
return JNI_VERSION_1_4; }
#ifdef __cplusplus
}
#endif
通過代碼閱讀,我們發現JNI_OnLoad函數的實現主要包含兩步:第一、vm->GetEnv()函數獲取JNIEnv結構體指針,該指針指向一個函數表,對應JNI函數,我們可以通過這些JNI函數實現JNI編程;第二、RegisterNatives()函數實現native方法的注冊,其中主要應用了一個靜態變量JNINativeMethod類型的數組,它代表了native方法。JNINativeMethod結構被定義在jni.h中,Java與JNI可以通過該結構建立聯系,如此Java虛擬機就可以用相應的函數映射表來調用相應的函數,而不需要通過函數名來查找需要調用的函數了。
小結
簡而言之,靜態關聯:先由Java聲明本地方法,然后通過JNI實現方法的定義。動態關聯:先通過JNI_OnLoad實現本地方法,然后直接在Java中調用。兩種方法各有優缺點,大家根據自己的代碼習慣選擇合適的方式就好。