5.NDK Android jni开发 异常处理 native奔溃解决(相机图片美化)

 
程序运行时常会碰到一些异常情况,例如:
  • 做除法的时候除数为 0;
  • 用户输入年龄时输入了一个负数;
  • 用 new 运算符动态分配空间时,空间不够导致无法分配;
  • 访问数组元素时,下标越界;打开文件读取时,文件不存在。
 

NDK异常信息一般有三个要素:

  1. 信号
  2. 调用栈信息
  3. 寄存器信息
  • 错误信号:11是信号量sigNum,SIGSEGV是信号的名字,SEGV_MAPERR是SIGSEGV下的一种类型。
  • 寄存器快照:进程收到错误信号时保存下来的寄存器快照,其中PC寄存器存储的就是下个要运行的指令(出错的位置)。
  • 调用栈:#00是栈顶,#02是栈底,#02调用#01调用#00方法,#00的方法时libspirit.so中的Spirit类下的testCrash方法,出错的地方是testCrash方法内汇编偏移17(不是行号哦!)

 

通常的来源有三个:
1、硬件发生异常,即硬件(通常是CPU)检测到一个错误条件并通知Linux内核,内核处理该异常,给相应的进程发送信号。硬件异常的例子包括执行一条异常的机器语言指令,诸如,被0除,或者引用了无法访问的内存区域。大部分信号如果没有被进程处理,默认的操作就是杀死进程。在本文中,SIGSEGV(段错误),SIGBUS(内存访问错误),SIGFPE(算数异常)属于这种信号。

2、进程调用的库发现错误,给自己发送中止信号,默认情况下,该信号会终止进程。在本文中,SIGABRT(中止进程)属于这种信号。

3、用户(手贱)或第三方App(恶意)通过kill-信号 pid的方式给错误进程发送,这时signal中的si_code会小于0。

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

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

catch(...) {
    ...
}

实战举例:
   try {
   in.substract_mean_normalize(mean_vals, norm_vals);

} catch (...) {
       return -1;
   }
 
C++:异常捕获;
c++常见的错误,和如何处理?

一. 空指针:

说明:

  1. 指针引用不能访问地址
  2. 指针为空

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

  • 操作不能访问地址:
/**
 * 空指针
 */
void crashNull() {
    //0为其实地址,是不可读写的,会立即崩溃
    int *p = 0;
    *p = 1;
    LOGE("p:%d", *p);
}
 

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

//判断不为空再进行执行
if (!*p == NULL) {
        *p = 1;
        LOGE("p:%d", *p);
    }
 

二. 野指针:

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

  1. 如果该地址是不可读不可写,那么立马会遇到crash(内核给进程发送错误信息SIGSEGV)
  2. 如果该指针的地址可写,那么可能等一会才会出现崩溃(其他指针修改了这一块地址),这时候查看调用栈和野指针所在代码部分可能根本没有关联。
/**
 * 野指针
 */
void wildPointer() {
    //开始没有初始化
    int *p;
    //中途使用
    *p = 1;
}
这种情况不会立马crash,但是是很不安全的,说不定在之后某一时刻就崩溃了
 

解决方案:

  1. 指针变量一定要初始化,特别是结构体或类中的成员变量的指针
  2. 使用完后,在不用的情况,尽量执为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#)的最大劣势之一。

三. 数组越界
说明:
数组越界和野指针有点像,访问了其他地址,如果访问地址是不可读写的,那么立马会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]);
 

四. 内存泄漏

说明:

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);
 

解决方案:

  1. 用完后进行释放

例如:

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中一样有堆栈溢出异常,比如超过堆大小,或超过栈深度时候爆出。
解决方案:
对于堆溢出,在做好释放操作,只要做好避免内存泄漏,合理分配内存使用,一般不会出,现在堆大小还是挺大的
对于栈溢出,做递归的时候重复调用方法的时候,考虑好栈的深度,适时的做好切换操作,避免栈溢出
 

1、内存溢出

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

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

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

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

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

2、内存泄漏

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

六. 格式化参数错误

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

比如

int b=0;
    LOGE("b:%s", b);
解决方案
  1. 在书写输出格式和参数时,要做到参数个数和类型都要与输出格式一致。
  2. 在GCC的编译选项中加入-wformat,让GCC在编译时检测出此类错误。
 

六. 除以0

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

示例代码

/**

 * 除以0

 */

void zeroDiv() {

    int a = 1;

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

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

}

 

解决方案

  1. 在有除法的时候,判断下分母为0的情况
实战:
jni异常捕获:(ok)
非常好:JNI Crash:异常定位与捕获处理
 
发现不行:
{

 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 <jni.h>
#include <string>

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来说没有效果。
=============================================
java---jni异常处理
异常处理,异常捕获:jni
 
1.数据源修改
2.释放掉数据
可能问题:内存,因为不是必先的。在低端手机和一些手机内存回收不是很好的
===================
工具函数

JNI中抛异常很经典:找异常类,调用ThrowNew抛出之;所以,可以写一个工具函数。


 
 

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

自己调用,内存异常
递归:循环调用的问题
超过了8M
<span style="color:#000000"><span style="color:#cccccc"><code class="language-bash">export NDK_HOME=/Users/mac/Library/Android/sdk/ndk/16.1.4479499/ndk-build
export PATH=$PATH:$NDK_HOME/</code></span></span>
/Users/mac/Nokia-AI-sport/nokia-ai/moduleHealth/jni
/Users/mac/Downloads/android-ndk-r16b

2.理解StackOverflowError与OutOfMemoryError

1.递归
2.循环依赖
3.如果这里的对象属性比较多的时候,这种循环调用之后会导致该线程的堆栈异常,最终导致StackOverflowError异常;如何避免这种情况发生呢?可以克隆或者新建对象:
     //避免循环调用出现StackOverflowError异常,这里用临时对象studentTemp
在Eclipse中JDK的配置中加上 -XX:MaxDirectMemorySize=128 这代码,就行了,默认是64M
  • 如果你确认递归实现没有问题,你可以通过-Xss参数增加栈的大小,这个参数可以在项目配置或命令行指定。
3.NDK问题 ,so库类型不对奔溃

ndk {
    abiFilters "armeabi"
}
4.奔溃,因为找不到方法或者so库奔溃,方法找不到原因:要包名一样才行的
 java.lang.UnsatisfiedLinkError: No implementation found for double
6.NDK版本不对,导致奔溃问题

完美解决 No toolchains found in the NDK toolchains folder for ABI with prefix: mips64el-linux-android

7..地址配置有问题:拼接
Unable to resolve host "open-api1.51yund.comauth": No address associated with hostname
==========================
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章