1.JNI概述
Android系統按語言來劃分的話由兩個世界組成,分別是Java世界和Native世界。那爲什麼要這麼劃分呢?Android系統由Java寫不好嗎?除了性能的之外,最主要的原因就是在Java誕生之前,就有很多程序和庫都是由Native語言寫的,因此,重複利用這些Native語言編寫的庫是十分必要的,況且Native語言編寫的庫具有更好的性能。
這樣就產生了一個問題,Java世界的代碼要怎麼使用Native世界的代碼呢,這就需要一個橋樑來將它們連接在一起,而JNI就是這個橋樑。
通過JNI,Java世界的代碼就可以訪問Native世界的代碼,同樣的,Native世界的代碼也可以訪問Java世界的代碼。
靜態註冊
原理:根據函數名來建立 java 方法與 JNI 函數的一一對應關係;
實現流程:
編寫 java 代碼;
利用 javah 指令生成對應的 .h 文件;
對 .h 中的聲明進行實現;
弊端:
編寫不方便,JNI 方法名字必須遵循規則且名字很長;
編寫過程步驟多,不方便;
程序運行效率低,因爲初次調用native函數時需要根據根據函數名在JNI層中搜索對應的本地函數,然後建立對應關係,這個過程比較耗時;
extern "C" JNIEXPORT jstring JNICALL
Java_cn_com_jni_dexshell_MainActivity_stringFromJNI(
JNIEnv *env,
jobject jobj/* this */,jstring id) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello .c_str());
}
動態註冊
原理:利用 RegisterNatives 方法來註冊 java 方法與 JNI 函數的一一對應關係;
實現流程:
利用結構體 JNINativeMethod 數組記錄 java 方法與 JNI 函數的對應關係;
實現 JNI_OnLoad 方法,在加載動態庫後,執行動態註冊;
調用 FindClass 方法,獲取 java 對象;
調用 RegisterNatives 方法,傳入 java 對象,以及 JNINativeMethod 數組,以及註冊數目完成註冊;
優點:
流程更加清晰可控;
效率更高;
JNINativeMethod
在動態註冊的過程中使用到了結構體 JNINativeMethod 用於記錄 java 方法與 jni 函數的對應關係
typedef struct {
const char* name;
const char* signature;
void* fnPtr;
} JNINativeMethod;
結構體的第一個參數 name 是java 方法名;
第二個參數 signature 用於描述方法的參數與返回值;
第三個參數 fnPtr 是函數指針,指向 jni 函數;
其中,第二個參數 signature 使用字符串記錄方法的參數與返回值,具體格式形如“()V”、“(II)V”,其中分爲兩部分,括號內表示的是參數,括號右側表示的是返回值;
數據類型映射
基本數據類型
java 類型 | native 類型 | 域描述符 |
boolean | jboolean | Z |
byte | jbyte | B |
char | jchar | C |
short | jshort | S |
int | jint | I |
long | jlong | J |
float | jfloat | F |
double | jdouble | D |
void | void | V |
2. 數組引用類型
如果是一維數組則遵循下表,如果是二維數組或更高維數組則對應的 native 類型爲 jobjectArray,域描述符中使用 ‘[’ 的個數表示維數
java 類型 | native 類型 | 域描述符 |
int[] | jintArray | [I |
float[] | jfloatArray | [f |
byte[] | jbyteArray | [B |
char[] | jcharArray | [C |
short[] | jshortArray | [S |
double[] | jdoubleArray | [D |
long[] | jlongArray | [F |
boolean[] | jbooleanArray | [Z |
3. 對象引用類型
對於其它引用類型,即 java 中的對象,其映射規則爲
java 類型 | native 類型 | 域描述符 |
類名(如 Surface) | 通常是 jobject,僅有一種例外,如果 java 類型是 String,則對應的native 類型是 jstring | 以”L”開頭,以”;”結尾中間是用”/” 隔開的包及類名(如 Landroid/view/Surface;)如果內部類則使用$連接內部類; |
4. 對象數組引用類型
如果是一維數組則遵循下表,如果是二維數組或更高維數組則對應的 native 類型爲 jobjectArray,域描述符中使用 ‘[’ 的個數表示維數
java 類型 | native 類型 | 域描述符 |
類名(如 Surface) | 通常是 jobject,僅有一種例外,如果 java 類型是 String,則對應的native 類型是 jstring | 在對象引用類型的域描述符的基礎上在左邊添加’[‘字符 |
jni 函數默認參數
在 jni 函數中有兩個默認參數
JNIEnv *env, jobject thiz
其中 JNIEnv 指代的是當前 java 環境,可以利用 JNIEnv 可以操作 java 層代碼;jobject 指代的是 jni 函數對應的 java native 方法的類實例,如果 java 方法是 static,則代表的是 class 對象;
例:
java
package cn.com.jni.jniautoreg;
public class TestJNI {
public static native void native_init();
public static native String stringFromJNI(String name,String address);
}
native
#include <jni.h>
#include <string>
#include <stdio.h>
#include <stdlib.h>
#include <android/log.h>
#include <memory.h>
#define LOG_TAG "my-jni"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG, __VA_ARGS__)
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG, __VA_ARGS__)
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN,LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG, __VA_ARGS__)
#define LOGF(...) __android_log_print(ANDROID_LOG_FATAL,LOG_TAG, __VA_ARGS__)
#define NELEM(x) ((int) (sizeof(x) / sizeof((x)[0])))
using namespace std;
static const char *const kClassJniTest =
"cn/com/jni/jniautoreg/TestJNI";
jstring JNI_stringFromJNI(JNIEnv *env, jobject obj, jstring name,jstring address) {
LOGE("JAVA call JNI stringFromJNI");
string hello = "Hello from JNI";
return env->NewStringUTF(hello.c_str());
}
void JNI_native_init(JNIEnv *env, jobject thizz) {
LOGE(" native_1init");
}
static const JNINativeMethod gMethods[] = {
{
"native_init",
"()V",
(void *) JNI_native_init
},
{
"stringFromJNI",
"(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;",
(void *) JNI_stringFromJNI
},
};
static int registerNativeMethods(JNIEnv *env, const char *className, const JNINativeMethod *gMethod,
int numMethods) {
jclass clazz;
clazz = env->FindClass(className);
if (clazz == NULL) {
LOGI(" JNI reg faild:%s",className);
return JNI_FALSE;
}
if (env->RegisterNatives(clazz, gMethod, numMethods) < 0) {
LOGI(" JNI reg method failed:%s",gMethod->name);
return JNI_FALSE;
}
return JNI_TRUE;
}
static int register_along_jni(JNIEnv *env) {
return registerNativeMethods(env, kClassJniTest, gMethods,
NELEM(gMethods));
}
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void * /* reserved */) {
JNIEnv *env = NULL;
jint result = -1;
if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {
LOGE("ERROR: JNI version error");
return JNI_ERR;
}
if (register_along_jni(env) == -1) {
LOGE("ERROR: JNI_OnLoad failed");
return JNI_ERR;
}
result = JNI_VERSION_1_6;
return result;
}
注意:
在JNI_OnLoad函數的結尾處,我們一定要有返回值,而且必須是JNI_VERSION_1_4 或 JNI_VERSION_1_6,也就是JNI的版本號,我們一定要返回正確的版本號,否則系統也是無法加載的。