一般native代码导致的崩溃问题,奔溃日志提示大概类似这样:
Fatal signal 11 (SIGSEGV), code 1, fault addr 0x0 in tid 13261
只有这样而没有详细的调用栈信息,这样我们开发者无法定位到JNI中到底哪一行导致程序崩掉的。根本无法定位问题所在,就更不用说解决问题了。
好在NDK给开发者们提供了ndk-stack工具(在NDK根目录下),我们可以通过ndk-stack工具来查看so库中崩溃的堆栈信息。
如果是使用命令行编译,则使用如下语句:
ndk-build clean all NDK_DEBUG=1
clean all 的意思是编译之前先清理全部上次编译生成的内容。NDK_DEBUG=1 意思是生成调试版本的文件。加了这个参数后 调试的时候能定位到源码行数。
如果是使用gradle,则写法如下(注意这里已经覆盖了gradle默认的NDK编译,详细请前往 《Android Studio覆盖了gradle默认的NDK编译》 ):
task ndkBuild(type: Exec) { commandLine 'ndk-build', '-C', file('src/main/jni').absolutePath, 'clean','all', 'NDK_DEBUG=1' } tasks.withType(JavaCompile) { compileTask -> compileTask.dependsOn ndkBuild }
最后记得在AndroidManifest.xml设置debuggable为true ,在Application节点中。
前提是要搭建好NDK开发环境并在项目中集成NDK,不会的可以参考 Ubuntu下NDK编译环境搭建及在Android Studio中集成NDK 。
为了演示,我这里先模拟一个错误:
JNIEXPORT jstring JNICALL Java_com_liuling_ndkjnidemo_JniUtils_getStringFromC (JNIEnv *env, jclass obj) { int * p = NULL; *p = 1; //这里会导致程序崩溃 return (jstring)(*env)-> NewStringUTF(env, "I am string from jni"); }
在命令行中执行如下命令:
adb logcat | 你的NDK所在的路径/ndk-stack -sym 你的项目所在的路径/app/src/main/obj/local/armeabi
这里要确定,ndk编译后生成了”你的项目所在的路径/app/src/main/obj/local/armeabi”目录,也就是这个目录要存在。
执行完这个命令之后,终端会阻塞在那,一旦程序崩溃,就会在终端打印出崩溃信息栈。如图所示:
从崩溃信息可以看出导致崩溃的代码是在com_liuling_ndkjnidemo_JniUtils.c中的13行。
打开com_liuling_ndkjnidemo_JniUtils.c文件查看代码,确实是在13行出的问题。
能够定位崩溃所在的位置,就对于我们排查问题来说有很大的帮助,其实修复bug大部分时间都是在找哪里出的问题,能够快速找出哪里出的问题,问题也就很快修复了。