Unity3D 异常处理机制剖析
在收集Unity3D crash过程我们分2步出发,1个人负责对上线的3款产品做crash的收集和分析,一个人负责对u3d c#脚本进行扫描工作,扫描某游戏的结果如下:
我们在工具的指引下,用毕业生的miniGame做相关规则测试,测试项空引用、除0、内存泄漏、堆栈溢出等常异常,发现除了内存泄漏(OOM)和堆栈溢出(SOF)会导致u3d minigame crash外,其他的异常都不会导致crash, 有木有抓狂,以前的c/c++相关异常在u3d中完全不适应。
进一步分析(空引用、越界和除零)发现异常在u3d的编辑器中有相关信息提示,在真机上运行,logcat中也会有相应的异常LOG提示,就是不CRASH,从另外一同事那分析的数据发现基本crash都是在java和native, 未能找到c#相关的蜘丝马迹。
跪求度娘和谷哥才发现4.5.1版本unity3d对异常做了很好的封装, 框架层会自动捕获异常而不会导致进程的crash (OOM和 SOF不会捕获),实际游戏中我们对碰撞检查位置进行除零操作,发现脚本运行到除零后就自动结束不会再向下运行(表现上是功能不正确,但是不会导致游戏进程crash)。
在思考人生中突然灵感一现,既然unity3d c#脚本会主动throw异常,那么数据统计功能组件(代号:Duka)是否能把异常crash并上报,接入Duka虽然简单,但是坑仍然还是有,折腾了1天半才搞定,主要原因是太懒只看了文档无视了DEMO,面壁下。
1. 注册Duka项目(以为只接入单独的Duka SDK无需RDM)
2. 注册RDM项目
3. 配置RDM相关数据
4. 引入JAR包到工程并初始化
5. 初始化JAVA端CRASH抓取
6. 初始化NATIVE CRASH抓取
接入后发现c#层的异常(非OOM和SOF外)仍然不会导致U3D游戏进程crash,,在RDM上并未相关的CRASH信息,为验证Duka接入的功能是否正确,分别手动在java层和native层抛出异常,在RDM上看有相应的CRASH信息,C#异常未被Duka捕获的原因还是因为unity本身对异常进行了捕获并很好的处理了异常。
通过GOOGLE 搜索了下Andriod的程序架构,并通过APKTOOL对unity editer生成的pkg进行反编译,结果如下:
Andriod的程序架构( google搜索):
Unity3d miniGame 利用APKTOOL反编译结果:
疑问: unity 的C#脚本是运行在java层还是Native层
经过查阅资料和动手验证发现,在解开的APK中,asserts/bin/data/managed下的所有DLL为C#脚本和相关系统API文件(后续统称脚本), 脚本是通过libmono.so来加载并执行,libmono.so为native层,所以如果由于C#脚本导致的CRASH(目前只发现2种OOM和SOF)一定会在native层。
疑问:unity3d 游戏c# 脚本本身异常如何定位
C#异常并不一定会导致游戏进程crash, 在实际运行过程中,只是停止当前的脚本继续向下执行, 游戏表现上是当前的功能不正确,在底层上可能会导致资源泄漏最终可能会出现OOM情况,如不能正确的处理会给游戏功能缺陷定位和OOM crash定位带来不少问题。
查询资料发现untiy本身由于非常好的封装了异常,可以利用u3d的日志类来很好的获取异常,并通过jni调用java异常抛出函数,在java异常抛出函数上可以做相应的日志输出和处理,如果确实需要修改的异常点可以继续抛出异常,接入Duka的项目就可以抓取相应的JAVA异常。
C#异常处理步骤:
1. 注册日志回调函数
Application.RegisterLogCallback(LogCallback);
2. 在日志函数中判断type哪些是异常
void LogCallback( string strLog, string stackTrace, LogType type)
3. 判断是异常后保存相关信息在脚本变量中
4. 在Update操作中调用异常上报函数
ThrowCShapException(reason,stack);
5. 在ThrowCShapException中通过JNI调用JAVA层的异常处理函数把相应的reason和stack通过给JAVA层
6. 在JAVA层实现个JAVA异常处理函数,专门处理c#上报的异常信息,JAVA异常函数可以进一步throw异常或者自己默默处理掉,进一步上报的异常就可以被Duka捕获到。