深刻探索Android稳定性优化

前言

成为一名优秀的Android开发,须要一份完备的知识体系,在这里,让咱们一块儿成长为本身所想的那样~。

众所周知,移动开发已经来到了后半场,为了可以在众多开发者中脱颖而出,咱们须要对某一个领域有深刻地研究与心得,对于Android开发者来讲,目前,有几个好的细分领域值得咱们去创建本身的技术壁垒,以下所示:html

  • 一、性能优化专家:具有深度性能优化与体系化APM建设的能力
  • 二、架构师:具备丰富的应用架构设计经验与心得,对Android Framework层与热门三方库的实现原理与架构设计了如指掌。
  • 三、音视频/图像处理专家:毫无疑问,掌握NDK,深刻音视频与图像处理领域能让咱们在将来几年大放异彩。
  • 四、大前端专家:深刻掌握Flutter及其设计原理与思想,可让咱们具备快速学习前端知识的能力。

在上述几个细分领域中,最难也最具技术壁垒的莫过于性能优化,要想成为一个顶尖的性能优化专家,须要对许多领域的深度知识及广度知识有深刻的了解与研究,其中不乏须要掌握架构师、NDK、Flutter所涉及的众多技能。从这篇文章开始,笔者将会带领你们一步一步深刻探索Android的性能优化。前端

为了可以全面地了解Android的性能优化知识体系,咱们先看看我总结的下面这张图,以下所示:linux

image

要作好应用的性能优化,咱们须要创建一套成体系的性能优化方案,这套方案被业界称为 APM(Application Performance Manange),为了让你们快速了解APM涉及的相关知识,笔者已经将其总结成图,以下所示:android

image

在建设APM和对App进行性能优化的过程当中,咱们必须首先解决的是App的稳定性问题,如今,让咱们搭乘航班,来深刻探索Android稳定性优化的疆域git

思惟导图大纲

目录

  • 1、正确认识
    • 一、稳定性纬度
    • 二、稳定性优化注意事项
    • 三、Crash 相关指标
    • 四、Crash 率评价
    • 五、Crash 关键问题
    • 六、APM Crash 部分总体架构
    • 七、责任归属
  • 2、Crash 优化
    • 一、单个 Crash 处理方案
    • 二、Crash 率治理方案
    • 三、Java Crash
    • 四、Java Crash 处理流程
    • 五、Native Crash
    • 六、疑难 Crash 解决方案
    • 七、进程保活
    • 八、总结
  • 3、ANR 优化
    • 一、ANR 监控实现方式
    • 二、ANR 优化
    • 三、关于 ANR 的一些常见问题
    • 四、理解 ANR 的触发流程
  • 4、移动端业务高可用方案建设
    • 一、业务高可用重要性
    • 二、业务高可用方案建设
    • 三、移动端容灾方案
  • 5、稳定性长效治理
    • 一、开发阶段
    • 二、测试阶段
    • 三、合码阶段
    • 四、发布阶段
    • 五、运维阶段
  • 6、稳定性优化问题
    • 一、大家作了哪些稳定性方面的优化?
    • 二、性能稳定性是怎么作的?
    • 三、业务稳定性如何保障?
    • 四、若是发生了异常状况,怎么快速止损?
  • 7、总结

1、正确认识

首先,咱们必须对App的稳定性有正确的认识,它是App质量构建体系中最基本和最关键的一环。若是咱们的App不稳定,而且常常不能正常地提供服务,那么用户大几率会卸载掉它。因此稳定性很重要,而且Crash是P0优先级,须要优先解决。 并且,稳定性可优化的面很广,它不只仅只包含Crash这一部分,也包括卡顿、耗电等优化范畴。github

一、稳定性纬度

应用的稳定性能够分为三个纬度,以下所示:面试

  • 一、Crash纬度:最重要的指标就是应用的Crash率
  • 二、性能纬度:包括启动速度、内存、绘制等等优化方向,相对于Crash来讲是次要的,在作应用性能体系化建设以前,咱们必需要确保应用的功能稳定可用。
  • 三、业务高可用纬度:它是很是关键的一步,咱们须要采用多种手段来保证咱们App的主流程以及核心路径的稳定性,只有用户常用咱们的App,它才有可能发现别的方面的问题。

二、稳定性优化注意事项

咱们在作应用的稳定性优化的时候,须要注意三个要点,以下所示:算法

一、重在预防、监控必不可少

对于稳定性来讲,若是App已经到了线上才发现异常,那其实已经形成了损失,因此,对于稳定性的优化,其重点在于预防。从开发同窗的编码环节,到测试同窗的测试环节,以及到上线前的发布环节、上线后的运维环节,这些环节都须要来预防异常状况的发生。若是异常真的发生了,也须要将千方百计将损失降到最低,争取用最小的代价来暴露尽量多的问题。shell

此外,监控也是必不可少的一步,预防作的再好,到了线上,总会有各类各样的异常发生。因此,不管如何,咱们都须要有全面的监控手段来更加灵敏地发现问题json

二、思考更深一层、重视隐含信息:如解决Crash问题时思考是否会引起同一类问题

当咱们看到了一个Crash的时候,不能简单地只处理这一个Crash,而是须要思考更深一层,要考虑会不会在其它地方会有同样的Crash类型发生。若是有这样的状况,咱们必须对其统一处理和预防

此外,咱们还要关注Crash相关的隐含信息,好比,在面试过程中,面试官问你,大家应用的Crash率是多少,这个问题代表上问的是Crash率,可是实际上它是问你一些隐含信息的,太高的Crash率就表明开发人员的水平不行,leader的架构能力不行,项目的各个阶段中优化的空间很是大,这样一来,面试官对你的印象和评价也不会好。

三、长效保持须要科学流程

应用稳定性的建设过程是一个细活,因此很容易出现这个版本优化好了,可是在接下来的版本中若是咱们无论它,它就会发生持续恶化的状况,所以,咱们必须从项目研发的每个流程入手,创建科学完善的相关规范,才能保证长效的优化效果

三、Crash相关指标

要对应用的稳定性进行优化,咱们就必须先了解与Crash相关的一些指标。

一、UV、PV

  • PV(Page View):访问量
  • UV(Unique Visitor):独立访客,0 - 24小时内的同一终端只计算一次

二、UV、PV、启动、增量、存量 Crash率

  • UV Crash率(Crash UV / DAU):针对用户使用量的统计,统计一段时间内全部用户发生崩溃的占比,用于评估Crash率的影响范围,结合PV。须要注意的是,须要确保一直使用同一种衡量方式。
  • PV Crash率:评估相关Crash影响的严重程度
  • 启动Crash率:启动阶段,用户尚未彻底打开App而发生的Crash,它是影响最严重的Crash,对用户伤害最大,没法经过热修复拯救,需结合客户端容灾,以进行App的自主修复。(这块后面会讲)
  • 增量、存量Crash率:增量Crash是指的新增的Crash,而存量Crash则表示一些历史遗留bug。增量Crash是新版本重点,存量Crash是须要持续啃的硬骨头,咱们须要优先解决增量、持续跟进存量问题

四、Crash率评价

那么,咱们App的Crash率下降多少才能算是一个正常水平或优秀的水平呢?

  • Java与Native的总崩溃率必须在千分之二如下。
  • Crash率万分位为优秀:须要注意90%的Crash都是比较容易解决的,可是要解决最后的10%须要付出巨大的努力。

五、Crash关键问题

这里咱们还须要关注Crash相关的关键问题,若是应用发生了Crash,咱们应该尽量还原Crash现场。所以,咱们须要全面地采集应用发生Crash时的相关信息,以下所示:

  • 堆栈、设备、OS版本、进程、线程名、Logcat
  • 先后台、使用时长、App版本、小版本、渠道
  • CPU架构、内存信息、线程数、资源包信息、用户行为日志

接着,采集完上述信息并上报到后台后,咱们会在APM后台进行聚合展现,具体的展现信息以下所示:

  • Crash现场信息
  • Crash Top机型、OS版本、分布版本、区域
  • Crash起始版本、上报趋势、是否新增、持续、量级

最后,咱们能够根据以上信息决定Crash是否须要立马解决以及在哪一个版本进行解决,关于APM聚合展现这块能够参考 Bugly平台 的APM后台聚合展现。

而后,咱们再来看看与Crash相关的总体架构。

六、APM Crash部分总体架构

APM Crash部分的总体架构从上之下分为采集层、处理层、展现层、报警层。下面,咱们来详细讲解一下每一层所作的处理。

采集层

首先,咱们须要在采集层这一层去获取足够多的Crash相关信息,以确保可以精肯定位到问题。须要采集的信息主要为以下几种:

  • 错误堆栈
  • 设备信息
  • 行为日志
  • 其它信息
处理层

而后,在处理层,咱们会对App采集到的数据进行处理。

  • 数据清洗:将一些不符合条件的数据过滤掉,好比说,由于一些特殊状况,一些App采集到的数据不完整,或者因为上传数据失败而致使的数据不完整,这些数据在APM平台上确定是没法全面地展现的,因此,首先咱们须要把这些信息进行过滤。
  • 数据聚合:在这一层,咱们会把Crash相关的数据进行聚合。
  • 纬度分类:如Top机型下的Crash、用户Crash率的前10%等等维度。
  • 趋势对比
展现层

通过处理层以后,就会来到展现层,展现的信息为以下几类:

  • 数据还原
  • 纬度信息
  • 起始版本
  • 其它信息
报警层

最后,就会来到报警层,当发生严重异常的时候,会通知相关的同窗进行紧急处理。报警的规则咱们能够自定义,例如总体的Crash率,其环比(与上一期进行对比)或同比(如本月10号与上月10号)抖动超过5%,或者是单个Crash忽然间激增。报警的方式能够经过 邮件、IM、电话、短信 等等方式。

七、责任归属

最后,咱们来看下Crash相关的非技术问题,须要注意的是,咱们要解决的是如何长期保持较低的Crash率这个问题。咱们须要保证可以迅速找到相关bug的相关责任人并让开发同窗可以及时地处理线上的bug。具体的解决方法为以下几种:

  • 设立专项小组轮值:成立一个虚拟的专项小组,来专门跟踪每一个版本线上的Crash率,组内的成员能够轮流跟踪线上的Crash,这样,就能够从源头来保证全部Crash必定会有人跟进。
  • 自动匹配责任人将APM平台与bug单系统打通,这样APM后台一旦发现紧急bug就能第一时间下发到bug单系统给相关责任人发提醒
  • 处理流程全纪录:咱们须要记录Crash处理流程的每一步,确保紧急Crash的处理不会被延误

2、Crash优化

一、单个Crash处理方案

对与单个Crash的处理方案咱们能够按以下三个步骤来进行解决处理。

一、根据堆栈及现场信息找答案

  • 解决90%问题
  • 解决完后需考虑产生Crash深层次的缘由

二、找共性:机型、OS、实验开关、资源包,考虑影响范围

三、线下复现、远程调试

二、Crash率治理方案

要对应用的Crash率进行治理,通常须要对如下三种类型的Crash进行对应的处理,以下所示:

  • 一、解决线上常规Crash
  • 二、系统级Crash尝试Hook绕过
  • 三、疑难Crash重点突破或更换方案

三、Java Crash

出现未捕获异常,致使出现异常退出

Thread.setDefaultUncaughtExceptionHandler();
复制代码

咱们经过设置自定义的UncaughtExceptionHandler,就能够在崩溃发生的时候获取到现场信息。注意,这个钩子是针对单个进程而言的,在多进程的APP中,监控哪一个进程,就须要在哪一个进程中设置一遍ExceptionHandler

获取主线程的堆栈信息:

Looper.getMainLooper().getThread().getStackTrace();
复制代码

获取当前线程的堆栈信息:

Thread.currentThread().getStackTrace();
复制代码

获取所有线程的堆栈信息:

Thread.getAllStackTraces();
复制代码

第三方Crash监控工具如 Fabric、腾讯Bugly,都是以字符串拼接的方式将数组StackTraceElement[]转换成字符串形式,进行保存、上报或者展现。

那么,咱们如何反混淆上传的堆栈信息?

对此,咱们通常有两种可选的处理方案,以下所示:

  • 一、每次打包生成混淆APK的时候,须要把Mapping文件保存并上传到监控后台。
  • 二、Android原生的反混淆的工具包是retrace.jar,在监控后台用来实时解析每一个上报的崩溃时。它会将Mapping文件进行文本解析和对象实例化,这个过程比较耗时。所以能够将Mapping对象实例进行内存缓存,但为了防止内存泄露和内存过多占用,须要增长按期自动回收的逻辑。

如何获取logcat方法?

logcat日志流程是这样的,应用层 --> liblog.so --> logd,底层使用 ring buffer 来存储数据。获取的方式有如下三种:

一、经过logcat命令获取。

  • 优势:很是简单,兼容性好。
  • 缺点:整个链路比较长,可控性差,失败率高,特别是堆破坏或者堆内存不足时,基本会失败。

二、hook liblog.so实现

经过hook liblog.so 中的 __android_log_buf_write 方法,将内容重定向到本身的buffer中

  • 优势:简单,兼容性相对还好。
  • 缺点:要一直打开。

三、自定义获取代码。经过移植底层获取logcat的实现,经过socket直接跟logd交互。

  • 优势:比较灵活,预先分配好资源,成功率也比较高。
  • 缺点:实现很是复杂

如何获取Java 堆栈?

当发生native崩溃时,咱们经过unwind只能拿到Native堆栈。咱们但愿能够拿到当时各个线程的Java堆栈。对于这个问题,目前有两种处理方式,分别以下所示:

一、Thread.getAllStackTraces()。

优势

简单,兼容性好。

缺点
  • 成功率不高,依靠系统接口在极端状况也会失败。
  • 7.0以后这个接口是没有主线程堆栈。
  • 使用Java层的接口须要暂停线程。

二、hook libart.so。

经过hook ThreadList和Thread 的函数,得到跟ANR同样的堆栈。为了稳定性,须要在fork的子进程中执行

  • 优势:信息很全,基本跟ANR的日志同样,有native线程状态,锁信息等等。
  • 缺点:黑科技的兼容性问题,失败时咱们能够使用Thread.getAllStackTraces()兜底。

四、Java Crash处理流程

讲解了Java Crash相关的知识后,咱们就能够去了解下Java Crash的处理流程,这里借用Gityuan流程图进行讲解,以下图所示:

image

一、首先发生crash所在进程,在建立之初便准备好了defaultUncaughtHandler,用来来处理Uncaught Exception,并输出当前crash基本信息;

二、调用当前进程中的AMP.handleApplicationCrash;通过binder ipc机制,传递到system_server进程;

三、接下来,进入system_server进程,调用binder服务端执行AMS.handleApplicationCrash;

四、从mProcessNames查找到目标进程的ProcessRecord对象;并将进程crash信息输出到目录/data/system/dropbox;

五、执行makeAppCrashingLocked:

  • 建立当前用户下的crash应用的error receiver,并忽略当前应用的广播;
  • 中止当前进程中全部activity中的WMS的冻结屏幕消息,并执行相关一些屏幕相关操做;

六、再执行handleAppCrashLocked方法:

  • 当1分钟内同一进程连续crash两次时,且非persistent进程,则直接结束该应用全部activity,并杀死该进程以及同一个进程组下的全部进程。而后再恢复栈顶第一个非finishing状态的activity;
  • 当1分钟内同一进程连续crash两次时,且persistent进程,,则只执行恢复栈顶第一个非finishing状态的activity;
  • 当1分钟内同一进程未发生连续crash两次时,则执行结束栈顶正在运行activity的流程。

七、经过mUiHandler发送消息SHOW_ERROR_MSG,弹出crash对话框;

八、到此,system_server进程执行完成。回到crash进程开始执行杀掉当前进程的操做;

九、当crash进程被杀,经过binder死亡通知,告知system_server进程来执行appDiedLocked();

十、最后,执行清理应用相关的activity/service/ContentProvider/receiver组件信息。

补充加油站:binder 死亡通知原理

这里咱们还须要了解下binder 死亡通知的原理,其流程图以下所示:

image

因为Crash进程中拥有一个Binder服务端ApplicationThread,而应用进程在建立过程调用attachApplicationLocked(),从而attach到system_server进程,在system_server进程内有一个ApplicationThreadProxy,这是相对应的Binder客户端。当Binder服务端ApplicationThread所在进程(即Crash进程)挂掉后,则Binder客户端能收到相应的死亡通知,从而进入binderDied流程。

五、Native Crash

特色:

  • 访问非法地址
  • 地址对齐出错
  • 发送程序主动abort

上述都会产生相应的signal信号,致使程序异常退出。

一、合格的异常捕获组件

一个合格的异常捕获组件须要包含如下功能:

  • 支持在crash时进行更多扩张操做
  • 打印logcat和日志
  • 上报crash次数
  • 对不一样crash作不一样恢复措施
  • 能够针对业务不断改进的适应

二、现有方案

一、Google Breakpad

  • 优势:权威、跨平台
  • 缺点:代码体量较大

二、Logcat

  • 优势:利用安卓系统实现
  • 缺点:须要在crash时启动新进程过滤logcat日志,不可靠

三、coffeecatch

  • 优势:实现简洁、改动容易
  • 缺点:有兼容性问题

三、Native崩溃捕获流程

Native崩溃捕获的过程涉及到三端,这里咱们分别来了解下其对应的处理。

一、编译端

编译C/C++需将带符号信息的文件保留下来。

二、客户端

捕获到崩溃时,将收集到尽量多的有用信息写入日志文件,而后选择合适的时机上传到服务器。

三、服务端

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

四、Native崩溃捕获的难点

核心:如何确保客户端在各类极端状况下依然能够生成崩溃日志。

一、文件句柄泄漏,致使建立日志文件失败?

提早申请文件句柄fd预留。

二、栈溢出致使日志生成失败?

  • 使用额外的栈空间signalstack,避免栈溢出致使进程没有空间建立调用栈执行处理函数。(signalstack:系统会在危险状况下把栈指针指向这个地方,使得能够在一个新的栈上运行信号处理函数)
  • 特殊请求需直接替换当前栈,因此应在堆中预留部分空间。

三、堆内存耗尽致使日志生产失败?

参考Breakpad从新封装Linux Syscall Support的作法以免直接调用libc去分配堆内存。

四、堆破坏或二次崩溃致使日志生成失败?

Breakpad使用了fork子进程甚至孙进程的方式去收集崩溃现场,即使出现二次崩溃,也只是这部分信息丢失。

这里说下Breakpad缺点:

  • 生成的minidump文件时二进制的,包含过多不重要的信息,致使文件数MB。但minidump能够使用gdb调试、看到传入参数。

须要了解的是,将来Chromium会使用Crashpad替代Breakpad。

五、想要遵循Android的文本格式并添加更多重要的信息?

改造Breakpad,增长Logcat信息,Java调用栈信息、其它有用信息。

五、Native崩溃捕获注册

一个Native Crash log信息以下:

image

堆栈信息中 pc 后面跟的内存地址,就是当前函数的栈地址,咱们能够经过下面的命令行得出出错的代码行数

arm-linux-androideabi-addr2line -e 内存地址
复制代码

下面列出所有的信号量以及所表明的含义:

#define SIGHUP 1  // 终端链接结束时发出(无论正常或非正常)
#define SIGINT 2  // 程序终止(例如Ctrl-C)
#define SIGQUIT 3 // 程序退出(Ctrl-\)
#define SIGILL 4 // 执行了非法指令,或者试图执行数据段,堆栈溢出
#define SIGTRAP 5 // 断点时产生,由debugger使用
#define SIGABRT 6 // 调用abort函数生成的信号,表示程序异常
#define SIGIOT 6 // 同上,更全,IO异常也会发出
#define SIGBUS 7 // 非法地址,包括内存地址对齐出错,好比访问一个4字节的整数, 但其地址不是4的倍数
#define SIGFPE 8 // 计算错误,好比除0、溢出
#define SIGKILL 9 // 强制结束程序,具备最高优先级,本信号不能被阻塞、处理和忽略
#define SIGUSR1 10 // 未使用,保留
#define SIGSEGV 11 // 非法内存操做,与 SIGBUS不一样,他是对合法地址的非法访问,    好比访问没有读权限的内存,向没有写权限的地址写数据
#define SIGUSR2 12 // 未使用,保留
#define SIGPIPE 13 // 管道破裂,一般在进程间通讯产生
#define SIGALRM 14 // 定时信号,
#define SIGTERM 15 // 结束程序,相似温和的 SIGKILL,可被阻塞和处理。一般程序如    果终止不了,才会尝试SIGKILL
#define SIGSTKFLT 16  // 协处理器堆栈错误
#define SIGCHLD 17 // 子进程结束时, 父进程会收到这个信号。
#define SIGCONT 18 // 让一个中止的进程继续执行
#define SIGSTOP 19 // 中止进程,本信号不能被阻塞,处理或忽略
#define SIGTSTP 20 // 中止进程,但该信号能够被处理和忽略
#define SIGTTIN 21 // 当后台做业要从用户终端读数据时, 该做业中的全部进程会收到SIGTTIN信号
#define SIGTTOU 22 // 相似于SIGTTIN, 但在写终端时收到
#define SIGURG 23 // 有紧急数据或out-of-band数据到达socket时产生
#define SIGXCPU 24 // 超过CPU时间资源限制时发出
#define SIGXFSZ 25 // 当进程企图扩大文件以致于超过文件大小资源限制
#define SIGVTALRM 26 // 虚拟时钟信号. 相似于SIGALRM,     可是计算的是该进程占用的CPU时间.
#define SIGPROF 27 // 相似于SIGALRM/SIGVTALRM, 但包括该进程用的CPU时间以及系统调用的时间
#define SIGWINCH 28 // 窗口大小改变时发出
#define SIGIO 29 // 文件描述符准备就绪, 能够开始进行输入/输出操做
#define SIGPOLL SIGIO // 同上,别称
#define SIGPWR 30 // 电源异常
#define SIGSYS 31 // 非法的系统调用
复制代码

通常关注SIGILL, SIGABRT, SIGBUS, SIGFPE, SIGSEGV, SIGSTKFLT, SIGSYS便可。

要订阅异常发生的信号,最简单的作法就是直接用一个循环遍历全部要订阅的信号,对每一个信号调用sigaction()。

注意

  • JNI_OnLoad是最适合安装信号初识函数的地方。
  • 建议在上报时调用Java层的方法统一上报。Native崩溃捕获注册。

六、崩溃分析流程

首先,应收集崩溃现场的一些相关信息,以下:

一、崩溃信息

  • 进程名、线程名
  • 崩溃堆栈和类型
  • 有时候也须要知道主线程的调用栈

二、系统信息

  • 系统运行日志

    /system/etc/event-log-tags

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

注意,咱们能够去寻找共性问题,以下:

  • 设备状态
  • 是否root
  • 是不是模拟器

三、内存信息

系统剩余内存
/proc/meminfo
复制代码

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

应用使用内存

包括Java内存、RSS、PSS

PSS和RSS经过/proc/self/smap计算,能够获得apk、dex、so等更详细的分类统计。

虚拟内存

获取大小:

/proc/self/status
复制代码

获取其具体的分布状况:

/proc/self/maps
复制代码

须要注意的是,对于32位进程,32位CPU,虚拟内存达到3GB就可能会引发内存失败的问题。若是是64位的CPU,虚拟内存通常在3~4GB。若是支持64位进程,虚拟内存就不会成为问题。

四、资源信息

若是应用堆内存和设备内存比较充足,但还出现内存分配失败,则可能跟资源泄漏有关。

文件句柄fd

获取fd的限制数量:

/proc/self/limits
复制代码

通常单个进程容许打开的最大句柄个数为1024,若是超过800需将全部fd和文件名输出日志进行排查

线程数

获取线程数大小:

/proc/self/status
复制代码

一个线程通常占2MB的虚拟内存,线程数超过400个比较危险,须要将全部tid和线程名输出到日志进行排查。

JNI

容易出现引用失效、引用爆表等崩溃。

经过DumpReferenceTables统计JNI的引用表,进一步分析是否出现JNI泄漏等问题。

补充加油站:dumpReferenceTables的出处

在dalvik.system.VMDebug类中,是一个native方法,亦是static方法;在JNI中能够这么调用

jclass vm_class = env->FindClass("dalvik/system/VMDebug");
jmethodID dump_mid = env->GetStaticMethodID( vm_class, "dumpReferenceTables", "()V" );
env->CallStaticVoidMethod( vm_class, dump_mid );
复制代码

五、应用信息

  • 崩溃场景
  • 关键操做路径
  • 其它跟自身应用相关的自定义信息:运行时间、是否加载补丁、是否全新安装或升级。

六、崩溃分析流程

接下来进行崩溃分析:

一、肯定重点
  • 确认严重程度
  • 优先解决Top崩溃或对业务有重大影响的崩溃:如启动、支付过程的崩溃
  • Java崩溃:若是是OOM,需进一步查看日志中的内存信息和资源信息
  • Native崩溃:查看signal、code、fault addr以及崩溃时的Java堆栈

常见的崩溃类型有:

  • SIGSEGV:空指针、非法指针等
  • SIGABRT:ANR、调用abort推出等

若是是ANR,先看主线程堆栈、是否由于锁等待致使,而后看ANR日志中的iowait、CPU、GC、systemserver等信息,肯定是I/O问题或CPU竞争问题仍是大量GC致使的ANR。

注意,当从一条崩溃日志中没法看出问题缘由时,须要查看相同崩溃点下的更多崩溃日志,或者也能够查看内存信息、资源信息等进行异常排查。

二、查找共性

机型、系统、ROM、厂商、ABI这些信息均可以做为共性参考,对于下一步复现问题有明确指引。

三、尝试复现

复现以后再增长日志或使用Debugger、GDB进行调试。如不能复现,能够采用一些高级手段,如xlog日志、远程诊断、动态分析等等。

补充加油站:系统崩溃解决方式

  • 一、经过共性信息查找可能的缘由
  • 二、尝试使用其它使用方式规避
  • 三、Hook解决

七、实战:使用Breakpad捕获native崩溃

首先,这里给出《Android开发高手课》张绍文老师写的crash捕获示例工程,工程里面已经集成了Breakpad 来获取发生 native crash 时候的系统信息和线程堆栈信息。下面来详细介绍下使用Breakpad来分析native崩溃的流程:

一、示例工程是采用cmake的构建方式,因此须要先到Android Studio中SDK Manager中的SDK Tools下下载NDK和cmake。

二、安装实例工程后,点击CRASH按钮产生一个native崩溃。生成的 crash信息,若是授予Sdcard权限会优先存放在/sdcard/crashDump下,便于咱们作进一步的分析。反之会放到目录 /data/data/com.dodola.breakpad/files/crashDump中。

三、使用adb pull命令将抓取到的crash日志文件放到电脑本地目录中:

adb pull /sdcard/crashDump/***.dmp > ~/Documents/crash_log.dmp
复制代码

四、下载并编译Breakpad源码,在src/processor目录下找到minidump_stackwalk,使用这个工具将dmp文件转换为txt文件:

// 在项目目录下clone Breakpad仓库
git clone https://github.com/google/breakpad.git

// 切换到Breakpad根目录进行配置、编译
cd breakpad
./configure && make

// 使用src/processor目录下的minidump_stackwalk工具将dmp文件转换为txt文件
./src/processor/minidump_stackwalk ~/Documents/crashDump/crash_log.dmp >crash_log.txt 
复制代码

五、打开crash_log.txt,能够获得以下内容:

Operating system: Android
                  0.0.0 Linux 4.4.78-perf-g539ee70 #1 SMP PREEMPT Mon Jan 14 17:08:14 CST 2019 aarch64
CPU: arm64
     8 CPUs

GPU: UNKNOWN

Crash reason:  SIGSEGV /SEGV_MAPERR
Crash address: 0x0
Process uptime: not available

Thread 0 (crashed)
 0  libcrash-lib.so + 0x650
复制代码

其中咱们须要的关键信息为CPU是arm64的,而且crash的地址为0x650。接下来咱们须要将这个地址转换为代码中对应的行。

六、使用ndk 中提供的addr2line来根据地址进行一个符号反解的过程。

若是是arm64的so使用 $NDKHOME/toolchains/aarch64-linux-android-4.9/prebuilt/darwin-x86_64/bin/aarch64-linux-android-addr2line。

若是是arm的so使用 $NDKHOME/toolchains/arm-linux-androideabi-4.9/prebuilt/darwin-x86_64/bin/arm-linux-androideabi-addr2line。

由crash_log.txt的信息可知,咱们机器的cpu架构是arm64的,所以须要使用aarch64-linux-android-addr2line这个命令行工具。该命令的通常使用格式以下: // 注意:在mac下 ./ 表明执行文件 ./aarch64-linux-android-addr2line -e 对应的.so 须要解析的地址

上述中对应的.so文件在项目编译以后,会出如今Chapter01-master/sample/build/intermediates/merged_native_libs/debug/out/lib/arm64-v8a/libcrash-lib.so这个位置,因为个人手机CPU架构是arm64的,因此这里选择的是arm64-v8a中的libcrash-lib.so。接下来咱们使用aarch64-linux-android-addr2line这个命令:

./aarch64-linux-android-addr2line -f -C -e ~/Documents/open-project/Chapter01-master/sample/build/intermediates/merged_native_libs/debug/out/lib/arm64-v8a/libcrash-lib.so 0x650

参数含义:
-e --exe=<executable>:指定须要转换地址的可执行文件名。
-f --functions:在显示文件名、行号输出信息的同时显示函数名信息。
-C --demangle[=style]:将低级别的符号名解码为用户级别的名字。
复制代码

结果输出为:

Crash()
/Users/quchao/Documents/open-project/Chapter01-master/sample/src/main/cpp/crash.cpp:10
复制代码

由此,咱们得出crash的代码行为crahs.cpp文件中的第10行,接下来根据项目具体状况进行相应的修改便可。

Tips:这是从事NDK开发(音视频、图像处理、OpenCv、热修复框架开发)同窗调试native层错误时常常要使用的技巧,强烈建议熟练掌握。

六、疑难Crash解决方案

最后,笔者这里再讲解下一些疑难Crash的解决方案。

问题1:如何解决Android 7.0 Toast BadTokenException?

参考Android 8.0 try catch的作法,代理Toast里的mTN(handler)就能够实现捕获异常。

问题2:如何解决 SharedPreference apply 引发的 ANR 问题

apply为何会引发ANR?

SP 调用 apply 方法,会建立一个等待锁放到 QueuedWork 中,并将真正数据持久化封装成一个任务放到异步队列中执行,任务执行结束会释放锁。Activity onStop 以及 Service 处理 onStop,onStartCommand 时,执行 QueuedWork.waitToFinish() 等待全部的等待锁释放。

如何解决?

全部此类 ANR 都是经由 QueuedWork.waitToFinish() 触发的,只要在调用此函数以前,将其中保存的队列手动清空便可。

具体是Hook ActivityThrad的Handler变量,拿到此变量后给其设置一个Callback,Handler 的 dispatchMessage 中会先处理 callback。最后在 Callback 中调用队列的清理工做,注意队列清理须要反射调用 QueuedWork。

注意

apply 机制自己的失败率就比较高(1.8%左右),清理等待锁队列对持久化形成的影响不大。

问题3:如何解决TimeoutExceptin异常?

它是由系统的FinalizerWatchdogDaemon抛出来的。

这里首先介绍下看门狗 WatchDog,它 的做用是监控重要服务的运行状态,当重要服务中止时,发生 Timeout 异常崩溃,WatchDog 负责将应用重启。而当关闭 WatchDog(执行stop()方法)后,当重要服务中止时,也不会发生 Timeout 异常,是一种经过非正常手段防止异常发生的方法。

规避方案

stop方法,在Android 6.0以前会有线程同步问题。 由于6.0以前调用threadToStop的interrupt方法是没有加锁的,因此可能会有线程同步的问题。

注意:Stop的时候有必定几率致使即便没有超时也会报timeoutexception。

缺点

只是为了不上报异常采起的一种hack方案,并无真正解决引发finialize超时的问题。

问题4:如何解决输入法的内存泄漏?

经过反射将输入法的两个View置空。

七、进程保活

咱们能够利用SyncAdapter提升进程优先级,它是Android系统提供一个帐号同步机制,它属于核心进程级别,而使用了SyncAdapter的进程优先级自己也会提升,使用方式请Google,关联SyncAdapter后,进程的优先级变为1,仅低于前台正在运行的进程,所以能够下降应用被系统杀掉的几率

八、总结

对于App的Crash优化,总的来讲,咱们须要考虑如下四个要点:

  • 一、重在预防:重视应用的整个流程、包括开发人员的培训、编译检查、静态扫描、规范的测试、灰度、发布流程等
  • 二、不该该随意使用try catch去隐藏问题:而应该从源头入手,了解崩溃的本质缘由,保证后面的运行流程。
  • 三、解决崩溃的过程应该由点到面,考虑一类崩溃怎么解决。
  • 四、崩溃与内存、卡顿、I/O内存紧密相关

3、ANR优化

一、ANR监控实现方式

一、使用FileObserver监听 /data/anr/traces.txt的变化

缺点

高版本ROM须要root权限。

解决方案

海外Google Play服务、国内Hardcoder。

二、监控消息队列的运行时间(WatchDog)

卡顿监控原理:

利用主线程的消息队列处理机制,应用发生卡顿,必定是在dispatchMessage中执行了耗时操做。咱们经过给主线程的Looper设置一个Printer,打点统计dispatchMessage方法执行的时间,若是超出阀值,表示发生卡顿,则dump出各类信息,提供开发者分析性能瓶颈。

为卡顿监控代码增长ANR的线程监控,在发送消息时,在ANR线程中保存一个状态,主线程消息执行完后再Reset标志位。若是在ANR线程中收到发送消息后,超过必定时间没有复位,就能够任务发生了ANR。

缺点

  • 没法准确判断是否真正出现ANR,只能说明APP发生了UI阻塞,须要进行二次校验。校验的方式就是等待手机系统出现发生了Error的进程,而且Error类型是NOT_RESPONDING(值为2)。 在每次出现ANR弹框前,Native层都会发出signal为SIGNAL_QUIT(值为3)的信号事件,也能够监听此信号。
  • 没法获得完整ANR日志
  • 隶属于卡顿优化的方式

三、须要考虑应用退出场景

  • 主动自杀
  • Process.killProcess()、exit()等。
  • 崩溃
  • 系统重启
  • 系统异常、断电、用户重启等:经过比较应用开机运行时间是否比以前记录的值更小。
  • 被系统杀死
  • 被LMK杀死、从系统的任务管理器中划掉等。

注意

因为traces.txt上传比较耗时,因此通常线下采用,线上建议综合ProcessErrorStateInfo和出现ANR时的堆栈信息来实现ANR的实时上传。

二、ANR优化

ANR发生缘由:没有在规定的时间内完成要完成的事情。

ANR分类

发生场景

  • Activity onCreate方法或Input事件超过5s没有完成
  • BroadcastReceiver前台10s,后台60s
  • ContentProvider 在publish过超时10s;
  • Service前台20s,后台200s

发生缘由

  • 主线程有耗时操做
  • 复杂布局
  • IO操做
  • 被子线程同步锁block
  • 被Binder对端block
  • Binder被占满致使主线程没法和SystemServer通讯
  • 得不到系统资源(CPU/RAM/IO)

从进程角度看发生缘由有:

  • 当前进程:主线程自己耗时或者主线程的消息队列存在耗时操做、主线程被本进程的其它子线程所blocked
  • 远端进程:binder call、socket通讯

Andorid系统监测ANR的核心原理是消息调度和超时处理。

ANR排查流程

一、Log获取

一、抓取bugreport

adb shell bugreport > bugreport.txt
复制代码

二、直接导出/data/anr/traces.txt文件

adb pull /data/anr/traces.txt trace.txt
复制代码

二、搜索“ANR in”处log关键点解读

  • 发生时间(可能会延时10-20s)

  • pid:当pid=0,说明在ANR以前,进程就被LMK杀死或出现了Crash,因此没法接受到系统的广播或者按键消息,所以会出现ANR

  • cpu负载Load: 7.58 / 6.21 / 4.83

    表明此时一分钟有平均有7.58个进程在等待 一、五、15分钟内系统的平均负荷 当系统负荷持续大于1.0,必须将值降下来 当系统负荷达到5.0,表面系统有很严重的问题

  • cpu使用率

    CPU usage from 18101ms to 0ms ago 28% 2085/system_server: 18% user + 10% kernel / faults: 8689 minor 24 major 11% 752/android.hardware.sensors@1.0-service: 4% user + 6.9% kernel / faults: 2 minor 9.8% 780/surfaceflinger: 6.2% user + 3.5% kernel / faults: 143 minor 4 major

上述表示Top进程的cpu占用状况。

注意

若是CPU使用量不多,说明主线程可能阻塞。

三、在bugreport.txt中根据pid和发生时间搜索到阻塞的log处

----- pid 10494 at 2019-11-18 15:28:29 -----
复制代码

四、往下翻找到“main”线程则可看到对应的阻塞log

"main" prio=5 tid=1 Sleeping
| group="main" sCount=1 dsCount=0 flags=1 obj=0x746bf7f0 self=0xe7c8f000
| sysTid=10494 nice=-4 cgrp=default sched=0/0 handle=0xeb6784a4
| state=S schedstat=( 5119636327 325064933 4204 ) utm=460 stm=51 core=4 HZ=100
| stack=0xff575000-0xff577000 stackSize=8MB
| held mutexes=
复制代码

上述关键字段的含义以下所示:

  • tid:线程号
  • sysTid:主进程线程号和进程号相同
  • Waiting/Sleeping:各类线程状态
  • nice:nice值越小,则优先级越高,-17~16
  • schedstat:Running、Runable时间(ns)与Switch次数
  • utm:该线程在用户态的执行时间(jiffies)
  • stm:该线程在内核态的执行时间(jiffies)
  • sCount:该线程被挂起的次数
  • dsCount:该线程被调试器挂起的次数
  • self:线程自己的地址

补充加油站:各类线程状态

须要注意的是,这里的各类线程状态指的是Native层的线程状态,关于Java线程状态与Native线程状态的对应关系以下所示:

enum ThreadState {
  //                                   Thread.State   JDWP state
  kTerminated = 66,                 // TERMINATED     TS_ZOMBIE    Thread.run has returned, but Thread* still around
  kRunnable,                        // RUNNABLE       TS_RUNNING   runnable
  kTimedWaiting,                    // TIMED_WAITING  TS_WAIT      in Object.wait() with a timeout
  kSleeping,                        // TIMED_WAITING  TS_SLEEPING  in Thread.sleep()
  kBlocked,                         // BLOCKED        TS_MONITOR   blocked on a monitor
  kWaiting,                         // WAITING        TS_WAIT      in Object.wait()
  kWaitingForLockInflation,         // WAITING        TS_WAIT      blocked inflating a thin-lock
  kWaitingForTaskProcessor,         // WAITING        TS_WAIT      blocked waiting for taskProcessor
  kWaitingForGcToComplete,          // WAITING        TS_WAIT      blocked waiting for GC
  kWaitingForCheckPointsToRun,      // WAITING        TS_WAIT      GC waiting for checkpoints to run
  kWaitingPerformingGc,             // WAITING        TS_WAIT      performing GC
  kWaitingForDebuggerSend,          // WAITING        TS_WAIT      blocked waiting for events to be sent
  kWaitingForDebuggerToAttach,      // WAITING        TS_WAIT      blocked waiting for debugger to attach
  kWaitingInMainDebuggerLoop,       // WAITING        TS_WAIT      blocking/reading/processing debugger events
  kWaitingForDebuggerSuspension,    // WAITING        TS_WAIT      waiting for debugger suspend all
  kWaitingForJniOnLoad,             // WAITING        TS_WAIT      waiting for execution of dlopen and JNI on load code
  kWaitingForSignalCatcherOutput,   // WAITING        TS_WAIT      waiting for signal catcher IO to complete
  kWaitingInMainSignalCatcherLoop,  // WAITING        TS_WAIT      blocking/reading/processing signals
  kWaitingForDeoptimization,        // WAITING        TS_WAIT      waiting for deoptimization suspend all
  kWaitingForMethodTracingStart,    // WAITING        TS_WAIT      waiting for method tracing to start
  kWaitingForVisitObjects,          // WAITING        TS_WAIT      waiting for visiting objects
  kWaitingForGetObjectsAllocated,   // WAITING        TS_WAIT      waiting for getting the number of allocated objects
  kWaitingWeakGcRootRead,           // WAITING        TS_WAIT      waiting on the GC to read a weak root
  kWaitingForGcThreadFlip,          // WAITING        TS_WAIT      waiting on the GC thread flip (CC collector) to finish
  kStarting,                        // NEW            TS_WAIT      native thread started, not yet ready to run managed code
  kNative,                          // RUNNABLE       TS_RUNNING   running in a JNI native method
  kSuspended,                       // RUNNABLE       TS_RUNNING   suspended by GC or debugger
};
复制代码

其它分析方法:Java线程调用分析方法

  • 先使用jps命令列出当前系统中运行的全部Java虚拟机进程,拿到应用进程的pid。
  • 而后再使用jstack命令查看该进程中全部线程的状态以及调用关系,以及一些简单的分析结果。

三、关于ANR的一些常见问题

一、sp调用apply致使anr问题?

虽然apply并不会阻塞主线程,可是会将等待时间转嫁到主线程。

二、检测运行期间是否发生过异常退出?

在应用启动时设定一个标志,在主动自杀或崩溃后更新标志 ,下次启动时检测此标志便可判断。

四、理解ANR的触发流程

broadcast跟service超时机制大抵相同,但有一个很是隐蔽的技能点,那就是经过静态注册的广播超时会受SharedPreferences(简称SP)的影响。

当SP有未同步到磁盘的工做,则需等待其完成,才告知系统已完成该广播。而且只有XML静态注册的广播超时检测过程会考虑是否有SP还没有完成,动态广播并不受其影响。

  • 对于Service, Broadcast, Input发生ANR以后,最终都会调用AMS.appNotResponding。
  • 对于provider,在其进程启动时publish过程可能会出现ANR, 则会直接杀进程以及清理相应信息,而不会弹出ANR的对话框。
  • 对于输入事件发生ANR,首先会调用InputMonitor.notifyANR,最终也会调用AMS.appNotResponding。

一、AMS.appNotResponding流程

  • 输出ANR Reason信息到EventLog. 也就是说ANR触发的时间点最接近的就是EventLog中输出的am_anr信息。
  • 收集并输出重要进程列表中的各个线程的traces信息,该方法较耗时。
  • 输出当前各个进程的CPU使用状况以及CPU负载状况。
  • 将traces文件和 CPU使用状况信息保存到dropbox,即data/system/dropbox目录(ANR信息最为重要的信息)。
  • 根据进程类型,来决定直接后台杀掉,仍是弹框告知用户。

二、AMS.dumpStackTraces流程

一、收集firstPids进程的stacks:

  • 第一个是发生ANR进程;
  • 第二个是system_server;
  • 其他的是mLruProcesses中全部的persistent进程。

二、收集Native进程的stacks。(dumpNativeBacktraceToFile)

  • 依次是mediaserver,sdcard,surfaceflinger进程。

三、收集lastPids进程的stacks:

  • 依次输出CPU使用率top 5的进程;
注意

上述导出每一个进程trace时,进程之间会休眠200ms。

4、移动端业务高可用方案建设

一、业务高可用重要性

关于业务高可用重要性有以下五点:

  • 高可用
  • 性能
  • 业务
  • 侧重于用户功能完整可用
  • 真实影响收入

二、业务高可用方案建设

业务高可用方案建设须要注意的点比较繁杂,可是整体能够归结为以下几点:

  • 数据采集
  • 梳理项目主流程、核心路径、关键节点
  • Aop自动采集、统一上报
  • 报警策略:阈值报警、趋势报警、特定指标报警、直接上报(或底阈值)
  • 异常监控
  • 单点追查:须要针对性分析的特定问题,全量日志回捞,专项分析
  • 兜底策略
  • 配置中心、功能开关
  • 跳转分发中心(组件化路由)

三、移动端容灾方案

灾包括:

  • 性能异常
  • 业务异常

传统流程:

用户反馈、从新打包、渠道更新、不可接受。

容灾方案建设

关于容灾方案的建设主要能够细分为如下七点,下面,咱们分别来了解下。

一、功能开关

配置中心,服务端下发配置控制

针对场景
  • 功能新增
  • 代码改动

二、统跳中心

  • 界面切换经过路由,路由决定是否重定向
  • Native Bug不能热修复则跳转到临时H5页面

三、动态化修复

热修复能力,可监控、灰度、回滚、清除。

四、推拉结合、多场景调用保证到达率

五、Weex、RN增量更新

六、安全模式

微信读书、蘑菇街、淘宝、天猫等“重运营”的APP都使用了安全模式保障客户端启动流程,启动失败后给用户自救机会。先介绍一下它的核心特色:

  • 根据Crash信息自动恢复,屡次启动失败重置应用为安装初始状态
  • 严重Bug可阻塞性热修复
安全模式设计

配置后台:统一的配置后台,具有灰度发布机制

一、客户端能力:

  • 在APP连续Crash的状况下具有分级、无感自修复能力
  • 具有同步热修复能力
  • 具有指定触发某项特定功能的能力
  • 具体功能注册能力,方便后期扩展安全模式

二、数据统计及告警

  • 统一的数据平台
  • 监控告警功能,及时发现问题
  • 查看热修复成功率等数据

三、快速测试

  • 优化预发布环境下测试
  • 优化回归验证安全模式难点等
天猫安全模式原理

一、如何判断异常退出?

APP启动时记录一个flag值,知足如下条件时,将flag值清空

  • APP正常启动10秒
  • 用户正常退出应用
  • 用户主动从前台切换到后台

若是在启动阶段发生异常,则flag值不会清空,经过flag值就能够判断客户端是否异常退出,每次异常退出,flag值都+1。

二、安全模式的分级执行策略

分为两级安全模式,连续Crash 2次为一级安全模式,连续Crash 2次及以上为二级安全模式。

业务线能够在一级安全模式中注册行为,好比清空缓存数据,再进入该模式时,会使用注册行为尝试修复客户端 若是一级安全模式没法修复APP,则进入二级安全模式将APP恢复到初次安装状态,并将Document、Library、Cache三个根目录清空。

三、热修复执行策略

只要发现配置中须要热修复,APP就会同步阻塞进行热修复,保证修复的及时性

四、灰度方案

灰度时,配置中会包含灰度、正式两份配置及其灰度几率 APP根据特定算法算出本身是否知足灰度条件,则使用灰度配置

易用性考量

一、接入成本

完善文档、接口简洁

二、统一配置后台

可按照APP、版本配置

三、定制性

支持定制功能,让接入方来决定具体行为

四、灰度机制

五、数据分析

采用统一数据平台,为安全模式改进提供依据

六、快速测试

建立更多的针对性测试案例,如模拟连续Crash

七、异常熔断

当屡次请求失败则可以让网络库主动拒绝请求。

容灾方案集合路径

功能开关 -> 统跳中心 -> 动态修复 -> 安全模式

5、稳定性长效治理

要实现App稳定性的长效治理,咱们须要从 开发阶段 => 测试阶段 => 合码阶段 => 发布阶段 => 运维阶段 这五个阶段来作针对性地处理。

一、开发阶段

  • 统一编码规范、加强编码功底、技术评审、CodeReview机制
  • 架构优化
  • 能力收敛
  • 统一容错:如在网络库utils中统一对返回信息进行预校验,如不合法就直接不走接下来的流程。

二、测试阶段

  • 功能测试、自动化测试、回归测试、覆盖安装
  • 特殊场景、机型等边界测试:如服务端返回异常数据、服务端宕机
  • 云测平台:提供更全面的机型进行测试

三、合码阶段

  • 编译检测、静态扫描
  • 预编译流程、主流程自动回归

四、发布阶段

  • 多轮灰度
  • 分场景、纬度全面覆盖

五、运维阶段

  • 灵敏监控
  • 回滚、降级策略
  • 热修复、本地容灾方案

6、稳定性优化问题

一、大家作了哪些稳定性方面的优化?

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

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

经过这三方面的优化咱们搭建了移动端的高可用平台。同时,也作了不少的措施来让App真正地实现了高可用。

二、性能稳定性是怎么作的?

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

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

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

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

三、业务稳定性如何保障?

  • 数据采集 + 报警
  • 须要对项目的主流程与核心路径进行埋点监控
  • 同时还需知道每一步发生了多少异常,这样,咱们就知道了全部业务流程的转换率以及相应界面的转换率
  • 结合大盘,若是转换率低于某个值,进行报警
  • 异常监控 + 单点追查
  • 兜底策略,如天猫安全模式

移动端业务高可用它侧重于用户功能完整可用,主要是为了解决一些线上一些异常状况致使用户他虽然没有崩溃,也没有性能问题,可是呢,只是单纯的功能不可用的状况,咱们须要对项目的主流程、核心路径进行埋点监控,来计算每一步它真实的转换率是多少,同时呢,还须要知道在每一步到底发生了多少异常。这样咱们就知道了全部业务流程的转换率以及相应界面的转换率,有了大盘的数据呢,咱们就知道了,若是转换率或者是某些监控的成功率低于某个值,那颇有可能就是出现了线上异常,结合了相应的报警功能,咱们就不须要等用户来反馈了,这个就是业务稳定性保障的基础。

同时呢,对于一些特殊状况,好比说,开发过程中或代码中出现了一些catch代码块,捕获住了异常,让程序不崩溃,这实际上是不合理的,程序虽然没有崩溃,当时程序的功能已经变得不可用,因此呢,这些被catch的异常咱们也须要上报上来,这样咱们才能知道用户到底出现了什么问题而致使的异常。此外,线上还有一些单点问题,好比说用户点击登陆一直进不去,这种就属于单点问题,其实咱们是没法找出其和其它问题的共性之处的,因此呢,咱们就必需要找到它对应的详细信息。

最后,若是发生了异常状况,咱们还采起了一系列措施进行快速止损。(=>4)

四、若是发生了异常状况,怎么快速止损?

  • 功能开关
  • 统跳中心
  • 动态修复:热修复、资源包更新
  • 自主修复:安全模式

首先,须要让App具有一些高级的能力,咱们对于任何要上线的新功能,要加上一个功能的开关,经过配置中心下发的开关呢,来决定是否要显示新功能的入口。若是有异常状况,能够紧急关闭新功能的入口,那就可让这个App处于可控的状态了。

而后,咱们须要给App设立路由跳转,全部的界面跳转都须要经过路由来分发,若是咱们匹配到须要跳转到有bug的这样一个新功能时,那咱们就不跳转了,或者是跳转到统一的异常正处理中的界面。若是这两种方式都不能够,那就能够考虑经过热修复的方式来动态修复,目前热修复的方案其实已经比较成熟了,咱们彻底能够低成本地在咱们的项目中添加热修复的能力,固然,若是有些功能是由RN或WeeX来实现就更好了,那就能够经过更新资源包的方式来实现动态更新。而这些若是都不能够的话呢,那就能够考虑本身去给应用加上一个自主修复的能力,若是App启动屡次的话,那就能够考虑清空全部的缓存数据,将App重置到安装的状态,到了最严重的等级呢,能够阻塞主线程,此时必定要等App热修复成功以后才容许用户进入。

7、总结

Android稳定性优化是一个须要 长期投入,持续运营和维护 的一个过程,上文中咱们不只深刻探讨了Java Crash、Native Crash和ANR的解决流程及方案,还分析了其内部实现原理和监控流程。到这里,能够看到,要想作好稳定性优化,咱们 必须对虚拟机运行、Linux信号处理和内存分配 有必定程度的了解,只有深刻了解这些底层知识,咱们才能比别人设计出更好的稳定性优化方案

参考连接:

一、《Android性能优化最佳实践》第五章 稳定性优化

二、慕课网之国内Top团队大牛带你玩转Android性能分析与优化 第十一章 App稳定性优化

三、极客时间之Android开发高手课 崩溃优化

四、Android 平台 Native 代码的崩溃捕获机制及实现

五、安全模式:天猫App启动保护实践

六、美团外卖Android Crash治理之路 (进阶)

七、海神平台Crash监控SDK(Android)开发经验总结

八、Android Native Crash 收集

九、理解Android Crash处理流程

十、Android应用ANR分析

十一、理解Android ANR的触发原理

十二、Input系统—ANR原理分析

1三、ANR监测机制

1四、理解Android ANR的触发原理

1五、理解Android ANR的信息收集过程

1六、应用与系统稳定性第一篇---ANR问题分析的通常套路

1七、巧妙定位ANR问题

1八、剖析 SharedPreference apply 引发的 ANR 问题

1九、Linux错误信号

Contanct Me

● 微信:

欢迎关注个人微信:bcce5360

● 微信群:

微信群若是不能扫码加入,麻烦你们想进微信群的朋友们,加我微信拉你进群。

● QQ群:

2千人QQ群,Awesome-Android学习交流群,QQ群号:959936182, 欢迎你们加入~

About me

很感谢您阅读这篇文章,但愿您能将它分享给您的朋友或技术群,这对我意义重大。

但愿咱们能成为朋友,在 Github掘金上一块儿分享知识。

相关文章
相关标签/搜索