Android中的JNI原理
JNI(Java Native Interface)是Java本地接口,用來與其他的語言進行通信。通常情況下,當需要節省程序的運行時間時,會選擇用C語言進行開發,用Java調用C語言實現一些功能。或者代碼的核心算法、密鑰等,也會保存在C語言的代碼中,以提高代碼的安全性。
一.Native方法的註冊
1.靜態註冊
1)靜態註冊的原理
根據方法名,將java方法和JNI函數建立關聯。
2)靜態註冊的方法
i)在項目中創建Test.java
package com.example;
public class Test{
static{
System.loadLibrary(“test_jni”);
}
public native void test();
}
ii)打開命令行,進入項目的src/main/java目錄下,執行
javac com/example/Test.java
javah com.example.Test
會在當前目錄下,生成com_example_Test.h文件。在com_example_Test.h文件中,生成了一個名爲Java_com_example_test的函數。
iii)當在Java中調用test方法時,JNI會尋找Java_com_example_test函數,找到後爲Java方法和JNI的函數建立聯繫,完成靜態註冊。
3)靜態註冊的缺點
i)JNI層的函數名過長。
ii)聲明Native方法的類需要用javah生成頭文件。
iii)初次調用Native方法時需要建立聯繫,影響效率。
2.動態註冊
JNI中有一種結構JNINativeMethod,用來記錄Java的Native方法和JNI方法的關聯關係,他在jni.h中定義:
Typedef struct{
const char* name;//java方法的名字
const char* signature;//java方法的簽名信息
void* fnPtr;//JNI中對應的方法指針
} JNINativeMethod;
1)動態註冊的方法
i)創建JNINativeMethod數組:
static const JNINativeMethod methods[] = {
{“test1”, ”()v”,(void *)com_example_Test_test1},
{“test2”, ”()v”,(void *)com_example_Test_test2},
{“test3”, ”()v”,(void *)com_example_Test_test3},
{“test4”, ”()v”,(void *)com_example_Test_test4}
};
ii)調用AndroidRuntime::registerNativeMethods函數完成註冊。
2)解析AndroidRuntime::registerNativeMethods函數
AndroidRuntime.cpp中registerNativeMethods函數的執行過程:
調用jniRegisterNativeMethods函數。
JNIHelp.cpp中jniRegisterNativeMethods函數的執行過程:
調用JNIEnv對象的RegisterNative函數完成JNI方法的註冊。
二.數據類型的轉換
1.基本數據類型的轉換
Java | Native | Signature |
---|---|---|
byte | jbyte | B |
char | jchar | C |
double | jdouble | D |
float | jfloat | F |
int | jint | I |
short | jshort | S |
long | jlong | J |
boolean | jboolean | Z |
void | void | V |
2.引用數據類型的轉換
Java | Native | Signature |
---|---|---|
Object | jobject | L+classname+; |
Class | jclass | Ljava/long/Class; |
String | jstring | Ljava/lang/String; |
Throwable | jthrowable | Ljava/lang/Throwable; |
Object[] | jobjectArray | [L+classname+; |
byte[] | jbyteArray | [B |
char[] | jcharArray | [C |
double[] | jdoubleArray | [D |
float[] | jfloatArray | [F |
int[] | jintArray | [I |
short[] | jshortArray | [S |
long[] | jlongArray | [J |
boolean[] | jbooleanArray | [Z |
3.引用數據類型的繼承關係
三.方法簽名
JNI通過引入方法簽名來解決Java中方法重載導致的僅通過方法名無法找到方法的問題。
1.JNI方法簽名的格式
格式:(參數的簽名格式…)返回值的簽名格式
java中提供javap命令自動生成方法簽名:
javac C:/Test.java
javap –s –p C:/Test.class
四.解析JNIEnv
JNIEnv是Native中Java環境的代表,用來調用Java的方法、操作Java的變量和對象。
jni.h中JNIEnv的定義:
#if defined (_cplusplus)
typedef _JNIEnv JNIEnv;
typedef _JavaVM JavaVM;
#else
typedef const struct JNINativeInterface* JNIEnv;
typedef const struct JNIInvokeInterface* JavaVM;
#endif
JavaVM是虛擬機層在JNI層的代表,一個虛擬機進程只有一個JavaVM。通過JavaVM的AttachCurrentThread函數可以獲取一個線程的JNIEnv,通過DetachCurrentThread函數釋放資源。
jni.h中_JNIEnv結構體的定義和部分函數:
struct _JNIEnv{
const struct JNINativeInterface* functions;
#if defined (_cplusplus)
…
jclass FindClass (const char* name)
{ return functions->FindClass (this, name); }
…
jmethodID GetMethodID (jclass clazz, const char* name, const char* sig)
{ return functions->GetMethodID (this, clazz, name, sig); }
…
jfieldID GetFieldID (jclass clazz, const char* name, const char* sig)
{ return functions->GetFieldID (this, clazz, name, sig); }
}
_JNIEnv結構體內部包含了JNINativeInterface結構體。其定義的函數通過調用JNINativeInterface的函數來實現。
jni.h中JNINativeInterface結構體的定義和部分函數:
sruct JNINativeInterface{
…
jclass (*FindClass) (JNIEnv*, const char* );
…
jmethodID (*GetMethodID) (JNIEnv*, jclass, const char*’, const char*);
…
jfieldID (*GetFieldID) (JNIEnv*, jclass, const char*, const char*);
…
}
在JNINativeInterface結構體中定義了很多函數指針,通過這些函數指針,能夠定位到虛擬機中的JNI函數表,實現JNI層在虛擬機中的函數調用。
jfieldID和jmethodID用來代替Java類中的成員變量和方法。
五.JNI的引用類型
1.本地引用
JNIEnv提供的函數所返回的引用基本上都是本地引用。
特點:
1)Native函數返回時,本地引用自動釋放。
2)只在創建它的線程有效,不能跨線程使用。
3)本地引用是JVM負責的引用類型,受JVM管理。
可以通過DeleteLocalRef函數手動刪除本地引用。
2.全局引用
特點:
1)在Native函數返回時不會自動釋放,需要手動釋放,不會被GC回收。
2)全局引用可以跨線程使用。
3)全局引用不受JVM管理。
可以通過NewGlobalRef函數來創建全局引用,通過DeleteGlobalRef函數釋放全局引用。
3.弱全局引用
弱全局引用是一種特殊的全局引用,和全局引用類似,弱全局引用可以被GC回收,回收之後會指向NULL。
可以通過NewWeakGlobalRef函數來創建弱全局引用,通過DeleteWeakGlobalRef函數釋放弱全局引用。在使用前需要使用JNIEvn的IsSameObject函數判斷對象是否被回收。