1. 程式人生 > >Android專案中實現native呼叫

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中呼叫。兩種方法各有優缺點,大家根據自己的程式碼習慣選擇合適的方式就好。