Android專案中實現native呼叫
轉載自搜狗測試公眾號,本人學習使用,侵權刪
最近小編在做公司輸入法專案中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中呼叫。兩種方法各有優缺點,大家根據自己的程式碼習慣選擇合適的方式就好。