在上一篇文章中,咱們已經瞭解瞭如何進行Java和Native的交互,本文將介紹在JNI中如何進行Java異常處理。java
在Java中咱們能看到一些類中聲明的native函數會拋出異常,好比:android
java.io.FileOutputStream
類中的:git
private native void open0(String name, boolean append)
throws FileNotFoundException;
複製代碼
android.hardware.Camera
類中的:github
public native final void setPreviewSurface(Surface surface) throws IOException;
public native final void setPreviewTexture(SurfaceTexture surfaceTexture) throws IOException;
複製代碼
這些函數的實現都在native層,可是其聲明卻包含了可能會出現的異常,那就說明native實際上是能夠向Java拋異常的;
JNI也能夠進行異常的捕獲,咱們也來看一下如何在native進行異常的處理。windows
函數介紹bash
JNI爲咱們提供了一些函數進行異常的處理,以下:app
// CATCH
// 若是有異常出現,返回值就是異常,若是沒有異常,回傳NULL
jthrowable ExceptionOccurred()
// 內部會調用異常對應的Java類的printStackTrace()函數
void ExceptionDescribe()
// 清除異常,清除異常後能夠繼續進行JNI操做
void ExceptionClear()
// 當前有沒發生異常,若發生了,回傳JNI_TRUE,不然回傳JNI_FALSE
jboolean ExceptionCheck()
// THROW
// 向Java拋出一個異常
jint Throw(jthrowable obj)
// 選擇異常的類型,填寫異常的信息進行拋出
jint ThrowNew(jclass clazz, const char* message)
複製代碼
捕獲異常ide
舉個例子:函數
jmethodID toURLID = env->GetMethodID(fileClazz,"toURL","()Ljava/net/URL;");
jobject toURLValue = env->CallObjectMethod(fileObj, toURLID);
// this exception may occur:
// java.net.MalformedURLException
jthrowable error = env->ExceptionOccurred();
if (error != NULL) {
// 出現了異常
}else{
// 沒出現異常
}
複製代碼
拋出異常工具
須要注意的是,這裏拋出的是Throwable
,也就是說,不只能夠拋出Exception
,還能拋出Error
。 使用方法以下:
public class CustomException extends Exception {
CustomException() {
super();
}
CustomException(String message) {
super(message);
}
}
複製代碼
public native String testExceptionNotCrash(int i) throws CustomException;
複製代碼
extern "C" JNIEXPORT jstring
JNICALL
Java_com_wsy_jnidemo_MainActivity_testExceptionNotCrash(
JNIEnv *env,
jobject /* this */, jint i) {
jstring hello = env->NewStringUTF("hello world");
if (i > 100) {
jclass exceptionCls = env->FindClass("com/wsy/jnidemo/CustomException");
env->ThrowNew(exceptionCls, "i must <= 100");
env->DeleteLocalRef(exceptionCls);
}
// 若出現異常,則env已不可以使用一些方法,例如建立String將會crash,
// 文檔說明:https://developer.android.google.cn/training/articles/perf-jni#exceptions_1
return hello;
}
複製代碼
這裏雖然沒出現crash,可是Java層也是沒法接收到hello
這個String對象的
注意事項
須要注意的是,在exception發生時,在native層已不能調用大部分的方法,例如對象建立。
NDK官網說明以下:
You must not call most JNI functions while an exception is pending. Your code is expected to notice the exception (via the function's return value,ExceptionCheck, or ExceptionOccurred) and return, or clear the exception and handle it. The only JNI functions that you are allowed to call while an exception is pending are:
- DeleteGlobalRef
- DeleteLocalRef
- DeleteWeakGlobalRef
- ExceptionCheck
- ExceptionClear
- ExceptionDescribe
- ExceptionOccurred
- MonitorExit
- PopLocalFrame
- PushLocalFrame
- ReleaseArrayElements
- ReleasePrimitiveArrayCritical
- ReleaseStringChars
- ReleaseStringCritical
- ReleaseStringUTFChars
例如在拋出exception後執行
env->NewStringUTF("hello world, after exception");
複製代碼
將致使crash,就像這樣。
可是咱們剛纔說到,當異常發生時,執行ExceptionDescribe
會調用異常對應的Java類的printStackTrace()
函數,這又是怎麼作到的?明明已經出現了異常,爲什麼還能進行和Java交互? 讓咱們看一看源碼:
art/runtime/jni_internal.cc
static void ExceptionDescribe(JNIEnv* env) {
ScopedObjectAccess soa(env);
// If we have no exception to describe, pass through.
if (!soa.Self()->GetException()) {
return;
}
StackHandleScope<1> hs(soa.Self());
Handle<mirror::Throwable> old_exception(
hs.NewHandle<mirror::Throwable>(soa.Self()->GetException()));
soa.Self()->ClearException();
ScopedLocalRef<jthrowable> exception(env,
soa.AddLocalReference<jthrowable>(old_exception.Get()));
ScopedLocalRef<jclass> exception_class(env, env->GetObjectClass(exception.get()));
jmethodID mid = env->GetMethodID(exception_class.get(), "printStackTrace", "()V");
if (mid == nullptr) {
LOG(WARNING) << "JNI WARNING: no printStackTrace()V in "
<< mirror::Object::PrettyTypeOf(old_exception.Get());
} else {
env->CallVoidMethod(exception.get(), mid);
if (soa.Self()->IsExceptionPending()) {
LOG(WARNING) << "JNI WARNING: " << mirror::Object::PrettyTypeOf(soa.Self()->GetException())
<< " thrown while calling printStackTrace";
soa.Self()->ClearException();
}
}
soa.Self()->SetException(old_exception.Get());
}
複製代碼
這裏有個操做:
printStackTrace()
函數(調用過程當中出了異常仍是會clear,穩)哪怕JNI提供了這麼些函數,但仍是crash了咋辦?
NDK中有一個很方便的debug工具:addr2line
,可直接根據指令地址定位代碼crash位置。 addr2line
的位置在:{NDK安裝目錄}\toolchains\{平臺相關目錄}\prebuilt\windows-x86_64\bin
目錄。 咱們先在native層寫個錯誤示範代碼,再運行,能夠看到日誌中出現了crash信息:
/lib/x86/libnative-lib.so
以及指令地址爲
00000E61
,動態庫文件在工程目錄的位置以下圖:
addr2line
工具進行crash位置查找,用法爲:
addr2line -e so文件路徑 指令地址
複製代碼
如
addr2line -e D:\android-project\study\JNIDemo\app\build\intermediates\cmake\debug\obj\x86\libnative-lib.so 00000e61
複製代碼
運行結果以下
能夠看到定位到的致使crash的代碼是native-lib.cpp
文件的33行: