在Android下面开发,免不了要涉及到C/C++层的开发,这就会涉及到崩溃异常的处理问题。
随着程序的不断升级,更新,会出现多个版本的动态库同时在线上共存的问题,一旦出现崩溃日志,往往不能方便的知道到底是哪个版本出现的崩溃。
传统的Linux,可以通过编译时候指定版本号来处理,最后生成如“libc.so.6”这种形式的文件名,我们可以根据文件名来获得相应的版本信息。遗憾的是,这种命名方式,在Android上面是不支持的。
目前的需求主要有三点:
-
不依赖文件名,查询到版本号
版本号写在文件名中,是一个比较方便的方式,但是带来的问题却是,靠不住!文件名可以随意更改,往往传来传去,文件名中的信息就改得面目全非了。
-
logcat的崩溃日志中能得到版本号
系统自带的logcat会在进程崩溃的时候,打印出崩溃栈,但是信息非常的精简,只有当前的线程回退栈帧信息(backtrace)以及几个关键寄存器信息,往往不能准确提供有足够的信息。而如果想在客户的设备上面,拿到完整的core-dump信息,只能是呵呵一下了!
-
C/C++层的版本号变动,不要改动JAVA层的代码
一方面,如果我们把版本号写在动态库的名字里面,这样会造成每次动态库的升级,JAVA的调用代码都需要变动,小团队还好,大团队,完全不可想象。另一方面,如果是系统框架层的开发,更加悲催,一个文件名的改动,影响到一片,当然,软链接也是个不错的选择,但是这样,常用的APK开发又要折腾一番,免不了一堆的抱怨。
针对上面的需求,我们逐个来提供解决方案:
-
不依赖文件名,查询到版本号
在任意的C/C++文件中增加如下代码
1 |
const static __attribute__((unused,section(".SO_VERSION"))) char Version[] = VERSION; |
在Android.mk文件中增加
1 |
LOCAL_CFLAGS += -DVERSION='"1.0.3"' |
编译生成的".so"文件使用如下命令即可查询版本号信息:
1 2 3 4 |
$readelf --string-dump=".SO_VERSION" libSo.so String dump of section '.SO_VERSION': [ 0] 1.0.3 |
解释:
上面的代码的目的,是要求GCC在一个名为".SO_VERSION"的段内,记录我们的版本号信息。“unused”属性用于告诉GCC,不要在编译的时候警告这个变量没有被任何代码引用过。
注意事项:
在定义变量"Version"的时候,不要使用"volatile"来修饰。这个关键字影响到了最后生成的段的"PROGBITS"标记位置,这个标记表明了最后加载到内存中的数据是否可修改(我们当然希望这个位置不可修改,如果可写,可能由于越界导致版本号的不准确)。
没有使用"volatile"修饰
1 2 |
$ readelf -S libSo.so | grep .SO_VERSION [12] .SO_VERSION PROGBITS 0003f520 03f520 000008 00 A 0 0 8 |
使用"volatile"修饰
1 2 |
$ readelf -S libSo.so | grep .SO_VERSION [12] .SO_VERSION PROGBITS 0003f520 03f520 000008 00 WA 0 0 8 |
注意两者的不同,一个属性是"A",一个属性是"WA"。
-
logcat的崩溃日志中能得到版本号,并且C/C++层的版本号变动,不要改动JAVA层的代码
目前对于这个问题的解决方法,是设置代理So。具体操作方式如下(假定我们生成的".so"项目LOCAL_MODULE:=So):
1.修改原来项目中的"JNI_OnLoad","JNI_OnUnLoad"函数,重新命名为 "So_JNI_OnLoad","So_JNI_OnUnLoad" 其他代码不变,并且在"So-jni.h"中对这两个函数进行声明。
2.所有的JNI方法都通过 "JavaVM->RegisterNatives" 方法进行动态注册。
3.建立SoStub-jni.cpp,代码如下:
1 2 3 4 5 6 7 8 9 10 11 |
#include "So-jni.h" extern "C" JNIEXPORT int JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) { return So_JNI_OnLoad(vm,reserved); } extern "C" JNIEXPORT void JNICALL JNI_OnUnload(JavaVM* vm, void* reserved) { return So_JNI_OnUnload(vm,reserved); } |
4.原工程的"LOCAL_MODULE"修改为"LOCAL_MODULE:=So_1_0_3"(版本号根据实际情况调整即可)
5.修改Android.mk,增加如下内容
1 2 3 4 5 6 7 |
include $(CLEAR_VARS) LOCAL_MODULE := SoStub LOCAL_SRC_FILES := SoStub-jni.cpp LOCAL_SHARED_LIBRARIES += So_1_0_3 include $(BUILD_SHARED_LIBRARY) |
6.修改JAVA层的调用代码
1 2 3 |
static { System.loadLibrary("So"); } |
为:
1 2 3 |
static { System.loadLibrary("SoStub"); } |
解释:
由于logcat打印的崩溃栈,信息极少,因此我们只能采取这种折中的办法,这样设置之后,崩溃栈中会打印出"libSo_1_0_3.so"这样的字样,我们就可以知道版本号了。