公司從人人網(wǎng)接收了經(jīng)緯名片通,開始進行后續(xù)開發(fā)。
該項目安卓的名片本地識別模塊是調(diào)JNI的,最底層識別圖片的部分暫時沒有源代碼。糟糕的是,該模塊在部分機型上出現(xiàn)閃退,并報錯:Fatal signal 11 (SIGSEGV) at .... (code=1)
谷歌翻了十幾頁,都是講怎么定位錯誤,怎么修改錯誤??墒堑讓拥拇a我沒有啊,就算定位了也改不了。而且項目等著上線,就算后續(xù)要到源碼也來不及了。
所以,我要做的只是讓C把錯誤拋給Java,別讓程序閃退即可。
這里,我們需要用到C的兩組庫函數(shù):signal、setjmp/longjmp
信號是程序處理中的異常情況,信號產(chǎn)生時,系統(tǒng)會進行一些默認的操作。通過signal函數(shù),可以把這些默認操作更改為自己定義的操作。
signal函數(shù)的頭文件:
#include <iostream>
#include <csignal>
{
"data": {
"cardId": 72193540
},
"message": "sucess",
"status": 0
}
signal函數(shù)定義:
static __inline__ __sighandler_t signal(int s, __sighandler_t f)
signal函數(shù)調(diào)用:
void handler(int sig){...}
signal(SIGSEGV, handler);
其中,SIGSEGV就是錯誤里面提示的那個信號,在signal.h的頭文件里,它被定義為11。是不是有豁然開朗的感覺?_
我們需要在可能出錯的函數(shù)里寫上:signal(SIGSEGV, handler)
一旦系統(tǒng)接收到SIGSEGV,就會自動執(zhí)行handler函數(shù)。
handler函數(shù)是自定義的,它的參數(shù)就是signal函數(shù)的一個參數(shù)。我們來完善一下它。
void handler(int sig) {
//解除綁定到信號上的方法
signal(sig, SIG_DFL);
throwJNIException(penv);
}
JNIEXPORT void throwJNIException(JNIEnv* pEnv) {
//注意JNIException的路徑
jclass lClass = pEnv->FindClass("com/jingwei/ocrs/JNIException");
if (lClass != NULL) {
pEnv->ThrowNew(lClass, "Throw JNIException");
//如果我們長時間不再需要引用這個異常類時,可以使用DeleteLocalRef()來解除它。
pEnv->DeleteLocalRef(lClass);
}
}
handler函數(shù)的第一行,是將相應信號的操作重新改為系統(tǒng)默認的操作。
SIG_DFL代表執(zhí)行系統(tǒng)默認操作,類似地,SIG_ING 代表忽略信號。
penv是出錯的方法的環(huán)境,在文末總結(jié)的時候可以看到。
throwJNIException函數(shù)是把錯誤拋給JAVA,本例中需要新建一個JAVA的Exception類JNIException,這里不再貼代碼。
C語言不像JAVA,JAVA底層拋出錯誤后就可以不管了,而C語言需要函數(shù)正常返回。這時候我們就需要setjmp/longjmp函數(shù)。這兩個函數(shù)有點像goto一樣,可以互相跳轉(zhuǎn)。
setjmp/longjmp函數(shù)的頭文件:
#include <setjmp.h>
setjmp/longjmp函數(shù)定義:
int setjmp(jmp_buf);
void longjmp(jmp_buf, int);
來看一個從維基百科抄來的經(jīng)典例子:
#include <stdio.h>
#include <setjmp.h>
static jmp_buf buf;
void second(void) {
printf("second\n"); // 打印
longjmp(buf,1); // 跳回setjmp的調(diào)用處 - 使得setjmp返回值為1
}
void first(void) {
second();
printf("first\n"); // 不可能執(zhí)行到此行
}
int main() {
if (!setjmp(buf)) {
first(); // 進入此行前,setjmp返回0
} else {
// 當longjmp跳轉(zhuǎn)回,setjmp返回1,因此進入此行
printf("main\n"); // 打印
}
return 0;
}
打印結(jié)果:
second
main
main函數(shù)先執(zhí)行到if語句,setjmp將程序的調(diào)用環(huán)境存儲在緩沖區(qū)buf中。setjmp如果是首次調(diào)用會返回0,于是程序進入first函數(shù)。
從first函數(shù)進入second函數(shù)并遇到longjmp后,程序會返回main函數(shù)的if語句。此時,setjmp的返回值會變成longjmp的第二個參數(shù),于是打印"main"后結(jié)束。
所以最終,程序看起來應該是這個樣子:
#include <iostream>
#include <csignal>
#include <setjmp.h>
static jmp_buf jumpflg;
JNIEnv* penv = NULL;
...
void handler(int sig) {
//解除綁定到信號上的方法
signal(sig, SIG_DFL);
throwJNIException(penv);
longjmp (jumpflg, 1);
}
JNIEXPORT void throwJNIException(JNIEnv* pEnv) {
//注意JNIException的路徑
jclass lClass = pEnv->FindClass("com/jingwei/ocrs/JNIException");
if (lClass != NULL) {
pEnv->ThrowNew(lClass, "Throw JNIException");
//如果我們長時間不再需要引用這個異常類時,可以使用DeleteLocalRef()來解除它。
pEnv->DeleteLocalRef(lClass);
}
}
JNIEXPORT jint JNICALL Java_com_jingwei_ocrs_OCRHandler(JNIEnv* env, jobject thiz) {
signal(SIGSEGV, handler);
penv = env;
if (!setjmp(jumpflg)) {
//可能有錯誤的語句寫在這里
return 1;
} else {
return 0;
}
}
至此,外層JAVA即可捕捉JNIException。不過調(diào)試中發(fā)現(xiàn),部分機型會拋出StackOverflowError,具體原因不明,所以外層不得不在捕捉JNIException的同時,也捕捉StackOverflowError。