5.【干货】火爆全网的《超全NDK精品教程》JNI 异常处理

Exception



为了确保Java、C/C++代码可以正常执行下去,需要:

在JNI层手动清空异常信息(ExceptionClear),保证代码可以运行。

补救措施保证C/C++代码继续运行。

c层判断java层的异常

步骤: 

1.判断是否有异常

2.1  清楚异常

2.2  抛出异常到java

用户可以手动通过ThrowNew函数抛出异常,同样可以被Java代码捕获:

//异常处理

JNIEXPORT void JNICALL Java_com_test_JniTest_testException1

(JNIEnv * env, jobject jobj){

    jclass clz = (*env)->GetObjectClass(env, jobj);

    //属性名字不小心写错了,拿到的是空的jfieldID

    jfieldID fid = (*env)->GetFieldID(env, clz, "key1", "Ljava/lang/String;");

jthrowable err = (*env)->ExceptionOccurred(env);

    if (err != NULL){

        //手动清空异常信息,保证Java代码能够继续执行

(*env)->ExceptionClear(env);

        //提供补救措施,例如获取另外一个属性

        fid = (*env)->GetFieldID(env, clz, "key", "Ljava/lang/String;");

    }

    jstring key = (*env)->GetObjectField(env, jobj, fid);

    char* c_str = (*env)->GetStringUTFChars(env, key, NULL);

}

// 两种方式解决

// 1. 补救措施, 不拿 name3 拿 name

// 1.1 有没有异常

      jthrowable throwable = (*env)->ExceptionOccurred(env);

/*if (throwable){

// 补救措施,先把异常清除

printf("native have a exception");

// 清除异常

(*env)->ExceptionClear(env);

// 重新获取 name 属性

jfid = (*env)->GetStaticFieldID(env, j_clz, "name", "Ljava/lang/String;");

}*/

// 2. 想给 java 层抛一个异常

      if (throwable){

// 清除异常

          (*env)->ExceptionClear(env);

          // 给 java 层抛 一个 Throwable 异常

// 第一种方式,直接把异常抛给 java 层

          (*env)->Throw(env, throwable);

          // 第二种方式抛异常

// (*env)->ThrowNew(env, no_such_clz, "NoSuchFieldException name3");

          return; //  必须 return 如果不的话,程序会接着往下运行,肯定还会crash

      }

jstring name = (*env)->NewStringUTF(env, "Eastrise");

      (*env)->SetStaticObjectField(env, j_clz, jfid, name);

  }

API: 异常处理

jthrowable throwable = (*env)->ExceptionOccurred(env);:正在抛出一个异常的本地引用

(*env)->ExceptionClear(env);:清除异常

(*env)->Throw(env, throwable);:将ExceptionOccurred获取到的异常直接抛给java层

(*env)->ThrowNew(env, no_such_clz, "NoSuchFieldException name3");:抛出自己想抛出的异常

————————————————

非常好:JNI Crash:异常定位与捕获处理

https://www.jianshu.com/p/b6129f110e86

https://juejin.cn/post/7041062858917937165

https://blog.csdn.net/ddxxii/article/details/84781110

java---jni异常处理

异常处理,异常捕获:jni

https://blog.csdn.net/u013718120/article/details/65629074

http://www.droidsec.cn/常见android-native崩溃及错误原因/

总结:

1.c本身代码,通过try catch

2.c调用java 。通过异常检测    ExceptionCheck  。这样就不会奔溃,同时还可以打印异常的信息。

异常处理总结

JNI自己抛出的异常,是Error类型,Java可以通过Throwable或者Error来捕获得到,捕获异常后Java代码可以继续执行下去。在C层可以清空(ExceptionClear),保证try中的代码Java代码继续执行,并且最好要提供补救措施,确保JNI层代码正常继续运行。

用户通过ThrowNew手动抛出的异常,同样可以在Java层捕捉得到。

c层出问题了,java try catch不住的

C++本身的异常处理:

程序运行时常会碰到一些异常情况,例如:

做除法的时候除数为 0;

用户输入年龄时输入了一个负数;

用 new 运算符动态分配空间时,空间不够导致无法分配;

访问数组元素时,下标越界;打开文件读取时,文件不存在。

能够捕获任何异常的 catch 语句

如果希望不论拋出哪种类型的异常都能捕获,可以编写如下 catch 块:

catch(...) {

...

}

实战举例:

try {

in.substract_mean_normalize(mean_vals, norm_vals);

}catch (...) {

return -1;

  }

C++:异常捕获;

http://c.biancheng.net/view/422.html

c++常见的错误,和如何处理?

一. 空指针:

说明:

指针引用不能访问地址

指针为空

空指针是很容易出现的一种bug,但是它也很容易被发现和修复。比如以下代码就会报空指针:

操作不能访问地址:

/**

 * 空指针

 */

void crashNull() {

    //0为其实地址,是不可读写的,会立即崩溃

    int *p = 0;

    *p = 1;

    LOGE("p:%d", *p);

}

解决方案:使用前加非空判断

//判断不为空再进行执行

if (!*p == NULL) {

        *p = 1;

        LOGE("p:%d", *p);

    }

二. 数组越界

说明:

数组越界和野指针有点像,访问了其他地址,如果访问地址是不可读写的,那么立马会Crash(内核给进程发送错误信号SIGSEGV),如果是可读写的地址,修改了该地址内存,造成内存破坏,那么有可能等会在别的地方发生Crash。

解决方案:

所有数组遍历的循环,都要加上越界判断

用下标访问数组的时候,要判断是否越界

通过代码分析工具可以发现绝大部分数组越界问题

 int data[10];

    //越界赋值,先判断下size

    int sizeData = sizeof(data);

    for (int i = 0; i < 17; ++i) {

        if (sizeData <= 17) {

            data[i] = 666;

        }

    }

    LOGE("data[11]:%d", data[11]);

六. 格式化参数错误

需要格式化参数类型的时候,有可能出现错误

比如

int b=0;

    LOGE("b:%s", b);

解决方案

在书写输出格式和参数时,要做到参数个数和类型都要与输出格式一致。

在GCC的编译选项中加入-wformat,让GCC在编译时检测出此类错误。

六. 除以0

分母为0的这种情况,会很快Crash,一般都是在实际运行环境中还有可能出现,所以编码习惯的时候应该尽量习惯性去判断下

示例代码

/**

 * 除以0

 */

void zeroDiv() {

    int a = 1;

    int b = a / 0; //整数除以0,产生SIGFPE信号,导致Crash

    LOGE("b:%d", b);

}

解决方案

在有除法的时候,判断下分母为0的情况

三. 野指针:

说明:指针指向无效地址:

如果该地址是不可读不可写,那么立马会遇到crash(内核给进程发送错误信息SIGSEGV)

如果该指针的地址可写,那么可能等一会才会出现崩溃(其他指针修改了这一块地址),这时候查看调用栈和野指针所在代码部分可能根本没有关联。

/**

 * 野指针

 */

void wildPointer() {

    //开始没有初始化

    int *p;

    //中途使用

    *p = 1;

}

这种情况不会立马crash,但是是很不安全的,说不定在之后某一时刻就崩溃了

解决方案:

指针变量一定要初始化,特别是结构体或类中的成员变量的指针

使用完后,在不用的情况,尽量执为NULL(如果别的地方也有指针指向这段内存就不好解决)

例如:

int o;

    //初始化,指向给o的地址

    int *p = &o;

    //中途使用赋值,就是给o赋值了

    *p = 1;

    LOGE("o:%d", *p);

    //用完后指向NULL

    *p = NULL;

注意: 野指针造成的内存破坏,很难发现,一般需要专业内存检测工具,才能发现这一类的bug

Bug评述

野指针的bug,特别是内存破坏的问题,有时候查起来毫无头绪,没有一点线索,让开发者感觉到很茫然和无助( Bugly上报的堆栈看不出任何问题)。可以说内存破坏bug是服务器稳定性最大的杀手,也是C/C++在开发应用方面相比于其它语言(如Java, C#)的最大劣势之一。

四. 内存泄漏

说明:

c与c++没有像java那样自动回收的GC机制,所以使用完需要手动释放,如果没有释放,就造成内存泄漏。

比如:

jstring str = env->NewStringUTF("哈哈哈哈");

    if (str == NULL) {

        LOGE("str is null");

        return;

    }

    const char *stringChar = env->GetStringUTFChars(str, 0);

    LOGE("str:%s", stringChar);

解决方案:

用完后进行释放

例如:

jstring str = env->NewStringUTF("哈哈哈哈");

    if (str == NULL) {

        LOGE("str is null");

        return;

    }

    const char *stringChar = env->GetStringUTFChars(str, 0);

    LOGE("str:%s", stringChar);

    //用完后进行释放

    env->DeleteLocalRef(str);

    env->ReleaseStringUTFChars(str,stringChar);

五. 堆栈溢出

说明:

与java的堆栈溢出异常一样,jni中一样有堆栈溢出异常,比如超过堆大小,或超过栈深度时候爆出。

解决方案:

对于堆溢出,在做好释放操作,只要做好避免内存泄漏,合理分配内存使用,一般不会出,现在堆大小还是挺大的

对于栈溢出,做递归的时候重复调用方法的时候,考虑好栈的深度,适时的做好切换操作,避免栈溢出

C++内存溢出和内存泄漏?

1、内存溢出

      内存溢出是指程序在申请内存时没有足够的内存空间供其使用。原因可能如下:

         (1)内存中加载的数据过于庞大;

         (2)代码中存在死循环;

         (3)递归调用太深,导致堆栈溢出等;

         (4)内存泄漏最终导致内存溢出;

2、内存泄漏

    内存泄漏是指使用new申请内存, 但是使用完后没有使用delete释放内存,导致占用了有效内存。

1.解决java.lang.StackOverflowError: stack size 8MB报错问题:

自己调用,内存异常

递归:循环调用的问题

超过了8M

export NDK_HOME=/Users/mac/Library/Android/sdk/ndk/16.1.4479499/ndk-build

export PATH=$PATH:$NDK_HOME/

/Users/mac/Nokia-AI-sport/nokia-ai/moduleHealth/jni

/Users/mac/Downloads/android-ndk-r16b

2.理解StackOverflowError与OutOfMemoryError

1.递归

2.循环依赖

3.如果这里的对象属性比较多的时候,这种循环调用之后会导致该线程的堆栈异常,最终导致StackOverflowError异常;如何避免这种情况发生呢?可以克隆或者新建对象:

 //避免循环调用出现StackOverflowError异常,这里用临时对象studentTemp

https://www.jianshu.com/p/e4e224b87aa3

http://www.imooc.com/article/details/id/291031(非常好)

在Eclipse中JDK的配置中加上 -XX:MaxDirectMemorySize=128 这代码,就行了,默认是64M

如果你确认递归实现没有问题,你可以通过-Xss参数增加栈的大小,这个参数可以在项目配置或命令行指定。

发现不行:

{

try{

__android_log_print(ANDROID_LOG_DEBUG, "NDK-peng", "*****222*error*******error****************************",2);

  int *p=NULL;

    *p=1;

}catch(Exception){

__android_log_print(ANDROID_LOG_DEBUG, "NDK-peng", "**111111****error*******error****************************",2);

      return 9999;

}

不是说奔溃了,还会执行c吗?我发现没有

也不能检查异常:异常检查是对于java

__android_log_print(ANDROID_LOG_DEBUG, "NDK-peng", "*****222*error*******error****************************",2);

int *p=NULL;

  __android_log_print(ANDROID_LOG_DEBUG, "NDK-peng", "*****444444444***************************",2);

*p=1;

  if(env->ExceptionCheck()) {

__android_log_print(ANDROID_LOG_DEBUG, "NDK-peng", "*****33333333333*error*******error****************************",2);

        env->ExceptionDescribe(); // writes to logcat

        env->ExceptionClear();

        // return 9999999;

}else{

__android_log_print(ANDROID_LOG_DEBUG, "NDK-peng", "正常",2);

// return 555;

}

发现者2个异常不一样,指针如果异常,不能检查到。如果是普通空指针可以

char* a = NULL;

int val1 = a[1] -'0';

LOGE("JNI, process code %d, cnt %d", m, n);

int *p=NULL;

*p=1;

这个会奔溃

int a=1;

int b=0;

int yyyy=a/b;

这个不会奔溃

int a=1;

int b=0;

int yyyy=a/0;

自己些了一个demo,这个不奔溃,但是catch没有效果

#include

#include

extern "C" JNIEXPORT jstring JNICALL

Java_sport_yuedong_com_myapplication_MainActivity_stringFromJNI(

JNIEnv *env,

        jobject/* this */) {

std::string hello ="333333";

    int a =1;

    int b =0;

    int yyyy = a / b;

    try {

}catch (...) {

}

return env->NewStringUTF(hello.c_str());

}

异常检查也是没有效果的

extern "C" JNIEXPORT jstring JNICALL

Java_sport_yuedong_com_myapplication_MainActivity_stringFromJNI(

JNIEnv *env,

        jobject/* this */) {

std::string hello ="333333";

    try {

}catch (...) {

}

char* a = NULL;

    int val1 = a[1] -'0';

    if(env->ExceptionCheck()) {

env->ExceptionDescribe(); // writes to logcat

        env->ExceptionClear();

    }

demo:

extern "C"

JNIEXPORT jstring JNICALL

Java_sport_yuedong_com_myapplication_MyException_stringFromJNI(JNIEnv *env, jobject jobjectOther) {

std::string hello ="22";

    std::string error ="error";

    jclass jclass = env->FindClass("sport/yuedong/com/myapplication/MyException");

    jmethodID mid = env->GetMethodID(jclass, "operation", "()I");

    jmethodID mid2 = env->GetMethodID(jclass, "<init>", "()V");

    jobject jobject1 = env->NewObject(jclass, mid2);

    env->CallIntMethod(jobject1, mid);

    jthrowable jthrowable1 = env->ExceptionOccurred();

    //c调用java的处理,可以做异常检查

    if (jthrowable1) {

env->ExceptionDescribe();

        env->ExceptionClear();

        return env->NewStringUTF(error.c_str());

    }

return env->NewStringUTF(hello.c_str());

}

异常检查:异常检查只有c调用java的时候才有用。 异常检查对c来说没有效果。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,163评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,301评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,089评论 0 352
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,093评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,110评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,079评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,005评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,840评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,278评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,497评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,667评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,394评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,980评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,628评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,649评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,548评论 2 352

推荐阅读更多精彩内容