├── Crash-platform.png ├── README.md ├── Unity异常的类型.md ├── exception-common.png ├── symbol-path.png └── symbol-retrace.png /Crash-platform.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sundxing/Unity-Exception-Crash/c20f1cbbd9e37a4af6e1303b7ec5e4384d5094f3/Crash-platform.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Unity异常处理与分析 2 | 3 | 4 | ## 引言 5 | 对于程序开发者来说,异常可以理解为坏消息,为了避免异常我们需要良好的结构设计,完备的逻辑编写以及规范的代码格式。但是异常总会不和事宜的出现,针对可能异常的处理和异常的收集也至关重要。 6 | 7 | 此教程就针对异常处理,统计和分析进行展开讲解, 8 | 因为异常的内容知识非常庞大,特别是针对Android和iOS平台本身的Native异常,对于Native异常两个平台有大量的资料可供查阅学习,本教程给出了扩展链接,可以按需阅读。 9 | 10 | ## 核心要点 11 | - Unity exception的介绍和异常处理 12 | - Native crash的介绍 13 | - 上报平台Unity/Appcenter/Firebase/Bugly对比分析 14 | - 处理异常的一般原则和注意事项 15 | - 上报事所必要的信息 16 | 17 | 18 | ## Unity Excepction 19 | 默认情况下C#代码下Exception并不会造成进程杀死,这样的情况如果异常产生后会中断当前执行的代码逻辑,可能会造成一系列意外的结果。iOS平台上Unity提供了`Fast but no Exceptions` 选项[保证所有Exception触发Crash](https://docs.unity3d.com/Manual/iphone-iOS-Optimization.html)。对于Android平台,Unity并没有这样的选项,但是我们可以通过全局异常捕获接口获得Exception,然后强制终止/重启程序或者弹出异常弹窗让用户选择终止或重。下面我们会从种类、构建、处理三个方面介绍异常。 20 | 21 | ###异常种类 22 | Unity中Excepction都是派生于 `System.Exception`,下面是我们开发过程常见的Exception 23 | ![](exception-common.png) 24 | 25 | 26 | 27 | 具体参阅: 28 | 29 | [Handling and throwing exceptions in .NET 30 | ](https://docs.microsoft.com/en-us/dotnet/standard/exceptions/) 31 | 32 | ### 构建异常 33 | 通过 `throw` 关键字可以抛出自定义Exception,三个构造方法可选择: 34 | 35 | ``` 36 | public Exception(); 37 | public Exception(string message); 38 | public Exception(string message, Exception innerException); 39 | ``` 40 | 建议: 41 | 尽可能详尽的附加`message`信息 42 | `innerException`可以附加捕获的Exception,可用于保留异常第一现场的调用栈 43 | 44 | 45 | ### 异常处理 46 | #### 局部处理 47 | 使用try-catch-finally 语句。 48 | 49 | 如果使用了`Task`处理异步任务 ,还可以使用 `ContinueWith `获得Task运行期间的Exception,运行线程和Task相同。 50 | 51 | ``` 52 | Task.Run(() => { throw new CustomException("task1 faulted.");}) 53 | .ContinueWith( t => { Console.WriteLine("{0}: {1}", 54 | t.Exception.InnerException.GetType().Name, 55 | t.Exception.InnerException.Message); 56 | }, TaskContinuationOptions.OnlyOnFaulted); 57 | ``` 58 | #### 全局处理 59 | 60 | Unity 本身提供了`LogCallback `监听程序Log和异常 61 | 62 | ``` c# 63 | /// 64 | /// Use this delegate type with Application.logMessageReceived or Application.logMessageReceivedThreaded to monitor what gets logged. 65 | /// 66 | public delegate void LogCallback(string condition, string stackTrace, LogType type); 67 | ``` 68 | 其中 `LogType` 是 69 | 70 | ``` 71 | public enum LogType 72 | { 73 | Error, 74 | Assert, 75 | Warning, 76 | Log, 77 | Exception, 78 | } 79 | ``` 80 | 看到`LogType `中有 `Exception `,然后我们通过`Application.logMessageReceivedThreaded`注册接口,就可以获得C#代码中抛出的所有Exception了。 81 | 82 | 83 | ### Native Crash 84 | 前面我们介绍了Unity Exception,除了Unity托管代码本身产生的异常之外,运行平台也会有对应的异常,平台托管代码中产生的异常我们是Native Crash。 85 | 当然Native Crash也是跟随平台特性的不同也各有差异,具体到Android平台可以分为 `Java Exception` 和 `Android native crash` 两类,还有iOS平台的 `iOS native crash ` 86 | 87 | #### **Android Java Exception**: 88 | 发生在Android 虚拟机层面的异常,我们通过Java全局的异常捕获类`Thread.UncaughtExceptionHandler`拿到各个线程抛出的Exception 89 | 例1-Java Exception 90 | 91 | ``` 92 | java.lang.ArithmeticException: divide by zero 93 | at com.lbj.mvpflower.mvp.ui.activity.UserActivity.onUser(UserActivity.java:36) 94 | at java.lang.reflect.Method.invoke(Native Method)  95 | at android.view.View$DeclaredOnClickListener.onClick(View.java:4702)  96 | at android.view.View.performClick(View.java:5619)  97 | at android.view.View$PerformClick.run(View.java:22298)  98 | at android.os.Handler.handleCallback(Handler.java:754)  99 | at android.os.Handler.dispatchMessage(Handler.java:95)  100 | at android.os.Looper.loop(Looper.java:165)  101 | at android.app.ActivityThread.main(ActivityThread.java:6365)  102 | ``` 103 | 更多详情: 104 | 105 | - [Android捕获最少知识](https://www.jianshu.com/p/c13eb158d39e) 106 | 107 | #### **Android Native Crash**: 108 | 109 | 简单理解Android的native crash就是发生在so库中异常,所以这些异常实际上就是Linux系统 110 | 常见导致Native Crash的原因有以下几种: 111 | 1. so库内部代码数组越界、缓冲区溢出、空指针、野指针等; 112 | 2. Android ART发现或出现异常; 113 | 3. 其他framework、Kernel或硬件bug; 114 | 115 | 116 | 例2-Android Crash 117 | 118 | ``` 119 | E/CRASH (32251): signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 745a8008 120 | E/CRASH (32251): *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 121 | E/CRASH (32251): Build type 'Release', Scripting Backend 'il2cpp', CPU 'armeabi-v7a' 122 | E/CRASH (32251): Build fingerprint: 'Lenovo/A7600-F/A7600-F:4.4.2/KOT49H/A7600F_A442_000_027_141207_ROW:user/release-keys' 123 | E/CRASH (32251): Revision: '0' 124 | E/CRASH (32251): pid: 32251, tid: 10190, name: Flurry #82 >>> my.package.demo <<< 125 | E/CRASH (32251): r0 77878008 r1 745a8008 r2 0000ac50 r3 00320057 126 | E/CRASH (32251): r4 00310035 r5 00470062 r6 00730077 r7 006d0049 127 | E/CRASH (32251): r8 00300068 r9 00000000 sl 6999eb88 fp 0002bc70 128 | E/CRASH (32251): ip 00480064 sp 793f7a80 lr 007a0042 pc 400bd480 cpsr 793f7790 129 | E/CRASH (32251): 130 | E/CRASH (32251): backtrace: 131 | E/CRASH (32251): #00 pc 00026480 /system/lib/libc.so (__memcpy_base_aligned+52) 132 | E/CRASH (32251): #01 pc 00020bf9 /system/lib/libbinder.so (android::Parcel::appendFrom(android::Parcel const*, unsigned int, unsigned int)+136) 133 | E/CRASH (32251): #02 pc 0006cb9b /system/lib/libandroid_runtime.so 134 | E/CRASH (32251): #03 pc 0001e90c /system/lib/libdvm.so (dvmPlatformInvoke+112) 135 | E/CRASH (32251): #04 pc 0004fbbd /system/lib/libdvm.so (dvmCallJNIMethod(unsigned int const*, JValue*, Method const*, Thread*)+484) 136 | E/CRASH (32251): #05 pc 00027ce8 /system/lib/libdvm.so 137 | E/CRASH (32251): #06 pc 0002f2f0 /system/lib/libdvm.so (dvmMterpStd(Thread*)+76) 138 | E/CRASH (32251): #07 pc 0002c7d4 /system/lib/libdvm.so (dvmInterpret(Thread*, Method const*, JValue*)+188) 139 | E/CRASH (32251): #08 pc 00062ef9 /system/lib/libdvm.so (dvmCallMethodV(Thread*, Method const*, Object*, bool, JValue*, std::__va_list)+340) 140 | E/CRASH (32251): #09 pc 00062f1d /system/lib/libdvm.so (dvmCallMethod(Thread*, Method const*, Object*, JValue*, ...)+20) 141 | E/CRASH (32251): #10 pc 000575c5 /system/lib/libdvm.so 142 | ``` 143 | 144 | - 信号名:signal 11 (SIGSEGV) 145 | - 信号产生的原因:code 1 (SEGV_MAPERR) 146 | - 异常的内存地址:fault addr 745a8008(如果是SIGSEGV/SIGBUS等信号,一般都会有异常内存地址显示) 147 | - 编译类型:il2cpp 148 | - ABI信息:armeabi-v7a 149 | - 设备型号:Lenovo 150 | - Android系统版本:4.4.2 151 | - 系统的build号:A7600FA442000027141207_ROW 152 | - 系统的类型:user(对应有user/eng/userdebug/optional等,user表示是release版本,eng是调试版本) 153 | - Crash的进程号:32251 154 | - Crash的线程号:10190 155 | - Crash的线程名称:Flurry #82(名称可能被裁减导致不全) 156 | - Crash的进程名称:my.package.demo 157 | - 中止消息:无(如果是SIGABRT,可能有类似javavmext.cc:504] JNI DETECTED ERROR IN APPLICATION: java_array == null这样的消息) 158 | - 下面是 寄存器指向的内存地址: 159 | > r0 77878008 r1 745a8008 r2 0000ac50 r3 00320057 ... 160 | 161 | - 最后Native调用栈(栈帧序号-pc地址值-虚拟内存映射区域名称-(对应符号-符号偏移量): 162 | > #00 pc 00026480 /system/lib/libc.so (__memcpy_base_aligned+52) 163 | > ... 164 | 165 | 更多详情: 166 | - [如何分析Android Native Crash](https://cloud.tencent.com/developer/article/1192001) 167 | 168 | #### **iOS Native Crash** 169 | 170 | 171 | 例3-iOS Crash 172 | 173 | ``` 174 | Incident Identifier: D9857968-7AF7-40C0-BA05-192EF275F380 175 | Hardware Model: iPhone11,8 176 | Process: demo [4801] 177 | Path: /private/var/containers/Bundle/Application/03D4FE4F-26BC-47E5-A1D9-B8FF1D379B4A/demo.app/demo 178 | Identifier: my.bundleid.demo 179 | Version: 2 (1.0) 180 | AppStoreTools: 11E608a 181 | AppVariant: 1:iPhone11,8:13 182 | Beta: YES 183 | Code Type: ARM-64 (Native) 184 | Role: Foreground 185 | Parent Process: launchd [1] 186 | Coalition: my.bundleid.demo [1515] 187 | 188 | 189 | Date/Time: 2020-07-13 08:28:25.9201 +0700 190 | Launch Time: 2020-07-13 08:27:57.4775 +0700 191 | OS Version: iPhone OS 13.3 (17C54) 192 | Release Type: User 193 | Baseband Version: 2.03.07 194 | Report Version: 104 195 | 196 | Exception Type: EXC_BAD_ACCESS (SIGBUS) 197 | Exception Subtype: KERN_PROTECTION_FAILURE at 0x000000011b747ff0 198 | VM Region Info: 0x11b747ff0 is in 0x11b734000-0x11b748000; bytes after start: 81904 bytes before end: 15 199 | REGION TYPE START - END [ VSIZE] PRT/MAX SHRMOD REGION DETAIL 200 | MALLOC_LARGE 000000011b730000-000000011b734000 [ 16K] rw-/rwx SM=PRV 201 | ---> CG raster data 000000011b734000-000000011b748000 [ 80K] r--/r-- SM=PRV 202 | MALLOC_LARGE 000000011b748000-000000011b74c000 [ 16K] rw-/rwx SM=PRV 203 | 204 | Termination Signal: Bus error: 10 205 | Termination Reason: Namespace SIGNAL, Code 0xa 206 | Terminating Process: exc handler [4801] 207 | Triggered by Thread: 38 208 | 209 | Thread 0 name: 210 | Thread 0: 211 | 0 libsystem_kernel.dylib 0x00000001b92c8c04 mach_msg_trap + 8 212 | 1 libsystem_kernel.dylib 0x00000001b92c8020 mach_msg + 76 (mach_msg.c:103) 213 | 2 CoreFoundation 0x00000001b947b964 __CFRunLoopServiceMachPort + 220 (CFRunLoop.c:2575) 214 | 3 CoreFoundation 0x00000001b94767fc __CFRunLoopRun + 1428 (CFRunLoop.c:2931) 215 | 4 CoreFoundation 0x00000001b9475f40 CFRunLoopRunSpecific + 480 (CFRunLoop.c:3192) 216 | 5 GraphicsServices 0x00000001c36f3534 GSEventRunModal + 108 (GSEvent.c:2246) 217 | 6 UIKitCore 0x00000001bd5eea60 UIApplicationMain + 1940 (UIApplication.m:4773) 218 | 7 UnityFramework 0x000000010281a330 -[UnityFramework runUIApplicationMainWithArgc:argv:] + 108 (main.mm:96) 219 | 8 demo 0x0000000100eb7e1c main + 68 (main.mm:26) 220 | 9 libdyld.dylib 0x00000001b92f4e18 start + 4 221 | 222 | Thread 1: 223 | 0 libsystem_pthread.dylib 0x00000001b92119e0 start_wqthread + 0 224 | 225 | Thread 2: 226 | 0 libsystem_pthread.dylib 0x00000001b92119e0 start_wqthread + 0 227 | 228 | ···· 229 | Thread 38 Crashed: 230 | 0 libsystem_platform.dylib 0x00000001b9206688 _platform_memmove + 408 231 | 1 UnityFramework 0x000000010334f840 il2cpp::icalls::mscorlib::System::Buffer::BlockCopyInternal(Il2CppArray*, int, Il2CppArray*, int, int) + 176 232 | 2 UnityFramework 0x0000000103aaee4c StreamWriter_Write_m8056BDE8A4AD4816F9D7DBDBCB80D03BE8F3ED14 + 192 (mscorlib9.cpp:21738) 233 | 3 UnityFramework 0x0000000103ab22f4 UnexceptionalStreamWriter_Write_mAC310C8D24F673608DC7F130666E572ADD388E07 + 92 (mscorlib9.cpp:24996) 234 | 4 UnityFramework 0x0000000103729968 Logger_Log_mC3BD9B8C40067382718BD9A54688A92599404E6B + 240 (SmartFox2X2.cpp:32959) 235 | 5 UnityFramework 0x00000001036f8548 UDPManager_OnUDPData_mCD9304C4A4BD03D6F85EFAD9512893D4B2D037DC + 396 (SmartFox2X1.cpp:19687) 236 | 6 UnityFramework 0x0000000103712ec0 OnDataDelegate_Invoke_mF1F76E2DCD9F8028B8956AE76035840B21615920 + 488 (SmartFox2X1.cpp:0) 237 | 7 UnityFramework 0x00000001037175fc ThreadManager_InThread_m70E3D0A1EBE6F3AF67DFE1E8D50F54463DA49D10 + 180 (SmartFox2X2.cpp:18993) 238 | 8 UnityFramework 0x00000001039fcb98 ThreadStart_Invoke_m11B6A66E82F02C74399A7314C14C7F52393CC4B4 + 284 (ClassInlines.h:0) 239 | 9 UnityFramework 0x00000001039d0940 ContextCallback_Invoke_m76E65E7A67AA99858554F451E0F78E22B6478998 + 440 (ClassInlines.h:0) 240 | 10 UnityFramework 0x00000001039d1bf8 ExecutionContext_RunInternal_m5BF955CE8B04D9A7C959B9011579CC0FABD5FC56 + 316 (mscorlib19.cpp:30738) 241 | 11 UnityFramework 0x00000001039fcb98 ThreadStart_Invoke_m11B6A66E82F02C74399A7314C14C7F52393CC4B4 + 284 (ClassInlines.h:0) 242 | 12 UnityFramework 0x000000010286b620 RuntimeInvoker_TrueVoid_t22962CB4C05B1D89B55A6E1139F0E87A90987017(void (*)(), MethodInfo const*, void*, void**) + 20 (Il2CppInvokerTable.cpp:18279) 243 | 13 UnityFramework 0x00000001033925c8 il2cpp::vm::Runtime::Invoke(MethodInfo const*, void*, void**, Il2CppException**) + 116 244 | 14 UnityFramework 0x000000010335c678 il2cpp::icalls::mscorlib::System::Threading::ThreadStart(void*) + 108 245 | 15 UnityFramework 0x0000000103365a7c il2cpp::os::Thread::RunWrapper(void*) + 88 246 | 16 UnityFramework 0x0000000103367d14 il2cpp::os::ThreadImpl::ThreadStartWrapper(void*) + 40 247 | 17 libsystem_pthread.dylib 0x00000001b9209840 _pthread_start + 168 (pthread.c:896) 248 | 18 libsystem_pthread.dylib 0x00000001b92119f4 thread_start + 8 249 | 250 | ···· 251 | 252 | Binary Images: 253 | 0x100eb0000 - 0x100eb7fff demo arm64 <14cde28b305d3bffb8abdfab5a30df25> /var/containers/Bundle/Application/03D4FE4F-26BC-47E5-A1D9-B8FF1D379B4A/demo.app/demo 254 | 0x100f80000 - 0x100f8bfff libobjc-trampolines.dylib arm64e <028df612175838e498958fd8c6a9a03c> /usr/lib/libobjc-trampolines.dylib 255 | 256 | ``` 257 | 上面省略了其他线程的信息 258 | 259 | - Incident Identifier : Crash唯一ID 260 | - Hardware Model: 设备型号 261 | - Process: 可执行文件名 262 | - Identifier :Bundle ID 263 | - Code Type : CPU架构 264 | - OS Version: 系统版本号号 265 | - Report Version :崩溃日志版本号 266 | - Exception Type - Termination Reason: 是异常的类型和原因 267 | - Triggered by Thread: 38 : 引发的线程 268 | - Thread 0: 主线程 269 | - 崩溃发生的堆栈 (位置, 地址, 函数符号+ 偏移量): 270 | > 0 libsystem_kernel.dylib 0x00000001b92c8c04 mach_msg_trap + 8 271 | 272 | - Binary Images: 二进制文件内存信息 ( 地址起始-地址结束 名称 CPU架构 路径) 273 | > 0x100eb0000 - 0x100eb7fff demo arm64 <14cde28b305d3bffb8abdfab5a30df25> /var/containers/Bundle/Application/03D4FE4F-26BC-47E5-A1D9-B8FF1D379B4A/demo.app/demo 274 | 275 | 更多详情: 276 | 277 | - [iOS Crash日志结构介绍](https://jianli2017.top/wiki/IOS/crash/1_system_Crash_Type/) 278 | - [了解和分析iOS Crash Report](https://juejin.im/post/6844903774780145678) 279 | 280 | 281 | 282 | 对于Debug环境中我们拿到的Crash日志通常都是带有符号的,也就是我们可以追踪到具体的crash方法和行号。如果Crash堆栈没有解析符号表,这种情况就需要我们手动解析符号表了,当然很多Crash平台都集成了解析符号表的功能,我们只需要自己上传即可。下面我们就介绍下符号表相关的知识。 283 | 284 | ### 解析符号表 285 | 为了能快速并准确地定位用户APP发生Crash的代码位置,我们使用符号表对APP发生Crash的程序堆栈进行解析和还原, 比如一个iOS的crash堆栈: 286 | ![](symbol-retrace.png) 287 | ##### 符号表 288 | 简单来说,符号表是内存地址与函数名,文件名,行号的映射表 289 | > <起始地址> <结束地址> <函数> [<文件名:行号>] 290 | 291 | 292 | - 对于Android工程,一般情况下我们采用 il2cpp 方式构建,unity-2018.4会把符号表自动打包成一个压缩包文件。后缀 `.symbols.zip`, 存放位置和 `.apk` 文件在同一目录。 293 | ![](symbol-path.png) 294 | 295 | - iOS系统的下的符号表就是dSYM文件,文件名通常为:xxx.app.dSYM。我们使用xcode编译完项目后,会生产dSYM文件,和app文件在同一个目录下 296 | 297 | 符号表相关的知识参考: 298 | Bugly官方手册 - [Android符号表](https://bugly.qq.com/docs/user-guide/symbol-configuration-android/?v=20200622202242),[iOS符号表](https://bugly.qq.com/docs/user-guide/symbol-configuration-ios/?v=20200622202242) 299 | 300 | ##### 解析符号表 301 | 拿到符号表,并且确认**UIID一致**的情况下,我们就可以进行工具进行堆栈符号解析了。 302 | 303 | - Android平台下可以使用NDK库中 `add2line ` `ndk-stack` 工具,`ndk-stack`可以对Android的Crash-Log进行提取和解析,非常方便在拿到Crashlog的情况下分析。 304 | 我们通过以下命名可以获取实时的Crash信息 305 | > adb shell logcat | ndk-stack -sym $PROJECT_PATH/obj/local/armeabi 306 | 更多内容请翻阅: 307 | [Android Native crash日志分析](https://www.cnblogs.com/willhua/p/6718379.html) 308 | 309 | - iOS平台下两种工具`symbolicatecrash ` 和`atos ` ,`symbolicatecrash`其实就是对`atos`的封装 310 | 更多内容请翻阅: 311 | [OS Crash分析必备:符号化系统库方法](https://zuikyo.github.io/2016/12/18/iOS%20Crash%E6%97%A5%E5%BF%97%E5%88%86%E6%9E%90%E5%BF%85%E5%A4%87%EF%BC%9A%E7%AC%A6%E5%8F%B7%E5%8C%96%E7%B3%BB%E7%BB%9F%E5%BA%93%E6%96%B9%E6%B3%95/) 312 | 313 | 314 | 315 | ## Unity上报平台的选择 316 | 317 | ### 主流上报平台的对比分析 318 | Unity平台的异常上报通常有四家较为主流的平台, 319 | 320 | - [Bugly](https://bugly.qq.com/docs/user-guide/instruction-manual-plugin-unity/?v=20200312155538) 321 | - [Appcenter](https://docs.microsoft.com/en-gb/appcenter/sdk/crashes/unity) 322 | - [Unity](https://unitytech.github.io/clouddiagnostics/) 323 | - [Firebase](https://firebase.google.com/docs/crashlytics/get-started?authuser=0&platform=unity) 324 | 325 | 这里对各个平台的特点梳理了一个表格,集成后可以参阅(标绿的部分是明显优势项,表格中仅拿Android项目进行了测试统计): 326 | ![](crash-platform.png) 327 | 328 | 根据咱们公司风控要求,**Bugly限制国内平台使用**,**Firebase限制全平台使用**,所以产品的集成建议以下组合方式: 329 | > 国内产品推荐:Bugly, Unity 330 | > 331 | > 海外产品推荐:Appcenter, Unity 332 | 333 | 334 | ### 搭建自己的上报平台 335 | 前面介绍了第三方Crash或者Unity平台,假如我们需要上报到自己的平台,需要分几个方面进行处理,这些可以单独进行集成: 336 | 1. Exception 捕获 337 | 2. Native Crash, 包括Android和iOS平台 338 | 339 | #### 全局异常处理 340 | 前面已经讲过 `Application.logMessageReceivedThreaded`注册接口可以获取全局的Exception,所以Unity托段代码的异常获得还是非常简单的,实现对应的接口方法即可监听。 341 | 342 | #### iOS 异常 343 | iOS平台提供了捕获异常的接口: 344 | 345 | ``` 346 | /* Set the uncaught exception handler */ 347 | NSSetUncaughtExceptionHandler(&uncaught_exception_handler); 348 | ``` 349 | 如果同时使用了注册了第三方的Crash平台, 需要先保存第三方的 ExceptionHandler,然后在设置自己处理 exception 的 ExceptionHandler,在自己的 ExceptionHandler 处理完异常之后,再将异常交给第三方处理。 350 | 351 | 如果需要集成服务端解析符号表,请参考: 352 | [有赞 crash 平台符号化实践](https://www.infoq.cn/article/JLtuf3PGfJV6ovjzu1sq?utm_source=related_read_bottom&utm_medium=article) 353 | 354 | #### Android Java 异常 355 | Android平台也提供了处理Java异常的全局接口 `Thread.UncaughtExceptionHandler`, 我们实现此接口通过 356 | `Thread.setDefaultUncaughtExceptionHandler` 注册给系统即可。同样iOS平台类似,应为此接口值接受一个Handler,需要先通过 `Thread.getDefaultUncaughtExceptionHandler` 获取之前的Handler,再根据需要将捕获的Exception进行转交。 357 | 358 | #### Android Native 异常 359 | Android 平台可以使用 [google breakpad](https://github.com/google/breakpad) 捕获native crash(TODO: 集成方案:Sentry), Appcenter平台就是采用的该方案。 360 | 361 | 362 | ## 异常处理和上报 363 | 364 | 当我们了解了所有的异常类型,也选择了合适的上报平台,最后就可以进行异常上报了。通常第三方都会主动收集异常信息,`AppCenter`这样的平台甚至会主动上报Error的log, `Bugly`的Android平台还是收集 logcat日志信息。 除了平台做的这些事情我们还需要注意两件非常重要的事情,来快速定位Crash: 365 | 366 | 1. 恰当的处理异常:异常的信息能够发现具体的**异常原因** 和 **异常发生位置** 367 | 2. 上报异常之外的其他信息,进行辅助判断,因为很多的native异常位置指向性并不能定位原因。 368 | 369 | #### 恰当的处理异常 370 | 371 | ##### 正确使用try-catch-finially语句处理异常。 372 | 373 | - `try`的作用域尽量单一,方便错误定位。 374 | - `catch`中做恢复资源,如果不能正确处理请上报事件或debug下输出错误,最后或者是重新throw。 375 | - `finally`中清理资源,可以不`catch` Exception,仅使用try-finally做资源闭环处理。 376 | 377 | 另外更多使用规则请参阅 378 | [Best practices for exceptions](https://docs.microsoft.com/en-us/dotnet/standard/exceptions/best-practices-for-exceptions) 379 | 380 | ##### throw 关键字使用 381 | 382 | 383 | * `throw [e];` 抛出一个异常,调用栈是当前调用方法。 384 | 385 | 注意: 当try-catch中 抛出一个新的异常,捕获的异常的调用栈并不是真正错误来源。如果需要保留catch的Exception,需要使用下面的构建方法: 386 | > `public Exception(string message, Exception innerException);` 387 | 388 | 一个常见的用法: 389 | 390 | ```c# 391 | public class ProcessFile 392 | { 393 | public static void Main() 394 | { 395 | FileStream fs; 396 | try 397 | { 398 | fs = new FileStream("data.txt", FileMode.Open); 399 | var sr = new StreamReader(fs); 400 | // .... 401 | } 402 | catch(FileNotFoundException e) 403 | { 404 | Console.WriteLine($"[Data File Missing] {e}"); 405 | throw new FileNotFoundException(@"[data.txt not in c:\temp directory]", e); 406 | } 407 | finally 408 | { 409 | if (fs != null) 410 | fs.Close(); 411 | } 412 | } 413 | } 414 | ``` 415 | 416 | * `throw;` 重新抛出try-catch的异常 ,通常可作为代码异常的监听,但不做拦截 417 | 418 | ##### 方便确定异常发生的位置 419 | il2cpp目前并不记录异常代码的行号,这对定位产生了一定的困难。以下几个点可以让我们更加方便确定异常的位置: 420 | 421 | - 编写职责单一的方法,方法行数不易过多。 422 | - 异常捕获时提供额外信息。 423 | - 准确记录堆栈路径,通常是自己构建Exception时注意保留Catch的Exception堆栈。 424 | 425 | 426 | #### 上报关键信息 427 | 428 | 我们可以通过反馈入口或者异常捕获拿拿到线上用户的现场信息,为了方便定位Crash,应当尽量提供一下现场信息 429 | 430 | - 设备信息: 包括设备基本类型,系统版本,系统语言,定义的UIID, 内存情况,存储情况(需要单独实现Native接口)等 431 | 432 | ``` 433 | SystemInfo.deviceModel, 434 | SystemInfo.deviceName, 435 | SystemInfo.deviceType, 436 | SystemInfo.deviceUniqueIdentifier, 437 | SystemInfo.operatingSystem, 438 | Localization.language, 439 | 440 | SystemInfo.systemMemorySize, 441 | SystemInfo.processorCount, 442 | SystemInfo.processorType, 443 | Screen.currentResolution.width, 444 | Screen.currentResolution.height, 445 | Screen.dpi, 446 | Screen.fullScreen, 447 | 448 | SystemInfo.graphicsDeviceName, 449 | SystemInfo.graphicsDeviceVendor, 450 | SystemInfo.graphicsMemorySize, 451 | SystemInfo.graphicsPixelFillrate, 452 | SystemInfo.maxTextureSize, 453 | ``` 454 | 455 | - 应用信息:运行时间,异常时所在界面,用户操作的动作链等 456 | - Trace事件:为了方便业务分析,我们在开发过程会上报事件用户埋点分析,除此之外我们可以提供Trace事件类型,主要来统计模块加载和错误输出,发生异常是可以根据场景附加上多个事件集合。 457 | - 日志文件:在可能的情况下,附加当前应用的日志文件,AppCenter提供了附加文件的接口,我们可以将日志文件一并上传。 458 | - 反馈接口:用户提供额外的文字描述和截屏。 459 | 460 | -------------------------------------------------------------------------------- /Unity异常的类型.md: -------------------------------------------------------------------------------- 1 | # Unity异常处理与分析 2 | 3 | 4 | ## 引言 5 | 对于程序开发者来说,异常可以理解为坏消息,为了避免异常我们需要良好的结构设计,完备的逻辑编写以及规范的代码格式。但是异常总会不和事宜的出现,针对可能异常的处理和异常的收集也至关重要。 6 | 7 | 此教程就针对异常处理,统计和分析进行展开讲解, 8 | 因为异常的内容知识非常庞大,特别是针对Android和iOS平台本身的Native异常,对于Native异常两个平台有大量的资料可供查阅学习,本教程给出了扩展链接,可以按需阅读。 9 | 10 | ## 核心要点 11 | - Unity exception的介绍和异常处理 12 | - Native crash的介绍 13 | - 上报平台Unity/Appcenter/Firebase/Bugly对比分析 14 | - 处理异常的一般原则和注意事项 15 | - 上报事所必要的信息 16 | 17 | 18 | ## Unity Excepction 19 | 默认情况下C#代码下Exception并不会造成进程杀死,这样的情况如果异常产生后会中断当前执行的代码逻辑,可能会造成一系列意外的结果。iOS平台上Unity提供了`Fast but no Exceptions` 选项[保证所有Exception触发Crash](https://docs.unity3d.com/Manual/iphone-iOS-Optimization.html)。对于Android平台,Unity并没有这样的选项,但是我们可以通过全局异常捕获接口获得Exception,然后强制终止/重启程序或者弹出异常弹窗让用户选择终止或重。下面我们会从种类、构建、处理三个方面介绍异常。 20 | 21 | ###异常种类 22 | Unity中Excepction都是派生于 `System.Exception`,下面是我们开发过程常见的Exception 23 | ![](exception-common.png) 24 | 25 | 26 | 27 | 具体参阅: 28 | 29 | [Handling and throwing exceptions in .NET 30 | ](https://docs.microsoft.com/en-us/dotnet/standard/exceptions/) 31 | 32 | ### 构建异常 33 | 通过 `throw` 关键字可以抛出自定义Exception,三个构造方法可选择: 34 | 35 | ``` 36 | public Exception(); 37 | public Exception(string message); 38 | public Exception(string message, Exception innerException); 39 | ``` 40 | 建议: 41 | 尽可能详尽的附加`message`信息 42 | `innerException`可以附加捕获的Exception,可用于保留异常第一现场的调用栈 43 | 44 | 45 | ### 异常处理 46 | #### 局部处理 47 | ##### 使用try-catch-finally 语句 48 | 49 | 注意:**如果try作用域包含了异步处理,该异步处理产生的异常不会被捕获** 50 | 常见异步情况有: 51 | 52 | - 协程(Coroutines)。 如果想要协程处理异常可以参考 [Catching Exceptions in Coroutines](https://www.jacksondunstan.com/articles/3718) 53 | - 线程处理 54 | - 其他延时操作,比如 `InvokeDelayed` 调用 55 | 56 | ##### 使用ContinueWith 57 | 如果使用了`Task`处理异步任务 ,还可以使用 `ContinueWith `获得Task运行期间的Exception,运行线程和Task相同。 58 | 59 | ``` 60 | Task.Run(() => { throw new CustomException("task1 faulted.");}) 61 | .ContinueWith( t => { Console.WriteLine("{0}: {1}", 62 | t.Exception.InnerException.GetType().Name, 63 | t.Exception.InnerException.Message); 64 | }, TaskContinuationOptions.OnlyOnFaulted); 65 | ``` 66 | 67 | 68 | #### 全局处理 69 | 70 | Unity 本身提供了`LogCallback `监听程序Log和异常 71 | 72 | ``` c# 73 | /// 74 | /// Use this delegate type with Application.logMessageReceived or Application.logMessageReceivedThreaded to monitor what gets logged. 75 | /// 76 | public delegate void LogCallback(string condition, string stackTrace, LogType type); 77 | ``` 78 | 其中 `LogType` 是 79 | 80 | ``` 81 | public enum LogType 82 | { 83 | Error, 84 | Assert, 85 | Warning, 86 | Log, 87 | Exception, 88 | } 89 | ``` 90 | 看到`LogType `中有 `Exception `,然后我们通过`Application.logMessageReceivedThreaded`注册接口,就可以获得C#代码中未被捕获的的所有Exception了,需要注意的是`LogCallback`接口拿不到Exception实例,只有Excetipion信息和调用栈。 91 | 92 | 93 | ### Native Crash 94 | 前面我们介绍了Unity Exception,除了Unity托管代码本身产生的异常之外,运行平台也会有对应的异常,平台托管代码中产生的异常我们是Native Crash,另外这些异常错误直接造成App终止运行,也就是我们常说的Crash闪退了。 95 | 当然Native Crash也是跟随平台特性的不同也各有差异,具体到Android平台可以分为 `Java Exception` 和 `Android native crash` 两类,还有iOS平台的 `iOS native crash ` 96 | 97 | #### **Android Java Exception**: 98 | 发生在Android 虚拟机层面的异常,我们通过Java全局的异常捕获类`Thread.UncaughtExceptionHandler`拿到各个线程抛出的Exception。 99 | 100 | 例1-Java Exception 101 | 102 | ``` 103 | java.lang.ArithmeticException: divide by zero 104 | at com.lbj.mvpflower.mvp.ui.activity.UserActivity.onUser(UserActivity.java:36) 105 | at java.lang.reflect.Method.invoke(Native Method)  106 | at android.view.View$DeclaredOnClickListener.onClick(View.java:4702)  107 | at android.view.View.performClick(View.java:5619)  108 | at android.view.View$PerformClick.run(View.java:22298)  109 | at android.os.Handler.handleCallback(Handler.java:754)  110 | at android.os.Handler.dispatchMessage(Handler.java:95)  111 | at android.os.Looper.loop(Looper.java:165)  112 | at android.app.ActivityThread.main(ActivityThread.java:6365)  113 | ``` 114 | 更多详情: 115 | 116 | - [Android捕获最少知识](https://www.jianshu.com/p/c13eb158d39e) 117 | 118 | #### **Android Native Crash**: 119 | 120 | 简单理解Android的native crash就是发生在so库中异常,所以这些异常实际上就是Linux系统 121 | 常见导致Native Crash的原因有以下几种: 122 | 123 | 1. so库内部代码数组越界、缓冲区溢出、空指针、野指针等; 124 | 2. Android ART发现或出现异常; 125 | 3. 其他framework、Kernel或硬件bug; 126 | 127 | 128 | 例2-Android Crash 129 | 130 | ``` 131 | E/CRASH (32251): signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 745a8008 132 | E/CRASH (32251): *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 133 | E/CRASH (32251): Build type 'Release', Scripting Backend 'il2cpp', CPU 'armeabi-v7a' 134 | E/CRASH (32251): Build fingerprint: 'Lenovo/A7600-F/A7600-F:4.4.2/KOT49H/A7600F_A442_000_027_141207_ROW:user/release-keys' 135 | E/CRASH (32251): Revision: '0' 136 | E/CRASH (32251): pid: 32251, tid: 10190, name: Flurry #82 >>> my.package.demo <<< 137 | E/CRASH (32251): r0 77878008 r1 745a8008 r2 0000ac50 r3 00320057 138 | E/CRASH (32251): r4 00310035 r5 00470062 r6 00730077 r7 006d0049 139 | E/CRASH (32251): r8 00300068 r9 00000000 sl 6999eb88 fp 0002bc70 140 | E/CRASH (32251): ip 00480064 sp 793f7a80 lr 007a0042 pc 400bd480 cpsr 793f7790 141 | E/CRASH (32251): 142 | E/CRASH (32251): backtrace: 143 | E/CRASH (32251): #00 pc 00026480 /system/lib/libc.so (__memcpy_base_aligned+52) 144 | E/CRASH (32251): #01 pc 00020bf9 /system/lib/libbinder.so (android::Parcel::appendFrom(android::Parcel const*, unsigned int, unsigned int)+136) 145 | E/CRASH (32251): #02 pc 0006cb9b /system/lib/libandroid_runtime.so 146 | E/CRASH (32251): #03 pc 0001e90c /system/lib/libdvm.so (dvmPlatformInvoke+112) 147 | E/CRASH (32251): #04 pc 0004fbbd /system/lib/libdvm.so (dvmCallJNIMethod(unsigned int const*, JValue*, Method const*, Thread*)+484) 148 | E/CRASH (32251): #05 pc 00027ce8 /system/lib/libdvm.so 149 | E/CRASH (32251): #06 pc 0002f2f0 /system/lib/libdvm.so (dvmMterpStd(Thread*)+76) 150 | E/CRASH (32251): #07 pc 0002c7d4 /system/lib/libdvm.so (dvmInterpret(Thread*, Method const*, JValue*)+188) 151 | E/CRASH (32251): #08 pc 00062ef9 /system/lib/libdvm.so (dvmCallMethodV(Thread*, Method const*, Object*, bool, JValue*, std::__va_list)+340) 152 | E/CRASH (32251): #09 pc 00062f1d /system/lib/libdvm.so (dvmCallMethod(Thread*, Method const*, Object*, JValue*, ...)+20) 153 | E/CRASH (32251): #10 pc 000575c5 /system/lib/libdvm.so 154 | ``` 155 | 156 | - 信号名:signal 11 (SIGSEGV) 157 | - 信号产生的原因:code 1 (SEGV_MAPERR) 158 | - 异常的内存地址:fault addr 745a8008(如果是SIGSEGV/SIGBUS等信号,一般都会有异常内存地址显示) 159 | - 编译类型:il2cpp 160 | - ABI信息:armeabi-v7a 161 | - 设备型号:Lenovo 162 | - Android系统版本:4.4.2 163 | - 系统的build号:A7600FA442000027141207_ROW 164 | - 系统的类型:user(对应有user/eng/userdebug/optional等,user表示是release版本,eng是调试版本) 165 | - Crash的进程号:32251 166 | - Crash的线程号:10190 167 | - Crash的线程名称:Flurry #82(名称可能被裁减导致不全) 168 | - Crash的进程名称:my.package.demo 169 | - 中止消息:无(如果是SIGABRT,可能有类似javavmext.cc:504] JNI DETECTED ERROR IN APPLICATION: java_array == null这样的消息) 170 | - 下面是 寄存器指向的内存地址: 171 | > r0 77878008 r1 745a8008 r2 0000ac50 r3 00320057 ... 172 | 173 | - 最后Native调用栈(栈帧序号-pc地址值-虚拟内存映射区域名称-(对应符号-符号偏移量): 174 | > #00 pc 00026480 /system/lib/libc.so (__memcpy_base_aligned+52) 175 | > ... 176 | 177 | 更多详情: 178 | - [如何分析Android Native Crash](https://cloud.tencent.com/developer/article/1192001) 179 | 180 | #### **iOS Native Crash** 181 | 182 | 与Android类似,iOS可以拿到的Crash记录也非常详细 183 | 184 | 例3-iOS Crash 185 | 186 | ``` 187 | Incident Identifier: D9857968-7AF7-40C0-BA05-192EF275F380 188 | Hardware Model: iPhone11,8 189 | Process: demo [4801] 190 | Path: /private/var/containers/Bundle/Application/03D4FE4F-26BC-47E5-A1D9-B8FF1D379B4A/demo.app/demo 191 | Identifier: my.bundleid.demo 192 | Version: 2 (1.0) 193 | AppStoreTools: 11E608a 194 | AppVariant: 1:iPhone11,8:13 195 | Beta: YES 196 | Code Type: ARM-64 (Native) 197 | Role: Foreground 198 | Parent Process: launchd [1] 199 | Coalition: my.bundleid.demo [1515] 200 | 201 | 202 | Date/Time: 2020-07-13 08:28:25.9201 +0700 203 | Launch Time: 2020-07-13 08:27:57.4775 +0700 204 | OS Version: iPhone OS 13.3 (17C54) 205 | Release Type: User 206 | Baseband Version: 2.03.07 207 | Report Version: 104 208 | 209 | Exception Type: EXC_BAD_ACCESS (SIGBUS) 210 | Exception Subtype: KERN_PROTECTION_FAILURE at 0x000000011b747ff0 211 | VM Region Info: 0x11b747ff0 is in 0x11b734000-0x11b748000; bytes after start: 81904 bytes before end: 15 212 | REGION TYPE START - END [ VSIZE] PRT/MAX SHRMOD REGION DETAIL 213 | MALLOC_LARGE 000000011b730000-000000011b734000 [ 16K] rw-/rwx SM=PRV 214 | ---> CG raster data 000000011b734000-000000011b748000 [ 80K] r--/r-- SM=PRV 215 | MALLOC_LARGE 000000011b748000-000000011b74c000 [ 16K] rw-/rwx SM=PRV 216 | 217 | Termination Signal: Bus error: 10 218 | Termination Reason: Namespace SIGNAL, Code 0xa 219 | Terminating Process: exc handler [4801] 220 | Triggered by Thread: 38 221 | 222 | Thread 0 name: 223 | Thread 0: 224 | 0 libsystem_kernel.dylib 0x00000001b92c8c04 mach_msg_trap + 8 225 | 1 libsystem_kernel.dylib 0x00000001b92c8020 mach_msg + 76 (mach_msg.c:103) 226 | 2 CoreFoundation 0x00000001b947b964 __CFRunLoopServiceMachPort + 220 (CFRunLoop.c:2575) 227 | 3 CoreFoundation 0x00000001b94767fc __CFRunLoopRun + 1428 (CFRunLoop.c:2931) 228 | 4 CoreFoundation 0x00000001b9475f40 CFRunLoopRunSpecific + 480 (CFRunLoop.c:3192) 229 | 5 GraphicsServices 0x00000001c36f3534 GSEventRunModal + 108 (GSEvent.c:2246) 230 | 6 UIKitCore 0x00000001bd5eea60 UIApplicationMain + 1940 (UIApplication.m:4773) 231 | 7 UnityFramework 0x000000010281a330 -[UnityFramework runUIApplicationMainWithArgc:argv:] + 108 (main.mm:96) 232 | 8 demo 0x0000000100eb7e1c main + 68 (main.mm:26) 233 | 9 libdyld.dylib 0x00000001b92f4e18 start + 4 234 | 235 | Thread 1: 236 | 0 libsystem_pthread.dylib 0x00000001b92119e0 start_wqthread + 0 237 | 238 | Thread 2: 239 | 0 libsystem_pthread.dylib 0x00000001b92119e0 start_wqthread + 0 240 | 241 | ···· 242 | Thread 38 Crashed: 243 | 0 libsystem_platform.dylib 0x00000001b9206688 _platform_memmove + 408 244 | 1 UnityFramework 0x000000010334f840 il2cpp::icalls::mscorlib::System::Buffer::BlockCopyInternal(Il2CppArray*, int, Il2CppArray*, int, int) + 176 245 | 2 UnityFramework 0x0000000103aaee4c StreamWriter_Write_m8056BDE8A4AD4816F9D7DBDBCB80D03BE8F3ED14 + 192 (mscorlib9.cpp:21738) 246 | 3 UnityFramework 0x0000000103ab22f4 UnexceptionalStreamWriter_Write_mAC310C8D24F673608DC7F130666E572ADD388E07 + 92 (mscorlib9.cpp:24996) 247 | 4 UnityFramework 0x0000000103729968 Logger_Log_mC3BD9B8C40067382718BD9A54688A92599404E6B + 240 (SmartFox2X2.cpp:32959) 248 | 5 UnityFramework 0x00000001036f8548 UDPManager_OnUDPData_mCD9304C4A4BD03D6F85EFAD9512893D4B2D037DC + 396 (SmartFox2X1.cpp:19687) 249 | 6 UnityFramework 0x0000000103712ec0 OnDataDelegate_Invoke_mF1F76E2DCD9F8028B8956AE76035840B21615920 + 488 (SmartFox2X1.cpp:0) 250 | 7 UnityFramework 0x00000001037175fc ThreadManager_InThread_m70E3D0A1EBE6F3AF67DFE1E8D50F54463DA49D10 + 180 (SmartFox2X2.cpp:18993) 251 | 8 UnityFramework 0x00000001039fcb98 ThreadStart_Invoke_m11B6A66E82F02C74399A7314C14C7F52393CC4B4 + 284 (ClassInlines.h:0) 252 | 9 UnityFramework 0x00000001039d0940 ContextCallback_Invoke_m76E65E7A67AA99858554F451E0F78E22B6478998 + 440 (ClassInlines.h:0) 253 | 10 UnityFramework 0x00000001039d1bf8 ExecutionContext_RunInternal_m5BF955CE8B04D9A7C959B9011579CC0FABD5FC56 + 316 (mscorlib19.cpp:30738) 254 | 11 UnityFramework 0x00000001039fcb98 ThreadStart_Invoke_m11B6A66E82F02C74399A7314C14C7F52393CC4B4 + 284 (ClassInlines.h:0) 255 | 12 UnityFramework 0x000000010286b620 RuntimeInvoker_TrueVoid_t22962CB4C05B1D89B55A6E1139F0E87A90987017(void (*)(), MethodInfo const*, void*, void**) + 20 (Il2CppInvokerTable.cpp:18279) 256 | 13 UnityFramework 0x00000001033925c8 il2cpp::vm::Runtime::Invoke(MethodInfo const*, void*, void**, Il2CppException**) + 116 257 | 14 UnityFramework 0x000000010335c678 il2cpp::icalls::mscorlib::System::Threading::ThreadStart(void*) + 108 258 | 15 UnityFramework 0x0000000103365a7c il2cpp::os::Thread::RunWrapper(void*) + 88 259 | 16 UnityFramework 0x0000000103367d14 il2cpp::os::ThreadImpl::ThreadStartWrapper(void*) + 40 260 | 17 libsystem_pthread.dylib 0x00000001b9209840 _pthread_start + 168 (pthread.c:896) 261 | 18 libsystem_pthread.dylib 0x00000001b92119f4 thread_start + 8 262 | 263 | ···· 264 | 265 | Binary Images: 266 | 0x100eb0000 - 0x100eb7fff demo arm64 <14cde28b305d3bffb8abdfab5a30df25> /var/containers/Bundle/Application/03D4FE4F-26BC-47E5-A1D9-B8FF1D379B4A/demo.app/demo 267 | 0x100f80000 - 0x100f8bfff libobjc-trampolines.dylib arm64e <028df612175838e498958fd8c6a9a03c> /usr/lib/libobjc-trampolines.dylib 268 | 269 | ``` 270 | 上面省略了其他线程的信息 271 | 272 | - Incident Identifier : Crash唯一ID 273 | - Hardware Model: 设备型号 274 | - Process: 可执行文件名 275 | - Identifier :Bundle ID 276 | - Code Type : CPU架构 277 | - OS Version: 系统版本号号 278 | - Report Version :崩溃日志版本号 279 | - Exception Type - Termination Reason: 是异常的类型和原因 280 | - Triggered by Thread: 38 : 引发的线程 281 | - Thread 0: 主线程 282 | - 崩溃发生的堆栈 (位置, 地址, 函数符号+ 偏移量): 283 | > 0 libsystem_kernel.dylib 0x00000001b92c8c04 mach_msg_trap + 8 284 | 285 | - Binary Images: 二进制文件内存信息 ( 地址起始-地址结束 名称 CPU架构 **UIID** 路径) 286 | > 0x100eb0000 - 0x100eb7fff demo arm64 <14cde28b305d3bffb8abdfab5a30df25> /var/containers/Bundle/Application/03D4FE4F-26BC-47E5-A1D9-B8FF1D379B4A/demo.app/demo 287 | 288 | 更多详情: 289 | 290 | - [iOS Crash日志结构介绍](https://jianli2017.top/wiki/IOS/crash/1_system_Crash_Type/) 291 | - [了解和分析iOS Crash Report](https://juejin.im/post/6844903774780145678) 292 | 293 | 294 | 295 | 对于Debug环境中我们拿到的Crash日志通常都是带有符号的,也就是我们可以追踪到具体的crash方法和行号。如果Crash堆栈没有解析符号表,这种情况就需要我们手动解析符号表了,当然很多Crash平台都集成了解析符号表的功能,我们只需要自己上传即可。下面我们就介绍下符号表相关的知识。 296 | 297 | ### 解析符号表 298 | 为了能快速并准确地定位用户APP发生Crash的代码位置,我们使用符号表对APP发生Crash的程序堆栈进行解析和还原, 比如一个iOS的crash堆栈: 299 | ![](symbol-retrace.png) 300 | ##### 符号表 301 | 简单来说,符号表是内存地址与函数名,文件名,行号的映射表 302 | > <起始地址> <结束地址> <函数> [<文件名:行号>] 303 | 304 | 305 | - 对于Android工程,一般情况下我们采用 il2cpp 方式构建,unity-2018.4会把符号表自动打包成一个压缩包文件。后缀 `.symbols.zip`, 存放位置和 `.apk` 文件在同一目录。 306 | ![](symbol-path.png) 307 | 308 | - iOS系统的下的符号表就是dSYM文件,文件名通常为:xxx.app.dSYM。我们使用xcode编译完项目后,会生产dSYM文件,和app文件在同一个目录下 309 | 310 | 符号表相关的知识参考: 311 | Bugly官方手册 - [Android符号表](https://bugly.qq.com/docs/user-guide/symbol-configuration-android/?v=20200622202242),[iOS符号表](https://bugly.qq.com/docs/user-guide/symbol-configuration-ios/?v=20200622202242) 312 | 313 | ##### 解析符号表 314 | 拿到符号表,并且确认**UIID一致**的情况下,我们就可以进行工具进行堆栈符号解析了。 315 | 316 | - Android平台下可以使用NDK库中 `add2line ` `ndk-stack` 工具,`ndk-stack`可以对Android的Crash-Log进行提取和解析,非常方便在拿到Crashlog的情况下分析。 317 | 我们通过以下命名可以获取实时的Crash信息 318 | > adb shell logcat | ndk-stack -sym $PROJECT_PATH/obj/local/armeabi 319 | 更多内容请翻阅: 320 | [Android Native crash日志分析](https://www.cnblogs.com/willhua/p/6718379.html) 321 | 322 | - iOS平台下两种工具`symbolicatecrash ` 和`atos ` ,`symbolicatecrash`其实就是对`atos`的封装 323 | 更多内容请翻阅: 324 | [OS Crash分析必备:符号化系统库方法](https://zuikyo.github.io/2016/12/18/iOS%20Crash%E6%97%A5%E5%BF%97%E5%88%86%E6%9E%90%E5%BF%85%E5%A4%87%EF%BC%9A%E7%AC%A6%E5%8F%B7%E5%8C%96%E7%B3%BB%E7%BB%9F%E5%BA%93%E6%96%B9%E6%B3%95/) 325 | 326 | 327 | 328 | ## Unity上报平台的选择 329 | 330 | ### 主流上报平台的对比分析 331 | Unity异常上报通常有四家较为主流的平台, 332 | 333 | - [Bugly](https://bugly.qq.com/docs/user-guide/instruction-manual-plugin-unity/?v=20200312155538) 334 | - [Appcenter](https://docs.microsoft.com/en-gb/appcenter/sdk/crashes/unity) 335 | - [Unity Cloud Diagnostics](https://unitytech.github.io/clouddiagnostics/) 336 | - [Firebase](https://firebase.google.com/docs/crashlytics/get-started?authuser=0&platform=unity) 337 | 338 | 这里对各个平台的特点梳理了一个表格,集成后可以参阅(标绿的部分是**明显优势项**,表格中仅拿Android项目进行了测试统计): 339 | ![](crash-platform.png) 340 | 341 | 根据咱们公司风控要求,**Bugly仅限于只在国内发行的App使用**,**Firebase当前阶段禁止使用**,因此产品的集成平台是建议选用以下组合: 342 | > 国内产品推荐:Bugly, Unity 343 | > 344 | > 海外产品推荐:Appcenter, Unity 345 | 346 | 347 | ### 搭建自己的上报平台 348 | 前面介绍了第三方Crash或者Unity平台,假如我们需要上报到自己的平台,需要分几个方面进行处理,这些可以单独进行集成: 349 | 1. Exception 捕获 350 | 2. Native Crash, 包括Android和iOS平台 351 | 352 | #### 全局异常处理 353 | 前面已经讲过 `Application.logMessageReceivedThreaded`注册接口可以获取全局的Exception,所以Unity托段代码的异常获得还是非常简单的,实现对应的接口方法即可监听。 354 | 355 | #### iOS 异常 356 | iOS平台提供了捕获异常的接口: 357 | 358 | ``` 359 | /* Set the uncaught exception handler */ 360 | NSSetUncaughtExceptionHandler(&uncaught_exception_handler); 361 | ``` 362 | 如果同时使用了注册了第三方的Crash平台, 需要先保存第三方的 ExceptionHandler,然后在设置自己处理 exception 的 ExceptionHandler,在自己的 ExceptionHandler 处理完异常之后,再将异常交给第三方处理。 363 | 364 | 如果需要集成服务端解析符号表,请参考: 365 | [有赞 crash 平台符号化实践](https://www.infoq.cn/article/JLtuf3PGfJV6ovjzu1sq?utm_source=related_read_bottom&utm_medium=article) 366 | 367 | #### Android Java 异常 368 | Android平台也提供了处理Java异常的全局接口 `Thread.UncaughtExceptionHandler`, 我们实现此接口通过 369 | `Thread.setDefaultUncaughtExceptionHandler` 注册给系统即可。同样iOS平台类似,应为此接口值接受一个Handler,需要先通过 `Thread.getDefaultUncaughtExceptionHandler` 获取之前的Handler,再根据需要将捕获的Exception进行转交。 370 | 371 | #### Android Native 异常 372 | Android 平台可以使用 [google breakpad](https://github.com/google/breakpad) 捕获native crash, Appcenter平台就是采用的该方案。 373 | 374 | 375 | ## 异常处理和上报 376 | 377 | 当我们了解了所有的异常类型,也选择了合适的上报平台,最后就可以进行异常上报了。通常第三方都会主动收集异常信息,`AppCenter`这样的平台甚至会主动上报Error的log, `Bugly`的Android平台还是收集 logcat日志信息。 除了平台做的这些事情我们还需要注意两件非常重要的事情,来快速定位Crash: 378 | 379 | 1. 恰当的处理异常:异常的信息能够发现具体的**异常原因** 和 **异常发生位置** 380 | 2. 上报异常之外的其他信息,进行辅助判断,因为很多的native异常位置指向性并不能定位原因。 381 | 382 | #### 恰当的处理异常 383 | 384 | ##### 正确使用try-catch-finially语句处理异常。 385 | 386 | - `try`的作用域尽量单一,方便错误定位。 387 | - `catch`中做恢复资源,如果不能正确处理请上报事件或debug下输出错误,最后或者是重新throw。 388 | - `finally`中清理资源,可以不`catch` Exception,仅使用try-finally做资源闭环处理。 389 | 390 | 391 | 另外更多使用规则请参阅 392 | [Best practices for exceptions](https://docs.microsoft.com/en-us/dotnet/standard/exceptions/best-practices-for-exceptions) 393 | 394 | ##### throw 关键字使用 395 | 396 | 397 | * `throw [e];` 抛出一个异常,调用栈是当前调用方法。 398 | 399 | 注意: 当try-catch中 抛出一个新的异常,捕获的异常的调用栈并不是真正错误来源。如果需要保留catch的Exception,需要使用下面的构建方法: 400 | > `public Exception(string message, Exception innerException);` 401 | 402 | 一个常见的用法: 403 | 404 | ```c# 405 | public class ProcessFile 406 | { 407 | public static void Main() 408 | { 409 | FileStream fs; 410 | try 411 | { 412 | fs = new FileStream("data.txt", FileMode.Open); 413 | var sr = new StreamReader(fs); 414 | // .... 415 | } 416 | catch(FileNotFoundException e) 417 | { 418 | Console.WriteLine($"[Data File Missing] {e}"); 419 | throw new FileNotFoundException(@"[data.txt not in c:\temp directory]", e); 420 | } 421 | finally 422 | { 423 | if (fs != null) 424 | fs.Close(); 425 | } 426 | } 427 | } 428 | ``` 429 | 430 | * `throw;` 重新抛出try-catch的异常 ,通常可作为代码异常的监听,但不做拦截 431 | 432 | ##### 方便确定异常发生的位置 433 | il2cpp目前并不记录异常代码的行号,这对定位产生了一定的困难。以下几个点可以让我们更加方便确定异常的位置: 434 | 435 | - 编写职责单一的方法,方法行数不易过多。 436 | - 异常捕获时提供额外信息。 437 | - 准确记录堆栈路径,通常是自己构建Exception时注意保留Catch的Exception堆栈。 438 | 439 | 440 | #### 上报关键信息 441 | 442 | 我们可以通过反馈入口或者异常捕获拿拿到线上用户的现场信息,为了方便定位Crash,应当尽量提供一下现场信息 443 | 444 | - 设备信息: 包括设备基本类型,系统版本,系统语言,定义的UIID, 内存情况,存储情况(需要单独实现Native接口)等 445 | 446 | ``` 447 | SystemInfo.deviceModel, 448 | SystemInfo.deviceName, 449 | SystemInfo.deviceType, 450 | SystemInfo.deviceUniqueIdentifier, 451 | SystemInfo.operatingSystem, 452 | Localization.language, 453 | 454 | SystemInfo.systemMemorySize, 455 | SystemInfo.processorCount, 456 | SystemInfo.processorType, 457 | Screen.currentResolution.width, 458 | Screen.currentResolution.height, 459 | Screen.dpi, 460 | Screen.fullScreen, 461 | 462 | SystemInfo.graphicsDeviceName, 463 | SystemInfo.graphicsDeviceVendor, 464 | SystemInfo.graphicsMemorySize, 465 | SystemInfo.graphicsPixelFillrate, 466 | SystemInfo.maxTextureSize, 467 | ``` 468 | 469 | - 应用信息:运行时间,异常时所在界面,用户操作的动作链等 470 | - Trace事件:为了方便业务分析,我们在开发过程会上报事件用户埋点分析,除此之外我们可以提供Trace事件类型,主要来统计模块加载和错误输出,发生异常是可以根据场景附加上多个事件集合。 471 | - 日志文件:在可能的情况下,附加当前应用的日志文件,AppCenter提供了附加文件的接口,我们可以将日志文件一并上传。 472 | - 反馈接口:用户提供额外的文字描述和截屏。 473 | 474 | ## 测试题 475 | 1. 关于Unity C# Excepction,下列说法正确的是: 476 | 477 | A. Android程序发生了 C# Exception默认情况下会造成Crash闪退。 478 | 479 | B. `Fast but no Exceptions` 的意思是运行速度快并且不会产生Crash闪退。 480 | 481 | C. 使用 `public Exception(string message, Exception innerException); ` 构造方法可以保留innerException的调用堆栈。 482 | 483 | D. try-catch 不能捕获协程中的异常 484 | 485 | 答案: CD 486 | 487 | 2. Unity LogCallback接口就是日志的回调,可以获得日志,但不能拿到Exception的实例 488 | 489 | 答案: 对(只能拿到Exception的message和callstack属性) 490 | 491 | 3. Native Crash 下列说法错误的是: 492 | A. Java Excpeption会造成程序终止 493 | B. 在符号表的前提下, iOS Crash可以获得异常堆栈的行号 494 | C. 可以使用 `ndk-stack` 和 `atos` 解析 Android native crash 495 | D. dSYM 是iOS编译过程中生成的符号表文件 496 | 497 | 答案 : C 498 | 499 | 4. 虽然国内的Android产品不能使用 `Firebase` ,但海外的产品都可以使用 500 | 501 | 答案 错 502 | 503 | 5. 下列说法正确的是: 504 | 505 | A. throw 关键字可以单独使用,不用跟随Exception对象。 506 | B. 可以不适用catch语句,使用try-finally进行及时的资源释放。 507 | C. 日志文件可以让我们更快的定位Crash原因,各大平台中仅bugly提供Logcat日志获取。 508 | D. 为了方便定位错误发生位置,应当编写职责单一的方法,方法行数不易过多。 509 | 510 | 答案: ABCD 511 | 512 | 513 | -------------------------------------------------------------------------------- /exception-common.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sundxing/Unity-Exception-Crash/c20f1cbbd9e37a4af6e1303b7ec5e4384d5094f3/exception-common.png -------------------------------------------------------------------------------- /symbol-path.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sundxing/Unity-Exception-Crash/c20f1cbbd9e37a4af6e1303b7ec5e4384d5094f3/symbol-path.png -------------------------------------------------------------------------------- /symbol-retrace.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sundxing/Unity-Exception-Crash/c20f1cbbd9e37a4af6e1303b7ec5e4384d5094f3/symbol-retrace.png --------------------------------------------------------------------------------