2024年Android稳定性优化深入解析_android共性问题(3),字节跳动面试是什么意思

面试复习笔记:

这份资料我从春招开始,就会将各博客、论坛。网站上等优质的Android开发中高级面试题收集起来,然后全网寻找最优的解答方案。每一道面试题都是百分百的大厂面经真题+最优解答。包知识脉络 + 诸多细节。
节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。

《960页Android开发笔记》

《1307页Android开发面试宝典》

包含了腾讯、百度、小米、阿里、乐视、美团、58、猎豹、360、新浪、搜狐等一线互联网公司面试被问到的题目。熟悉本文中列出的知识点会大大增加通过前两轮技术面试的几率。

《507页Android开发相关源码解析》

只要是程序员,不管是Java还是Android,如果不去阅读源码,只看API文档,那就只是停留于皮毛,这对我们知识体系的建立和完备以及实战技术的提升都是不利的。

真正最能锻炼能力的便是直接去阅读源码,不仅限于阅读各大系统源码,还包括各种优秀的开源库。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

内存信息

OOMANR、虚拟内存耗尽等,很多崩溃都跟内存有直接关系。如果我们把用户的手机内存分为“2GB 以下”和“2GB 以上”两个桶,会发现“2GB 以下”用户的崩溃率是“2GB 以上”用户的几倍。

  • 系统剩余内存。关于系统内存状态,可以直接读取文件 /proc/meminfo。当系统可用内存很小(低于 MemTotal 的 10%)时,OOM、大量 GC、系统频繁自杀拉起等问题都非常容易出现。
  • 应用使用内存。包括 Java 内存、RSSResident Set Size)、PSSProportional Set Size),我们可以得出应用本身内存的占用大小和分布。
  • 虚拟内存。虚拟内存可以通过 /proc/self/status 得到,通过 /proc/self/maps 文件可以得到具体的分布情况。有时候我们一般不太重视虚拟内存,但是很多类似 OOMtgkill 等问题都是虚拟内存不足导致的。

Name:     com.sample.name   // 进程名
FDSize:   800               // 当前进程申请的文件句柄个数
VmPeak:   3004628 kB        // 当前进程的虚拟内存峰值大小
VmSize:   2997032 kB        // 当前进程的虚拟内存大小
Threads:  600               // 当前进程包含的线程个数

一般来说,对于 32 位进程,如果是 32 位的 CPU,虚拟内存达到 3GB 就可能会引起内存申请失败的问题。如果是 64 位的 CPU,虚拟内存一般在 3~4GB 之间。当然如果我们支持 64 位进程,虚拟内存就不会成为问题。因此我们的应用应该尽量适配64位

资源信息

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

  • 文件句柄 fd。一般单个进程允许打开的最大文件句柄个数为 1024。但是如果文件句柄超过 800 个就比较危险,需要将所有的 fd 以及对应的文件名输出到日志中,进一步排查是否出现了有文件或者线程的泄漏
  • 线程数。一个线程可能就占 2MB 的虚拟内存,过多的线程会对虚拟内存和文件句柄带来压力。根据我的经验来说,如果线程数超过 400 个就比较危险。需要将所有的线程 id 以及对应的线程名输出到日志中,进一步排查是否出现了线程相关的问题。
应用信息

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

  • 崩溃场景。崩溃发生在哪个 ActivityFragment,发生在哪个业务中。
  • 关键操作路径。不同于开发过程详细的打点日志,我们可以记录关键的用户操作路径,这对我们复现崩溃会有比较大的帮助。
  • 其他自定义信息。不同的应用关心的重点可能不太一样,比如网易云音乐会关注当前播放的音乐,QQ 浏览器会关注当前打开的网址或视频。此外例如运行时间、是否加载了补丁、是否是全新安装或升级等信息也非常重要。

上面介绍了在崩溃现场应该采集的信息,当然开发一个这样的采集平台还是很复杂的,大多数情况我们只需要接入一些第三方的平台比如buglySentry即可。但是通过上述介绍,我们可以知道在分析崩溃的时候应该重点关注哪些信息,同时如果平台能力有缺失,我们也可以添加自定义的上报

崩溃分析

在崩溃现场上报了足够的信息之后,我们就可以开始分析崩溃了,下面我们介绍崩溃分析“三部曲”

第一步:确定重点

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

  1. 确认严重程度与优先级。解决崩溃也要看性价比,我们优先解决 Top 崩溃或者对业务有重大影响,
  2. 崩溃基本信息。确定崩溃的类型以及异常描述,对崩溃有大致的判断。一般来说,大部分的简单崩溃经过这一步已经可以得到结论。
  • Java 崩溃。Java 崩溃类型比较明显,比如 NullPointerException 是空指针,OutOfMemoryError 是资源不足,这个时候需要去进一步查看日志中的 “内存信息”和“资源信息”。
  • Native 崩溃。需要观察 signalcodefault addr 等内容,以及崩溃时 Java 的堆栈。关于各 signal 含义的介绍,你可以查看 崩溃信号介绍 。比较常见的是有 SIGSEGVSIGABRT,前者一般是由于空指针、非法指针造成,后者主要因为 ANR 和调用 abort() 退出所导致。
  • ANR。我的经验是,先看看主线程的堆栈,是否是因为锁等待导致。接着看看 ANR 日志中 iowaitCPUGCsystem server 等信息,进一步确定是 I/O 问题,或是 CPU 竞争问题,还是由于大量 GC 导致卡死
  1. LogcatLogcat 一般会存在一些有价值的线索,日志级别是 WarningError 的需要特别注意。从 Logcat 中我们可以看到当时系统的一些行为跟手机的状态,例如出现 ANR 时,会有“am_anr”;App 被杀时,会有“am_kill”。不同的系统、厂商输出的日志有所差别,当从一条崩溃日志中无法看出问题的原因,或者得不到有用信息时,不要放弃,建议查看相同崩溃点下的更多崩溃日志
  2. 各个资源情况。结合崩溃的基本信息,我们接着看看是不是跟 “内存信息” 有关,是不是跟“资源信息”有关。比如是物理内存不足、虚拟内存不足,还是文件句柄 fd 泄漏了。

无论是资源文件还是 Logcat,内存与线程相关的信息都需要特别注意,很多崩溃都是由于它们使用不当造成的。

第二步:查找共性

如果使用了上面的方法还是不能有效定位问题,我们可以尝试查找这类崩溃有没有什么共性。找到了共性,也就可以进一步找到差异,离解决问题也就更进一步。

机型、系统、ROM、厂商、ABI,这些采集到的系统信息都可以作为维度聚合,共性问题例如是不是因为安装了 Xposed,是不是只出现在 x86 的手机,是不是只有三星这款机型,是不是只在 Android 5.0 的系统上。应用信息也可以作为维度来聚合,比如正在打开的链接、正在播放的视频、国家、地区等。找到了共性,可以对你下一步复现问题有更明确的指引。

第三步:尝试复现

如果我们已经大概知道了崩溃的原因,为了进一步确认更多信息,就需要尝试复现崩溃。如果我们对崩溃完全没有头绪,也希望通过用户操作路径来尝试重现,然后再去分析崩溃原因。

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

系统崩溃解决

有时有些崩溃并不是我们应用的问题,而是系统的问题,系统崩溃系统崩溃常常令我们感到非常无助,它可能是某个 Android 版本的 bug,也可能是某个厂商修改 ROM 导致。
这种情况下的崩溃堆栈可能完全没有我们自己的代码,很难直接定位问题。

针对这种疑难问题,我们可以尝试通过以下方法解决。

  1. 查找可能的原因。通过上面的共性归类,我们先看看是某个系统版本的问题,还是某个厂商特定 ROM 的问题。虽然崩溃日志可能没有我们自己的代码,但通过操作路径和日志,我们可以找到一些怀疑的点。
  2. 尝试规避。查看可疑的代码调用,是否使用了不恰当的 API,是否可以更换其他的实现方式规避。
  3. Hook 解决。在了解了原因之后,最后可以通过Hook的方式修改系统代码的逻辑来处理

比如我们发现线上出现一个 Toast 相关的系统崩溃,它只出现在 Android 7.0 的系统中,看起来是在 Toast 显示的时候窗口的 token 已经无效了。这有可能出现在 Toast 需要显示时,窗口已经销毁了。

android.view.WindowManager$BadTokenException: 
  at android.view.ViewRootImpl.setView(ViewRootImpl.java)
  at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java)
  at android.view.WindowManagerImpl.addView(WindowManagerImpl.java4)
  at android.widget.Toast$TN.handleShow(Toast.java)

为什么 Android 8.0 的系统不会有这个问题?在查看 Android 8.0 的源码后我们发现有以下修改:

try {
  mWM.addView(mView, mParams);
  trySendAccessibilityEvent();
} catch (WindowManager.BadTokenException e) {
  /* ignore */
}

因此我们可以参考 Android 8.0 的做法,直接 catch 住这个异常。这里的关键在于寻找 Hook 点,Toast 里面有一个变量叫 mTN,它的类型为 handler,我们只需要代理它就可以实现捕获。

Crash长效治理

上面介绍了处理线上Crash的一般步骤,但是Crash治理真正重要的阶段在上线之前,我们需要从开发阶段开始,系统性的进行Crash长效治理

开发阶段

Crash的长效治理需要从开发阶段抓起,从长远来说,更好的代码质量将带来更好的稳定性,我们可以从以下两个角度来提升代码质量

  • 统一编码规范、增强编码功底、技术评审、增强CodeReview机制
  • 架构优化,能力收敛(即将一些常见的操作进行封装),统一容错:如在网络库utils中统一对返回信息进行预校验,如不合法就直接不走接下来的流程。
测试阶段

除了功能测试、自动化测试、回归测试、覆盖安装等常规测试流程之外,还需要针对特殊场景、机型等边界进行测试:如服务端返回异常数据、服务端宕机等情况

合码阶段
  • 在我们的功能开发完毕,即将合并到主分支时,首先要进行编译检测、静态扫描,来发现可能存在的问题。
  • 在扫描完成后也不能直接合入,因为多个分支可能会冲突,因此我们先进行一个预编译流程,即合入一个与主分支一样的分支、然后打包进行主流程自动化回归测试,流程通过后再合入主分支。当然这样做可能比较麻烦,但这些步骤应该都是自动化的
发布阶段
  • 在发布阶段,我们应该进行多轮灰度,灰度量级应逐渐由小变大,用最小的代价提前暴露问题
  • 灰度发布同样应该分场景、多纬度全面覆盖,可以针对特别的版本,机型等进行专门的灰度,看下那些更有可能出现问题的用户是否出现问题
运维阶段
  • 在上线之后,稳定性问题同样需要关注,因此特别依赖于APM的灵敏监控,发现问题及时报警
  • 如果出现了异常情况,也需要根据情况进行回滚或者降级策略
  • 如果不能回滚或降级的话,也可以采用热修复的方式来修复、如果热修复也失效的话,只能依赖于本地容灾方案来恢复

业务高可用方案建设

很多人认为稳定性优化就是降低Crash率,但其实稳定性优化还有一个重要的维度就是业务的高可用。
业务的不可用可能不会导致崩溃,但是会降低用户的体验,从而直接影响我们的收入

业务高可用方案建设
  1. 业务高可用不像Crash,需要我们自己打点做数据采集。我们需要梳理项目主流程、核心路径、关键节点,并添加打点
  2. 数据采集我们也可以采用AOP方式采集,减少手动打点的成本。
  3. 数据上报之后,我们可以建立数据大盘,统计每个步骤的转化率。
  4. 在数据之报之后,我们也可以建立报警策略,比如阈值报警、趋势报警(相比同期减少)、特定指标报警(比如支付失败)
  5. 同时我们可以做一些异常监控的工作,比如Catch住的异常与异常逻辑的上报,这些异常虽然不会崩溃,但也是我们需要关注的
  6. 针对一些难以解决的问题,我们可以针对特定用户采用全量日志回捞的方式来采集更多信息,
  7. 在发现了异常之后,我们可以通过一些兜底策略来解决问题,比如支持通过配置中心配置功能开关是否打开,当发现某个新功能有问题时,我们可以直接隐藏该功能,或者通过配置路由的方式跳转到另一个方式
客户端容灾方案

在性能或者业务异常发生了之后,我们该如何解决呢?传统的流程需要经过用户反馈,重新打包,渠道更新等多个步骤,可以看出其实比较麻烦,对用户的响应度也比较低
我们可以从以下角度来进行客户端的容灾方案建设

  1. 对于新加的功能或者代码重构,支持通过配置中心配置开关,如果发生问题可以及时关闭
  2. 同时如果我们的App所有的页面都是通过路由跳转的,可以通过动态配置路由的方式跳转到统一错误处理页面,或者跳转到临时h5页面
  3. 通过热修复技术修复BUG,比如接入腾讯的Tinker或者美团的Robust
  4. 如果你的项目使用了RN或者Weex,可直接实现增量更新
  5. 如果崩溃发生在刚启动APP时,这时候动态更新动态配置就都失效了,这个时候就需要用到安全模式。安全模式根据Crash信息自动恢复,多次启动失败重置应用为安装初始状态。如果是特别严重的Bug,也可以通过阻塞性热修复的方式来解决,即热修成功了才能进入APP。安全模式不仅可以用于APP,也可用于组件,如果某个组件多次报错,就可以进入兜底页面

稳定性优化常见面试题

下面介绍一下稳定性优化的模拟面试题

你们做了哪些稳定性方面的优化?

参考答案:

随着项目的逐渐成熟,用户基数逐渐增多,DAU持续升高,我们遇到了很多稳定性方面的问题,对于我们技术同学遇到了很多的挑战,用户经常使用我们的App卡顿或者是功能不可用,因此我们就针对稳定性开启了专项的优化,我们主要优化了三项:

  • Crash专项优化
  • 性能稳定性优化
  • 业务稳定性优化

通过这三方面的优化我们搭建了移动端的高可用平台。同时,也做了很多的措施来让App真正地实现了高可用。

性能稳定性是怎么做的?

参考答案:

  • 全面的性能优化:启动速度、内存优化、绘制优化
  • 线下发现问题、优化为主
  • 线上监控为主
  • Crash专项优化

我们针对启动速度,内存、布局加载、卡顿、瘦身、流量、电量等多个方面做了多维的优化。

我们的优化主要分为了两个层次,即线上和线下,针对于线下呢,我们侧重于发现问题,直接解决,将问题尽可能在上线之前解决为目的。而真正到了线上呢,我们最主要的目的就是为了监控,对于各个性能纬度的监控呢,可以让我们尽可能早地获取到异常情况的报警。

同时呢,对于线上最严重的性能问题性问题:Crash,我们做了专项的优化,不仅优化了Crash的具体指标,而且也尽可能地获取了Crash发生时的详细信息,结合后端的聚合、报警等功能,便于我们快速地定位问题。

业务稳定性如何保障?

参考答案:

尾声

一转眼时间真的过的飞快。我们各奔东西,也各自踏上了自己的旅途,但是即使多年不见,也因为这份情谊我们依旧如从前那般“亲密”。不忘初心方得始终。加油吧,程序员们,在我看来35岁,40岁从来不是危机,只要永远不要忘记自己为何踏上征程!

为了让更多在学习中或者最近要准备面试的朋友们看到这篇文章,希望你们能多多评论,点赞+转发!

再次感谢所有给我提供过题目的朋友们,感谢一路有你!

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

或者最近要准备面试的朋友们看到这篇文章,希望你们能多多评论,点赞+转发!

再次感谢所有给我提供过题目的朋友们,感谢一路有你!

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值