什么是JNI?
JNI Java Native Interface java本地接口
JNI 能夠解決什么問題灵寺?
在 java 面世之前,很多代碼都是使用 c/c++ 編寫的纷宇,在 java 面世后為了避免重復(fù)造輪子,并且可以讓 java 使用之前編寫的 c/c++ 代碼,這樣就出現(xiàn)了 JNI 這門技術(shù)火诸,它是一個(gè) java 和 native 進(jìn)行相互調(diào)用的中間人躲查。
因?yàn)橛行┕δ苁褂?java 代碼編寫可能會(huì)有性能問題它浅,并且反編譯代碼的安全性問題,因此在某些情況下就需要使用 c/c++ 來配合 java 來開發(fā)了镣煮。
Java層是如何使用JNI姐霍?
Java 層調(diào)用 JNI 層的方法需要做兩件事
- 加載動(dòng)態(tài)庫(kù)。
- 使用 native 關(guān)鍵字聲明與 JNI 層對(duì)應(yīng)的函數(shù)典唇。
如何加載動(dòng)態(tài)庫(kù)镊折?
理論上是在任何需要調(diào)用 native 代碼的地方之前就可以去加載動(dòng)態(tài)庫(kù),但是一般情況下都會(huì)在類的 static 代碼塊中去加載介衔。
System.loadLibrary("動(dòng)態(tài)庫(kù)的名稱")
動(dòng)態(tài)庫(kù)的名稱恨胚,例如 media_jni,系統(tǒng)會(huì)自動(dòng)根據(jù)不同的平臺(tái)拓展成真實(shí)的動(dòng)態(tài)庫(kù)文件名炎咖,例如在Linux系統(tǒng)上會(huì)拓展成libmedia_jni.so赃泡,而在Windows平臺(tái)上則會(huì)拓展成media_jni.dll。
Native 和 JNI 層的代碼怎么相互對(duì)應(yīng)呢乘盼?
MediaScanner 類定義的 native_init 方法 是在這個(gè)路徑下:android.media.MediaScanner.native_init
JNI 層的代碼 : android_media_MediaScanner_native_init
jni 方法的命名規(guī)則:Java_包名類名方法名
JNI函數(shù)的注冊(cè)問題
什么叫做注冊(cè)急迂?
就是將 Java 層的函數(shù)和 JNI 層的函數(shù)關(guān)聯(lián)起來。
函數(shù)注冊(cè)分為兩種方式:靜態(tài)注冊(cè)\動(dòng)態(tài)注冊(cè)
一蹦肴、靜態(tài)注冊(cè)
-
在 Java 層使用 native 關(guān)鍵字描述函數(shù)
public class Utils { public static native int add(int a, int b); }
-
根據(jù)在 Utils 類編寫的 add 函數(shù)生成對(duì)應(yīng)的 native 函數(shù)僚碎。
//javah 是 jdk 提供的一個(gè)工具 //在終端使用 cd 切換到 Utils 編譯后的文件目錄下,具體位置看下面的截圖阴幌,不要搞錯(cuò)地方勺阐,否則會(huì)出現(xiàn)找不到Utils類 //-o Utils.h 表示輸出的文件名為 Utils.h javah -o Utils.h com.lu.ndkdemo.Utils //生成文件的路徑:app/build/intermediates/classes/debug/com/lu/ndkdemo/Utils.class
-
過程:編寫 Java 代碼 -> 編譯 -> 使用 javah 命令生成 packageName_class.h 頭文件
javah -o output packgeName.className
-
生成對(duì)應(yīng)的 Utils.h 文件
/* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> /* Header for class com_lu_ndkdemo_Utils */ #ifndef _Included_com_lu_ndkdemo_Utils #define _Included_com_lu_ndkdemo_Utils #ifdef __cplusplus extern "C" { #endif /* * Class: com_lu_ndkdemo_Utils * Method: add * Signature: (II)I */ JNIEXPORT jint JNICALL Java_com_lu_ndkdemo_Utils_add (JNIEnv *, jclass, jint, jint); #ifdef __cplusplus } #endif #endif
native 函數(shù)是如何尋找 JNI 函數(shù)的卷中?
當(dāng)Java層調(diào)用native_init函數(shù)時(shí),它會(huì)從對(duì)應(yīng)的JNI庫(kù)Java_android_media_MediaScanner_native_init渊抽,如果沒有蟆豫,就會(huì)報(bào)錯(cuò)。如果找到懒闷,則會(huì)為這個(gè)native_init和Java_android_media_MediaScanner_native_init 建立一個(gè)關(guān)聯(lián)關(guān)系十减,其實(shí)就是保存JNI層函數(shù)的函數(shù)指針。以后再調(diào)用native_init函數(shù)時(shí)愤估,直接使用這個(gè)函數(shù)指針就可以了帮辟,當(dāng)然這項(xiàng)工作是由虛擬機(jī)完成的。將 Utils.h 文件拷貝到存放 native 代碼的文件夾中
我的工程存放 native 代碼的目錄是 main/cpp/
所以將生成 Utils.h 拷貝到 main/cpp 即可
-
編寫 native 層的代碼玩焰,我將其命名為 test.c
//引入剛才生成的 Utils.h 文件 #include "Utils.h" #include <android/log.h> //將 Utils.h 生成函數(shù)拷貝到test.c 中 //注意拷貝過來的函數(shù)是不完整的由驹,需要手動(dòng)添加參數(shù)變量名 JNIEXPORT jint JNICALL Java_com_lu_ndkdemo_Utils_add (JNIEnv *env, jclass obj, jint a, jint b) { //返回計(jì)算結(jié)果 return a + b; }
接下來就在 java 層代碼加載 so 庫(kù),并且調(diào)用該方法即可昔园,這一步就忽略不寫蔓榄。
二、動(dòng)態(tài)注冊(cè)
為什么有了靜態(tài)注冊(cè)之后還要有一個(gè)動(dòng)態(tài)注冊(cè)的功能呢默刚?
我們看到了甥郑,在靜態(tài)注冊(cè)中,我們使用了 javah 生成對(duì)應(yīng) native 函數(shù)荤西,可以看出它的方法名是非常的長(zhǎng)的澜搅,因此為了簡(jiǎn)化這個(gè)方法的表示,就有了動(dòng)態(tài)注冊(cè)皂冰。
動(dòng)態(tài)注冊(cè)的原理是這樣的:JNI 允許我們提供一個(gè)函數(shù)映射表店展,注冊(cè)給 JVM,這樣 JVM 就可以用函數(shù)映射表來調(diào)用相應(yīng)的函數(shù)秃流,而不必通過函數(shù)名來查找相關(guān)函數(shù)(這個(gè)查找效率很低赂蕴,函數(shù)名超級(jí)長(zhǎng))。
- 在 Java 層使用 native 關(guān)鍵字描述函數(shù)
public class Utils {
public static native int add(int a, int b);
}
- 在哪里告訴系統(tǒng)我要?jiǎng)討B(tài)注冊(cè)函數(shù)呢舶胀?
我們都知道 java 想要調(diào)用 c 層的代碼概说,主要分 2 步:
System.loadLibrary("so name");
調(diào)用對(duì)應(yīng)的 native 代碼
int result = Utlils.add(1,2);
現(xiàn)在我們需要找到一個(gè)時(shí)機(jī)告訴系統(tǒng),我要?jiǎng)討B(tài)注冊(cè)函數(shù)嚣伐,并且告訴系統(tǒng)怎么動(dòng)態(tài)注冊(cè)糖赔。
而系統(tǒng)在執(zhí)行完 System.loadLibrary("..")之后會(huì)執(zhí)行 native 層的 JNI_Onload 方法,因此我們只需要在該方法完成動(dòng)態(tài)注冊(cè)即可轩端。
- 在 JNI_Onload 函數(shù)動(dòng)態(tài)注冊(cè)
想要進(jìn)行動(dòng)態(tài)注冊(cè)放典,就必須要有一個(gè) java 層函數(shù)和 native 函數(shù)的對(duì)應(yīng)關(guān)系表。
在 jni 中是使用 JNINativeMethod 來保存對(duì)應(yīng)關(guān)系
typedef struct {
char *name;//
char *signature;
void *fnPtr;
} JNINativeMethod;
編寫對(duì)應(yīng)關(guān)系表
method_table 是一個(gè)數(shù)組,它存放就是 JNINativeMethod 定義的三個(gè)屬性參數(shù)奋构。
1. name java 層的方法名
2. signature 方法的簽名
3. fnPtr 對(duì)應(yīng)的 native 的方法名
//下面這段代碼的表示就是將 java 層的 add 方法映射
//到 native 層的 add 方法壳影,不再是靜態(tài)注冊(cè)那種很長(zhǎng)的方法名了。
static JNINativeMethod method_table[] = {
{
"add", "(II)I",(void *) add}
};
有了對(duì)應(yīng)關(guān)系表 method_table 之后弥臼,我們就要開始注冊(cè)了宴咧。下面的 JNI_Onload 方法就是對(duì) method_table 表進(jìn)行動(dòng)態(tài)注冊(cè)。因此只需要實(shí)現(xiàn)這個(gè)方法径缅,在方法內(nèi)部進(jìn)行動(dòng)態(tài)注冊(cè)即可掺栅。在動(dòng)態(tài)加載 so 庫(kù)之后,系統(tǒng)會(huì)調(diào)用 jint 纳猪。
/**
* 動(dòng)態(tài)注冊(cè)
* 在 native 代碼中重寫該 JNI_Onload 方法即可
*/
JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) {
//OnLoad方法是沒有JNIEnv參數(shù)的氧卧,需要通過vm獲取。
JNIEnv *env = NULL;
jint result = -1;
if ((*vm)->GetEnv(vm, (void **) &env, JNI_VERSION_1_4) != JNI_OK) {
return -1;
}
//獲取對(duì)應(yīng)聲明native方法的Java類
jclass clazz = (*env)->FindClass(env, "com/lu/ndkdemo/Utils");
if (clazz == NULL) {
return JNI_FALSE;
}
//注冊(cè)方法兆旬,成功返回正確的JNIVERSION假抄。
if ((*env)->RegisterNatives(env, clazz, method_table,
sizeof(method_table) / sizeof(method_table[0])) == JNI_OK) {
return JNI_VERSION_1_4;
}
return JNI_FALSE;
}
jni 的方法簽名
- Java 層的 native 方法:
private native void processFile(String path, String mimeType, MediaScannerClient client);
格式:(參數(shù)類型...)函數(shù)返回值類型
(Ljava/lang/String;Ljava/lang/String;Landroid/media/MediaScannerClient;)V
- 簽名的相關(guān)解釋
- L 開頭表示該參數(shù)是引用數(shù)據(jù)類型
- 參數(shù)類型使用包/類名表示
- 每一個(gè)引用類型使用;結(jié)束
- 最右邊表示該方法的返回值類型
- V 表示沒有返回值 void類型
如何將 jni 層的函數(shù)和 native 函數(shù)一一對(duì)應(yīng)呢怎栽?
一丽猬、通過JNINativeMethod一一對(duì)應(yīng)
在 jni.h 文件中聲明了一個(gè)叫 JNINativeMethod 的結(jié)構(gòu)體,通過這個(gè)結(jié)構(gòu)體就可以將 native 函數(shù)和 jni 函數(shù)進(jìn)行一一對(duì)應(yīng)了熏瞄。
/*
* used in RegisterNatives to describe native method name, signature,
* and function pointer.*/
typedef struct {
char *name;
char *signature;
void *fnPtr;
} JNINativeMethod;
二脚祟、如何使用 JNINativeMethod?
-
1、定義一個(gè) nativeMethod 數(shù)組
JNINativeMethod nativeMethod[] = { {"processFile", //native函數(shù)名稱 "(//函數(shù)參數(shù)類型 Ljava/lang/String; Ljava/lang/String; Landroid/mediaMediaScannerClient; ) V//函數(shù)返回值類型 ", (void*)android_media_MediaScanner_processFile // jni 層函數(shù)名稱 } };
static JNINativeMethod gMethods[] = { {"_start","()V",(void *)android_media_MediaPlayer_start} ... }
-
2强饮、動(dòng)態(tài)注冊(cè)數(shù)組 registerNativeMethods
static int register_android_media_MediaPlayer(JNIEnv *env){ //向 VM 進(jìn)行注冊(cè) return AndroidRuntime::registerNativeMethods(env, "android/media/MediaPlayer", gMethods, NELEM(gMethods)); } int AndroidRuntime::registerNativeMethods(JNIEnv*env, constchar* className, const JNINativeMethod* gMethods, int numMethods){ //調(diào)用jniRegisterNativeMethods函數(shù)完成注冊(cè) return jniRegisterNativeMethods(env, className, gMethods, numMethods);} int jniRegisterNativeMethods(JNIEnv* env, const char* className,const JNINativeMethod* gMethods, int numMethods){ jclassclazz; clazz= (*env)->FindClass(env, className); ... //實(shí)際上是調(diào)用JNIEnv的RegisterNatives函數(shù)完成注冊(cè)的 //gMethods = sizeof(methods) / sizeof(methods[0])) if((*env)->RegisterNatives(env, clazz, gMethods) numMethods) < 0) { return -1; } return0; }
其實(shí)最終都是調(diào)用 RegisterNatives 方法完成注冊(cè)功能的由桌。
數(shù)據(jù)類型轉(zhuǎn)化
1、基本數(shù)據(jù)類型轉(zhuǎn)化
2邮丰、引用數(shù)據(jù)類型轉(zhuǎn)化
除了Java中基本數(shù)據(jù)類型的數(shù)組行您、Class、String和Throwable外剪廉,其余所有Java對(duì)象的數(shù)據(jù)類型在JNI中都用jobject表示娃循。
processFile(String path, String mimeType,MediaScannerClient client);
android_media_MediaScanner_processFile(JNIEnv*env, jobject thiz,jstring path, jstring mimeType, jobject client)
在 JNI 層的 processFile 的第二參數(shù)的說明:表示的是調(diào)用 processFile 方法的 MediaScanner 對(duì)象,如果該方法是 static 方式的話斗蒋,那么該參數(shù)就是 jclass 類型的捌斧,表示的是調(diào)用哪個(gè)類調(diào)用靜態(tài)函數(shù) processFile 。
JNIEnv 的介紹
在 JNI 層的 processFile 函數(shù)聲明中可以看到第一個(gè)參數(shù)就是 JNIEnv* 類型的泉沾,那么這個(gè)類型表示的是什么意思呢捞蚂?
JNIEnv*是定義任意native函數(shù)的第一個(gè)參數(shù)(包括調(diào)用JNI的RegisterNatives函數(shù)注冊(cè)的函數(shù)),用于訪問JVM數(shù)據(jù)結(jié)構(gòu)的JNI函數(shù)表指針跷究。
通過 JNIEnv 操作 jobject
能通過 JNIEnv 操作 jobject 就實(shí)際上就是通過 JNIEnv 操作 jobject 中的屬性和方法姓迅。
- JNIEnv 操作 jobject 的屬性
- JNIEnv 操作 jobject 的方法
JNI 是如何定義 jobject 的屬性和方法的?
jfieldID GetFieldID(jclass clazz,const char*name, const char *sig);
jmethodID GetMethodID(jclass clazz, const char*name,const char *sig);
//得到j(luò)ava對(duì)象
jclass mediaScannerClientInterface = env->FindClass("android/media/MediaScannerClient");
//找到該對(duì)象的方法id
mScanFileMethodID = env->GetMethodID(mediaScannerClientInterface, "scanFile","(Ljava/lang/String;JJ)V");
// env
//jobject 要操作的java對(duì)象,可以通過 FindClass 獲取
//methodID 表示該方法的id丁存,可以通過 GetMethodID 獲取
NativeType Call<type>Method(JNIEnv *env,jobject obj,jmethodID methodID, ...)色冀。
jboolean CallBooleanMethod(jobject obj,jmethodID methodID, ...)
mEnv->CallBooleanMethod
//得到對(duì)象的屬性值
// env
//jobject 要操作的java對(duì)象,可以通過 FindClass 獲取
//fieldID 表示該屬性的id柱嫌,可以通過 GetFieldID 獲取
jboolean(jni對(duì)應(yīng)的類型) GetBooleanField(Get<type>Field)(jobject obj, jfieldID fieldID) {
return functions->GetBooleanField(this,obj,fieldID);
}
//設(shè)置對(duì)象的屬性值
void Set<type>Field(jobject obj, jfieldID fieldID,jboolean val) {
//const struct JNINativeInterface_ *functions
functions->SetBooleanField(this,obj,fieldID,val);
}
jstring
jstring NewString(const jchar *unicode, jsize len) {
return functions->NewString(this,unicode,len);
}
GetStringChars得到一個(gè)Unicode字符串
const jchar *GetStringChars(jstring str, jboolean *isCopy) {
return functions->GetStringChars(this,str,isCopy);
}
jstring NewStringUTF(const char *utf) {
return functions->NewStringUTF(this,utf);
}
GetStringUTFChars得到一個(gè)UTF-8字符串锋恬。
const char* GetStringUTFChars(jstring str, jboolean *isCopy) {
return functions->GetStringUTFChars(this,str,isCopy);
}
void ReleaseStringChars(jstring str, const jchar *chars) {
functions->ReleaseStringChars(this,str,chars);
}
void ReleaseStringUTFChars(jstring str, const char* chars) {
functions->ReleaseStringUTFChars(this,str,chars);
}
使用 jstring
android_media_MediaScanner_processFile(JNIEnv*env, jobject thiz, jstring path, jstring mimeType, jobject client) {
//調(diào)用JNIEnv的GetStringUTFChars得到本地字符串pathStr
const char *pathStr = env->GetStringUTFChars(path, NULL);
//使用完后,必須調(diào)用ReleaseStringUTFChars釋放資源
env->ReleaseStringUTFChars(path, pathStr);
}
JNI類型簽名
示例
"(Ljava/lang/String;Ljava/lang/String;Landroid/media/MediaScannerClient;)V",
- (參數(shù)1類型標(biāo)示參數(shù)2類型標(biāo)示...參數(shù)n類型標(biāo)示)返回值類型標(biāo)示
- L 表示該參數(shù)是引用類型的编丘;
- 參數(shù)類型使用權(quán)限類名表示与学,不過.需要替換為/
- 在引用類型后面需要使用;結(jié)尾
- 最右邊V表示沒有返回值
工具的使用
雖然函數(shù)簽名信息很容易寫錯(cuò),但Java提供一個(gè)叫javap的工具能幫助生成函數(shù)或變量的簽名信息嘉抓,它的用法如下:
javap –s -p xxx索守。其中xxx為編譯后的class文件,s表示輸出內(nèi)部數(shù)據(jù)類型的簽名信息抑片,p表示打印所有函數(shù)和成員的簽名信息卵佛,而默認(rèn)只會(huì)打印public成員和函數(shù)的簽名信息。
垃圾回收
-
Local Reference
LocalReference最大的特點(diǎn)就是敞斋,一旦JNI層函數(shù)返回截汪,這些jobject就可能被垃圾回收。
-
Global Reference
全局引用植捎,這種對(duì)象如不主動(dòng)釋放衙解,就永遠(yuǎn)不會(huì)被垃圾回收。
WeakRe Global ference
-
變量的內(nèi)存釋放
void DeleteLocalRef(jobject obj) mEnv->DeleteLocalRef(pathStr);
JNI 異常處理
當(dāng)調(diào)用 JNIEnv 的某些函數(shù)出錯(cuò)后焰枢,不會(huì)向 Java 一樣會(huì)終止下一行代碼的執(zhí)行蚓峦,直到從 JNI 層返回到 Java 層后,虛擬機(jī)才會(huì)拋出這個(gè)異常济锄。
出現(xiàn)異常后暑椰,不能再調(diào)用 JNIEnv 的函數(shù)了,不然程序就會(huì)掛掉荐绝。
JNI層函數(shù)可以在代碼中截獲和修改這些異常一汽,JNIEnv提供了三個(gè)函數(shù)進(jìn)行幫助:
ExceptionOccured函數(shù),用來判斷是否發(fā)生異常很泊。
ExceptionClear函數(shù)角虫,用來清理當(dāng)前JNI層中發(fā)生的異常。
ThrowNew函數(shù)委造,用來向Java層拋出異常戳鹅。
在jni層打log日志
- 引入 #include <android/log.h> 頭文件
- 聲明
#define LOG "JNILOG" // 這個(gè)是自定義的LOG的TAG
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,LOG,__VA_ARGS__) // 定義LOGD類型
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG,__VA_ARGS__) // 定義LOGI類型
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN,LOG,__VA_ARGS__) // 定義LOGW類型
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG,__VA_ARGS__) // 定義LOGE類型
#define LOGF(...) __android_log_print(ANDROID_LOG_FATAL,LOG,__VA_ARGS__) // 定義LOGF類型
- 使用
jint i = 3;
LOGE("i = %d\n",i ));
總結(jié)
- JNI 函數(shù)的注冊(cè)
- JNI 和 Java 的類型轉(zhuǎn)換
- JNIEnv 的含義及其作用
- jstring 的使用
- JNI 的簽名
- JNI 的垃圾回收
- JNI 的異常處理
注意事項(xiàng)
在java類中編寫 native 方法。注意這時(shí)方法本身會(huì)報(bào)紅昏兆,只要按 command+enter 就可以直接生成一個(gè)與之對(duì)應(yīng)的 jni 方法枫虏。
package com.lu.ndkdemo2;
/**
* Created by anlu on 2017/11/25.
*/
public class Utils {
public native void call(String name,int age,String[] args);
}
編譯:build->rebuild
找到 /Users/lu/dev/as_code/NDKDemo2/app/build/intermediates/classes/debug
javah -jni com.lu.ndkdemo2.Utils 生成的文件名就是 com_lu_ndkdemo2_Utils.h
javah -o test.h com.lu.ndkdemo2.Utils 生成的文件名就是 test.h
即可生成對(duì)應(yīng)的頭文件
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_lu_ndkdemo2_Utils */
#ifndef _Included_com_lu_ndkdemo2_Utils
#define _Included_com_lu_ndkdemo2_Utils
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_lu_ndkdemo2_Utils
* Method: call
* Signature: (Ljava/lang/String;I[Ljava/lang/String;)V
*/
JNIEXPORT void JNICALL Java_com_lu_ndkdemo2_Utils_call
(JNIEnv *, jobject, jstring, jint, jobjectArray);
#ifdef __cplusplus
}
#endif
#endif
路徑在:/Users/lu/dev/as_code/NDKDemo2/app/build/intermediates/classes/debug
#include <jni.h>
#include <string>
JNIEXPORT void JNICALL
Java_com_lu_ndkdemo2_Utils_call(JNIEnv *env, jobject instance, jstring name_, jint age,
jobjectArray args) {
const char *name = env->GetStringUTFChars(name_, 0);
// TODO
//釋放jstring
env->ReleaseStringUTFChars(name_, name);
}
JNIEXPORT,JNICALL:是系統(tǒng)定義的宏;
JNIEXPORT后面寫函數(shù)的輸出類型隶债;
JNICALL后面寫函數(shù)名腾它,實(shí)測(cè)這兩個(gè)宏可寫可不寫。
No implementation found for int com.lu.ndkdemo.Utils.add(int, int) (tried Java_com_lu_ndkdemo_Utils_add and Java_com_lu_ndkdemo_Utils_add__II)
1.方法沒有使用extern C 表示死讹,會(huì)報(bào)不到異常瞒滴。
2.沒有寫system.loadLibrary("")。
couldn't find "libtest.so"赞警,沒有在 CMakeLists.txt 聲明妓忍。
一個(gè)so可以構(gòu)建多個(gè)源文件
add_library(<name> [STATIC | SHARED | MODULE][EXCLUDE_FROM_ALL] source1 [source2 ...])
name 就是打包的 so 庫(kù)的名稱。
so 庫(kù)一般會(huì)使用 SHARED愧旦。source1世剖、 source2 多個(gè)source直接用空格即可,不可以用,在 CMakeLists.txt 指定多個(gè)子CMakeLists.txt 構(gòu)建多個(gè) so 庫(kù)笤虫。
#set(<variable> <value> [[CACHE <type> <docstring> [FORCE]] | PARENT_SCOPE])
#add_subdirectory(source_dir [binary_dir] [EXCLUDE_FROM_ALL])
如果在c/c++代碼打log日志
異常情況:
11-25 12:30:13.106 3165-3165/com.lu.ndkdemo E/JNILOG: 我是log3
11-25 12:30:13.106 3165-3165/com.lu.ndkdemo E/JNILOG: 執(zhí)行了java層的callExceptionMethod方法
11-25 12:30:13.106 3165-3165/com.lu.ndkdemo E/JNILOG: 沒有異常的情況...
11-25 12:30:13.106 3165-3165/com.lu.ndkdemo E/JNILOG: 沒有異常的情況...
11-25 12:30:13.106 3165-3165/com.lu.ndkdemo E/JNILOG: 沒有異常的情況...
11-25 12:30:13.106 3165-3165/com.lu.ndkdemo E/JNILOG: 沒有異常的情況...
11-25 12:30:13.106 3165-3165/com.lu.ndkdemo E/JNILOG: 沒有異常的情況5...
11-25 12:30:13.107 3165-3165/com.lu.ndkdemo E/zeal: java 層出現(xiàn)異常...JNI層拋出了異常...