1.打包时,尽可能保留符号表和对应的il2cppoutputproject下被il2cpp翻译后的代码。

因为有符号表才能解析闪退堆栈,所以我把该点提到最前面说。
不同的unity版本获取符号表的方式不同。
具体参考对应版本的官网文档(但对于unity2021及其以上,这个文档好久就过期了,目前根本没有生成symbols.zip文件,而且也没有sym.so文件,符号表都是so文件): https://docs.unity3d.com/Manual/android-symbols.html
以unity2021为例:
debug包:
libmain、libunity、libil2cpp符号表在打包后的 [安卓工程目录]/unityLibrary/symbols/ 目录下
il2cpp翻译后的代码在 [安卓工程目录]/unityLibrary/src/main/Il2CppOutputProject/ 目录下
release包:
il2cpp符号需要通过代码拷贝到指定位置来获取。
最好在程序代码中添加个测试il2cpp闪退的接口,后续可以方便测试符号表解析是否正常,比如bugly的符号表上传后是否可以正常解析。

2.争取获取崩溃当时的logcat日志

如果闪退发生在已有的手机中:
一般手机logcat会保存较长时间,至少半天左右的上下文是完整的(完整指包含调试级别日志)。
如果超过半天或一天以上,上下文是只会保留ERROR级别的日志。
如果超过好几天或者日志量达到一定范围,之前的日志会全部抛弃。
所以若闪退手机在手,拿日志最好及时,推荐包内内置些工具自动上传。

2.1 logcat日志的获取

2.1.1 android studio或者eclipse编辑器查看logcat日志

如果本地安装android studio或者eclipse编辑器自带的logcat工具查看日志,但该日志相对实时,当闪退发生后再连logcat查看可能会错过有用的信息。

2.1.2 adb工具查看logcat日志

如果本地没有安装上述软件,可以采用adb自带的logcat选项,这个比较轻量。(个人推荐,每次logcat都是全量日志,若您熟悉shell命令,分析起来非常方便。)
可以通过USB数据线连接,只需要adb devices就可以自动连接。(连接的稳定性取决于数据线/接口的好坏)
也可通过其他工具打开设备的端口,通过adb远程连接指定端口。(连接稳定性取决于网络的稳定和可达,但是让设备开放端口的手段相对麻烦)

adb通过USB数据线连接:

插入数据线后,命令行执行如下命令:

adb.exe devices // 若是连接成功,则执行下一步,如果连接不成功,则检查是不是默认的5555端口被占用、线是不是没插好或者根据连接失败的提示检查下adb版本是否对得上设备的adbd服务的版本。
adb.exe logcat // 这个直接打印到控制台
adb.exe logcat > 221008crash.log // 将日志重定向输出到本地221008crash.log文件。

      adb通过wifi连接(adb打开端口方式):

        首先确保设备的网络可达。
          1.带公网ip的网络,就是外部网络能访问到的;
          2.不带公网ip,即外部网络访问不到,但是内部机器可以访问,且同一个网段。
        通过“adb通过USB数据线连接”方式连接设备后

adb.exe shell ifconfig // 查看一下ip,假设ip为10.18.58.78
adb.exe tcpip 56999 // 通过tcpip打开设备自定义的56999端口。
可以断开数据线。
adb.exe connect 10.18.58.78:56999
adb.exe logcat > 221008crash.log // 将日志重定向输出到本地221008crash.log文件。

        打开设备端口的手段麻烦不仅如此,若没有数据线,你可能得通过app工具打开,有些设备通过拨号来开放adbd的端口,个人就没有深入这些黑魔法了。

    2.1.3 unity官方提供的android logcat插件获取logcat日志

      还可以尝试unity官方提供的android logcat插件(个人觉得启动unity会比较慢,而且日志相对实时,除非保持连接,否则当闪退发生后再查看可能会错过有用的信息。)
      可以分别在Unity Registry和My Registry中搜索一下“logcat”关键字就可以出来。点击“install”进行安装。

    2.1.4 若使用外网bugly闪退采集:

      该工具时免费的,但是会有些限制,具体参看官方文档:https://bugly.qq.com/docs/
      在“异常上报”->"崩溃分析"->“跟踪日志”可以看到闪退当时的日志,但是这段日志很短,而logcat日志本身很冗长,很多有用信息会被截断。
      解决该问题的方法:
        对于甚至堆栈信息被截断的情况,但仍可在“出错堆栈”中看到堆栈记录。
        因为不同设备上报日志完备程度不一,有些机型本身的闪退日志相对完备,通过同类闪退的不同机型查看日志。
        汇总同类闪退的机型,通过真机日志获取完备日志。
      它还有个牛逼的功能,就是可添加自定义崩溃回调,比如此时可以采集一些显存之类的信息,但若这块自定义代码本身可能导致崩溃,就可能影响外网上报,所以推荐只是在内部设备最追踪特定难查问题时开启。
      具体参考官方文档:https://bugly.qq.com/docs/user-guide/advance-features-android/?v=1.0.0#crash

    2.1.5 若使用外网testin闪退采集:

      首先,这是个收费的服务。
      testin会提供兼容性测试报告,在报告的“问题描述”这栏会提供闪退当时的详情“查看日志及详情”,点击它会跳到该问题的详情网址。
      在“日志展示”中的“单款日志”下载到相对完整的logcat日志。
      值得推荐的是testin有当时的录像,在“视频信息”这栏。
      回放的好处可不少:
        闪退回到桌面,可以很快速看到闪退时间点,方便跟进时间点看日志。
        进度条的白点就是闪退点(有些视频没有显示白点可能是未能准确解析出闪退时间点)。
        对于因为高温导致的这类闪退,就很方便看出来,加上日志搜索高温字样,更是能巩固证据。

          同时检索一下日志中高温提示,让你的证据更有力。
      个人认为有资本的条件下,推荐阶段性买个服务跑测下。

3.日志分析:

  3.1 若有堆栈,则解析堆栈

    大多数闪退都是带堆栈,只是闪退不一定发生在UnityMain这个线程,这样堆栈则不会带有unity的堆栈;也存在一部分闪退是没有留下任何堆栈的。

    3.1.1 addr2line的环境配置

      如果你本地本身安装了ndk环境,比如通过android studio或者eclipse,可以先试试命令行下敲如下命令,检测addr2line命令是否存在。

$ addr2line.exe -v
GNU addr2line (GNU Binutils) 2.38
This program is free software; you may redistribute it under the terms of
the GNU General Public License version 3 or (at your option) any later version.
This program has absolutely no warranty.

      若是addr2line命令不存在,可以下载个ndk,解压后,添加addr2line所在的路径到环境变量即可。
      (添加到环境变量是让你addr2line随处可用。)
      addr2line执行文件一般在android-ndk-r25b-windows\android-ndk-r25b\toolchains\llvm\prebuilt\windows-x86_64\bin目录下。
      我本地的路径如下:
        D:\down_cache\android-ndk-r25b-windows\android-ndk-r25b\toolchains\llvm\prebuilt\windows-x86_64\bin
      将其拷贝到环境变量中的path变量里,这样就可以随处可用了。

    3.1.2 addr2line的使用:

      一般使用-f -C -e参数就够了,甚至不看参数说明也能大致明白输出的含义。
      具体参数可参考:https://blog.csdn.net/weixin_42186870/article/details/116502987
      首先要明确这个堆栈意义:
        它只是发生闪退的线程当时运行记录。
        它不一定就是闪退的原因,因为闪退可能由第三方软件强制中止应用导致。
        若闪退发生在线程内部,那么这个堆栈可能能为我们提供:
          闪退可能所属的模块,可能发生在代码位置。(这也不是一定的)
          根据信号和闪退描述判断造成闪退的行为是什么。
          根据cpu架构类型查看项目本身是否支持。
          提供了该线程的pid、tid、uid(大多logcat打印的是pid)。

      经过il2cpp翻译过的堆栈,从名称上比较难看到原本的函数名。你可以根据堆栈显示的名称,到对应的il2cppoutpuproject找对应的文件,查看调用的上下文,进而推断原本的代码。
      若堆栈就是闪退的根本原因,最好能构造重现的环境,修复后加以真机验证。
      网上已提供现成的方便解析堆栈的脚本,推荐集成到项目的工具集中提高查问题的效率。

  3.2 若没有堆栈,可根据日志上下文分析:

    首先可以根据时间段加些关键字做日志的过滤,我这汇总了一些可过滤的关键字(根据具体情况组合使用)

// 异常的关键字,加上项目包名关键字或者项目特定日志标记
dump\|err\|fail\|crash\|outof\|out of\|deni\|invalid\| E \| W \| no \| not \|excep\|large\| too \|miss\| lost \|stop\|unity\|il2cpp\| E\/\|overf\|access\|kill\|leak\|pointer\|destroy\|nosuch\|unknown\|unsupport\|null\|anr\|protect
// 可过滤的字符串
MiuiGesturePointerEventListener\|Monkey\|LocSvc_api_v02\|WifiService\|WifiScanRequestProxy\|slim_daemon\|TelephonyIcons\|adbd\|audio\|Avrcp_ext\|BatteryStat\|StaticLayout\|Parsing\|GF_\|set_timerslack_ns\|Launcher\|hwservicemanager_prop\|E\/libc\|The referenced script\|pushService\|getDisplaySideSafeInsets\|CheckCldLayerValid\|WaitGpuAcquireFence
adbd:adb服务器
avrcp_ext:音量协议
batterystat:电量统计。
// 可能可以过滤的字符串
I\/Unity\|
// 具体shell命令用法案例
grep -nvi 'MiuiGesturePointerEventListener\|Monkey\|LocSvc_api_v02\|WifiService\|WifiScanRequestProxy\|slim_daemon\|TelephonyIcons\|adbd\|audio\|Avrcp_ext\|BatteryStat\|StaticLayout\|Parsing\|GF_\|set_timerslack_ns\|Launcher\|hwservicemanager_prop\|E\/libc\|The referenced script\|pushService\|getDisplaySideSafeInsets\|CheckCldLayerValid\|WaitGpuAcquireFence\|MemoryCeilingMonitor' ./crash.log | grep -i 'dump\|err\|fail\|crash\|outof\|out of\|deni\|invalid\| E \| W \| no \| not \|excep\|large\| too \|miss\| lost \|stop\|unity\|il2cpp\| E\/\|overf\|access\|kill\|leak\|pointer\|destroy\|nosuch\|unknown\|unsupport\|null\|anr\|protect' --color > bref_crash.log.log

    先确定哪条日志是闪退发生后,从该条日志向上追查。
    若闪退是第三方强制中止引起,这时闪退堆栈中提供的pid可能会提供关键作用,pid结合“kill、stop、force”等关键字检索上下文。
      还记得不久前有次华为真机频频发生闪退,日志显示app被pid为2567的进程强行终止,根据检索该进程为华为的省电精灵Powergenie。

4.总结:

  分析崩溃的方法其实有很多。除了闪退堆栈,没有固定的报错提示,实际上还是得具体情况具体分析,无非是通过现有的数据来证明“凶手”是谁,后续如何规避。后续我会补充真机调试的分享。
  若是本文对您有小小的帮助,记得三连,或请我喝瓶肥仔水,万分感谢。