面试题:Android性能优化之崩溃优化+卡顿优化

崩溃优化

崩溃分析

崩溃率是衡量一个应用质量高低的基本指标:

  1. Android 崩溃分为 Java 崩溃和 Native 崩溃:

  1. Java 崩溃就是在 Java 代码中,出现了未捕获异常,导致程序异常退出;

  1. Native 崩溃又是怎么产生的呢?一般都是因为在 Native 代码中访问非法地址, 也可能是地址对齐出现了问题,或者发生了程序主动 abort,这些都会产生相应的 signal 信号,导致程序异常退出。

  1. Native崩溃的捕获流程

完整的 Native 崩溃从捕获到解析的流程:

  1. 编译端。编译 C/C++ 代码时,需要将带符号信息的文件保留下来

  1. 客户端。捕获到崩溃时候,将收集到尽可能多的有用信息写入日志文件,然后选择合适的时机上传到服务器。

  1. 服务端。读取客户端上报的日志文件,寻找适合的符号文件,生成可读的 C/C++ 调用栈。

Native 崩溃捕获的难点

上面的三个流程中,最核心的是怎么样保证客户端在各种极端情况下依然可以生成崩溃日志。因为在崩溃时,程序会处于一个不安全的状态,如果处理不当,非常容易发生二次崩溃。那么,生成崩溃日志时会有哪些比较棘手的情况呢?

情况一: 文件句柄泄漏,导致创建日志文件失败,怎么办?应对方式:我们需要提前申请文件句柄 fd 预留,防止出现这种情况。

情况二: 因为栈溢出了,导致日志生成失败,怎么办?应对方式:为了防止栈溢出导致进程没有空间创建调用栈执行处理函数,我们通常会使用常见的 signalstack。在一些特殊情况,我们可能还需要直接替换当前栈,所以这里也需要在堆中预留部分空间。

情况三: 整个堆的内存都耗尽了,导致日志生成失败,怎么办?应对方式:这个时候我们无法安全地分配内存,也不敢使用 stl 或者 libc 的函数,因为它们内部实现会分配堆内存。这个时候如果继续分配内存,会导致出现堆破坏或者二次崩溃的情况。Breakpad 做的比较彻底,重新封装了Linux Syscall Support,来避免直接调用 libc。

情况四: 堆破坏或二次崩溃导致日志生成失败,怎么办?应对方式:Breakpad 会从原进程 fork 出子进程去收集崩溃现场,此外涉及与 Java 相关的,一般也会用子进程去操作。这样即使出现二次崩溃,只是这部分的信息丢失,我们的父进程后面还可以继续获取其他的信息。在一些特殊的情况,我们还可能需要从子进程 fork 出孙进程。

选择合适的崩溃服务

对于很多中小型公司来说,并不建议自己去实现一套如此复杂的系统,可以选择一些第三方的服务。目前各种平台也是百花齐放,包括腾讯的Bugly、阿里的啄木鸟平台、网易云捕、Google 的 Firebase 等等

崩溃现场应采集哪些信息

1. 崩溃信息:
  • 进程名、线程名:

崩溃的进程是前台进程还是后台进程,崩溃是不是发生在 UI 线程。

  • 崩溃堆栈和类型:

属于 Java 崩溃、Native 崩溃,还是 ANR

2. 系统信息
  • Logcat: 这里包括应用、系统的运行日志

  • 机型、系统、厂商、CPU、ABI、Linux 版本等

  • 设备状态:是否root、是否是模拟器

3. 内存信息

OOM、ANR、虚拟内存耗尽等,很多崩溃都跟内存有直接关系

  • 系统剩余内存:

当系统可用内存很小(低于 MemTotal 的 10%)时,OOM、大量 GC、系统频繁自杀拉起等问题都非常容易出现

  • 应用使用内存:

包括 Java 内存、RSS(Resident Set Size)、PSS(Proportional Set Size),我们可以得出应用本身内存的占用大小和分布

  • 虚拟内存:

可以通过 /proc/self/status 得到,通过 /proc/self/maps 文件可以得到具体的分布情况,有时候我们一般不太重视虚拟内存,但是很多类似 OOM、tgkill 等问题都是虚拟内存不足导致的

4. 资源信息

有的时候我们会发现应用堆内存和设备内存都非常充足,还是会出现内存分配失败的情况,这跟资源泄漏可能有比较大的关系

  • 文件句柄 fd:

文件句柄的限制可以通过 /proc/self/limits 获得,一般单个进程允许打开的最大文件句柄个数为 1024。 但是如果文件句柄超过 800 个就比较危险,需要将所有的 fd 以及对应的文件名输出到日志中,进一步排查是否出现了有文件或者线程的泄漏

  • 线程数:

当前线程数大小可以通过上面的 status 文件得到,一个线程可能就占 2MB 的虚拟内存,过多的线程会对虚拟内存和文件句柄带来压力。 根据我的经验来说,如果线程数超过 400 个就比较危险。需要将所有的线程 id 以及对应的线程名输出到日志中,进一步排查是否出现了线程相关的问题。

  • JNI:

使用 JNI 时,如果不注意很容易出现引用失效、引用爆表等一些崩溃。我们可以通过 DumpReferenceTables 统计 JNI 的引用表,进一步分析是否出现了 JNI 泄漏等问题。

5. 应用信息

除了系统,其实我们的应用更懂自己,可以留下很多相关的信息

  • 崩溃场景

崩溃发生在哪个 Activity 或 Fragment,发生在哪个业务中

  • 关键操作路径

不同于开发过程详细的打点日志,我们可以记录关键的用户操作路径,这对我们复现崩溃会有比较大的帮助

  • 其他自定义信息

不同的应用关心的重点可能不太一样,比如网易云音乐会关注当前播放的音乐,QQ 浏览器会关注当前打开的网址或视频。此外例如运行时间、是否加载了补丁、是否是全新安装或升级等信息也非常重要。

6. 其他信息

除了上面这些通用的信息外,针对特定的一些崩溃,我们可能还需要获取类似磁盘空间、电量、网络使用等特定信息。所以说一个好的崩溃捕获工具,会根据场景为我们采集足够多的信息,让我们有更多的线索去分析和定位问题。当然数据的采集需要注意用户隐私,做到足够强度的加密和脱敏。

崩溃分析 三部曲

第一步:确定重点

确认和分析重点,关键在于在日志中找到重要的信息,对问题有一个大致判断。一般来说,我建议在确定重点这一步可以关注以下几点。

1. 确认严重程度

解决崩溃也要看性价比,我们优先解决 Top 崩溃或者对业务有重大影响,例如启动、支付过程的崩溃。

2. 崩溃基本信息

确定崩溃的类型以及异常描述,对崩溃有大致的判断。

  1. Java 崩溃类型比较明显

  1. Native 崩溃: 需要观察 signal、code、fault addr 等内容,以及崩溃时 Java 的堆栈;

  1. ANR: 先看看主线程的堆栈,是否是因为锁等待导致。接着看看 ANR 日志中 iowait、CPU、GC、system server 等信息,进一步确定是 I/O 问题,或是 CPU 竞争问题,还是由于大量 GC 导致卡死。

3. Logcat

Logcat 一般会存在一些有价值的线索,日志级别是 Warning、Error 的需要特别注意。从 Logcat 中我们可以看到当时系统的一些行为跟手机的状态,例如出现 ANR 时,会有“am_anr”;App 被杀时,会有“am_kill”。

4. 各个资源情况

结合崩溃的基本信息,我们接着看看是不是跟 “内存信息” 有关,是不是跟“资源信息”有关。比如是物理内存不足、虚拟内存不足,还是文件句柄 fd 泄漏了。

第二步:查找共性

如果使用了上面的方法还是不能有效定位问题,我们可以尝试查找这类崩溃有没有什么共性。找到了共性,也就可以进一步找到差异,离解决问题也就更进一步 机型、系统、ROM、厂商、ABI,这些采集到的系统信息都可以作为维度聚合,找到了共性,可以对你下一步复现问题有更明确的指引。

第三步:尝试复现

“只要能本地复现,我就能解”,相信这是很多开发跟测试说过的话。有这样的底气主要是因为在稳定的复现路径上面,我们可以采用增加日志或使用 Debugger、GDB 等各种各样的手段或工具做进一步分析。

疑难问题:系统崩溃 的解决思路

1. 查找可能的原因。

通过上面的共性归类,我们先看看是某个系统版本的问题,还是某个厂商特定 ROM 的问题。虽然崩溃日志可能没有我们自己的代码,但通过操作路径和日志,我们可以找到一些怀疑的点。

2. 尝试规避。

查看可疑的代码调用,是否使用了不恰当的 API,是否可以更换其他的实现方式规避。

3. Hook 解决。

这里分为 Java Hook 和 Native Hook。

崩溃攻防是一个长期的过程,我们希望尽可能地提前预防崩溃的发生,将它消灭在萌芽阶段。这可能涉及我们应用的整个流程,包括人员的培训、编译检查、静态扫描工作,还有规范的测试、灰度、发布流程等。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值