標準Java平臺下,每個Process能夠產生不少JavaVM對象,但在Android平臺上,每個Process只能產生一個Dalvik VM對象,也就是說在Android進程中是經過一個虛擬器對象來服務全部Java和c/c++代碼。html
在加載動態連接庫的時候,JVM會調用JNI_OnLoad(JavaVM* jvm, void* reserved)
(若是定義了該函數)。第一個參數會傳入JavaVM指針。java
在native code中調用JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args)
能夠獲得JavaVM指針。android
兩種狀況下,均可以用全局變量,好比
JavaVM* g_jvm
來保存得到的指針以便在任意上下文中使用。c++
JNIEnv是一個指針, 指向一個線程相關的結構, 線程相關結構指向JNI函數指針數組, 這個數組中存放了大量的JNI函數指針, 這些指針指向了具體的JNI函數。
數組
調用Java函數:JNIEnv表明Java運行環境, 可使用JNIEnv調用Java中的代碼;緩存
操做Java對象:Java對象傳入JNI層就是Jobject對象, 須要使用JNIEnv來操做這個Java對象;markdown
當本地c/c++代碼想得到當前線程的JNIEnv時,可使用兩種方法:oracle
vm->AttachCurrentThread(&env, 0)
vm->GetEnv((void**)&env, JNI_VERSION_1_6)
須要強調的是JNIEnv是跟線程相關的,最好仍是不要緩存這個JNIEnv* 。app
當建立的線程須要獲取JNIEnv* 的時候,最好在剛建立的時候調用一次AttachCurrentThread,而且不要忘記線程結束的時候執行DettachCurrentThread。jvm
public native String getStringFromNative();
原生實例方法經過第二個參數獲取實例引用,是jobject類型:
JNIEXPORT jstring JNICALL Java_com_dean_testndk_JNIHelper_getStringFromNative
(JNIEnv *, jobject);
public static native String getStringFromNative();
靜態方法沒有與實例綁定,所以第二個參數是jclass類型:
JNIEXPORT jstring JNICALL Java_com_dean_testndk_JNIHelper_getStringFromNative
(JNIEnv *, jclass);
原生代碼中,c與c++調用JNI函數的語法不一樣:
return (*env)->NewStringUTF(env, "Hello from JNI !");
C代碼中,JNIEnv是指向JNIVativeInterface結構的指針,爲了訪問任何一個JNI函數,該指針須要首先被解引用。由於不瞭解JNI環境,因此須要將JNIEnv傳遞給調用者
return env->NewStringUTF("Hello from JNI !");
C++代碼中,JNIEnv是C++類實例,JNI函數以成員函數的形式存在。所以不須要給調用着傳遞參數便可使用。
Java中有兩種數據類型:
boolean, byte, char, short, int, long, float, double
Java基本數據類型,能夠直接與C/C++的相應基本數據類型映射
字符串類,數組類,以及其餘類
與基本類型不一樣,引用類型對原生方法不透明,所以引用類型不能直接使用和修改,須要經過JNIEnv接口指針來調用JNI提供的API。
jclass FindClass(JNIEnv *env, const char *name);
jclass clz = (*env)->FindClass(env, "com/example/hello_jnicallback/JniHandler");
jclass GetObjectClass(JNIEnv *env, jobject obj);
jclass clz = (*env)->GetObjectClass(env, instance);
jobject NewObject(JNIEnv *env, jclass clazz,
jmethodID methodID, ...);
經過類,方法ID,對應參數來建立一個對象的實例:
jclass clz = (*env)->FindClass(env, "com/example/hello_jnicallback/JniHandler");
jmethodID jniHelperCtor = (*env)->GetMethodID(env, g_ctx.jniHelperClz, "<init>", "()V");
jobject handler = (*env)->NewObject(env, g_ctx.jniHelperClz, jniHelperCtor);
在JNI開發中,常常會在Native中調用Java的域和方法。
Java的域和方法都有兩類:
以下:
public class JavaClass {
// 實例域
private String instanceFiled = "Instance Field";
// 靜態域
private static String staticFiled = "Static Field";
// 實例方法
private String instanceMethod() {
return "Instance Method";
}
// 靜態方法
private static String staticMethod() {
return "Static Method";
}
}
下面例子是獲取實例域,方法的代碼。靜態域,靜態方法使用基本相同,都是先獲取描述它的ID,而後在經過ID調用相應方法。
void nativeCallJavaClass(JNIEnv *env, jobject instance) {
jclass clazz = env->GetObjectClass(instance);
// 獲取實例域的FieldID
jfieldID instanceFieldId = env->GetFieldID(clazz, "instanceField", "Ljava/lang/String;");
// 獲取實例域
jstring instanceField = (jstring) env->GetObjectField(instance, instanceFieldId);
// 獲取實例方法的MethodID
jmethodID instanceMethodId = env->GetMethodID(clazz, "staticMethod", "Ljava/lang/String;");
// 調用方法得到返回值
jstring instanceMethod = (jstring) env->CallObjectMethod(clazz, instanceMethodId);
};
爲了提高應用程序的性能,對於頻繁使用的域和方法,能夠緩存它們的ID方便下次使用。
Ljava/lang/String
; [I
[F
[Ljava/lang/String;
[Ljava/lang/Object;
[[I
[[F
類描述符:將完整的包名+類名中的.分隔符換成/分隔符
java/lang/String
或者使用域描述符[Ljava/lang/String;
數組類型的描述符:同域描述符
Ljava/lang/String;
(ILjava/lang/Object;)I
([B)V
java在bin中提供了javap命令,用於查看一個類方法的簽名
cd到.class對應路徑,而後執行javap -s classname
例如:
cd /Users/DeanGuo/TestNDK/app/build/intermediates/classes/debug/com/dean/testndk/;
javap -s JNIHelper
Compiled from "JNIHelper.java"
public class com.dean.testndk.JNIHelper {
public com.dean.testndk.JNIHelper();
descriptor: ()V
public static native java.lang.String getStringFromNative();
descriptor: ()Ljava/lang/String;
}
bogon:testndk DeanGuo$ javap -s JNIHelper
Warning: Binary file JNIHelper contains com.dean.testndk.JNIHelper
Compiled from "JNIHelper.java"
public class com.dean.testndk.JNIHelper {
public com.dean.testndk.JNIHelper();
descriptor: ()V
public static native java.lang.String getStringFromNative();
descriptor: ()Ljava/lang/String;
}
局部引用不能在後續的調用中唄緩存及重用,主要由於它們的使用期限僅限於原生方法,一旦原生函數返回,局部引用就被釋放。也能夠用void DeleteLocalRef(JNIEnv *env, jobject localRef);
函數顯示的釋放。
全局引用在原生方法的後續調用過程當中依然有效,除非它們被原生代碼顯式釋放。
jclass globalClazz;
jclass localClazz = env->FindClass("java/lang/String");
globalClazz = (jclass) env->NewGlobalRef(localClazz);
env->DeleteGlobalRef(globalClazz);
與全局應用同樣,弱全局引用在原生方法的後續調用過程當中依然有效。不一樣的是,弱全局引用並不阻止潛在對象被垃圾回事。
jclass weakGlobalClazz;
jclass localClazz = env->FindClass("java/lang/String");
weakGlobalClazz = (jclass) env->NewWeakGlobalRef(localClazz);
if (JNI_FALSE == env->IsSameObject(weakGlobalClazz, nullptr)) {
// 有效
} else {
// 對象被垃圾回收器回收, 不可以使用
}
env->DeleteGlobalRef(weakGlobalClazz);
NDK開發中JNI時,可使用__android_log_print
打印log信息。
#include <android/log.h>
頭文件加入Lib
ldLibs "log"
LOCAL_LDLIBS+= -L$(SYSROOT)/usr/lib -llog
使用__android_log_print(ANDROID_LOG_INFO, "JNITag","string From Java To C : %s", str);
#include <android/log.h>
// Android log function wrappers
static const char* kTAG = "testNDK";
#define LOGI(...) \
((void)__android_log_print(ANDROID_LOG_INFO, kTAG, __VA_ARGS__))
#define LOGW(...) \
((void)__android_log_print(ANDROID_LOG_WARN, kTAG, __VA_ARGS__))
#define LOGE(...) \
((void)__android_log_print(ANDROID_LOG_ERROR, kTAG, __VA_ARGS__))
// 使用以下:
LOGI("JNI_LOG");
若是想在NDK中使用c++STL庫:
APP_STL := gnustl_static
stl "gnustl_static"
- system:使用默認最小的C++運行庫,這樣生成的應用體積小,內存佔用小,但部分功能將沒法支持
- stlport_static:使用STLport做爲靜態庫,這項是Android開發網極力推薦的
- stlport_shared:STLport 做爲動態庫,這個可能產生兼容性和部分低版本的Android固件,目前不推薦使用。
- gnustl_static:使用 GNU libstdc++ 做爲靜態庫