频频卡顿崩溃?移动应用如何跟踪定位性能问题

PamFrisby 7年前
   <p>百年前,人们获取信息的方式是通过书籍、报纸;十年前,人们获取信息的方式是通过PC、互联网;如今,在移动互联网高速发展的环境下,人们获取信息的方式已经全面转向了小小的手机。短短几年里,移动互联网已经与我们的生活形影不离,一部手机走天下不再是梦想。</p>    <p>移动应用的商业价值在互联网时代发生了变化,越来越多企业通过移动应用为用户提供服务,应用改变了商业世界。2016年Q1,用户从iOS和Android应用市场下载的应用数量高达172亿个,94%的应用以火箭般的速度进行更新。更新迭代越快,应用存在的性能问题就越突出,性能问题造成业务下降影响已经占到25%甚至更多,应用本身的变革迫在眉睫。</p>    <h2><strong>影响用户体验的十宗罪</strong></h2>    <p>比起用户流失来说,移动应用性能问题还会给用户带来更多的损失,我们看看影响用户体验的十宗罪:</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/85ad69f30fa29340596645993fd54d58.jpg"></p>    <p>当应用出现崩溃、错误时,便会引起关键业务中断、收入下降等情况,进一步便会影响到产品生命周期价值;如果应用请求响应时间长,那么便会导致终端用户体验缓慢、用户留存率下降的情况发生;如果是应用交互性能慢的话,那么页面元素加载就会缓慢进而造成卡顿或是不完整造成的布局错乱。</p>    <p>据统计,每天有 1400万 设备发生崩溃,每月累计有高达 1.8 亿 的设备遭遇应用崩溃。这些数字都是发生在真实的生产环境中,即使在上线之前进行过大量的测试,在生产环境中依然会发生,而且大部分都是特定机型上会发生莫名奇怪的崩溃问题,导致用户行为的中断,影响业务下降。</p>    <p>随着手机使用量的增加,手机用户对性能期望也在不断提高,响应时间超过5秒,有50%用户会放弃;30%用户直接卸载;33%用户会跑到竞争对手那里。企业必须时刻关注用户使用APP真实感受,为用户提供良好的体验,才能实现应用的商业价值。</p>    <h2><strong>如何持续提升移动应用的用户体验</strong></h2>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/689d3bf62bc04759fef2696f43cc3e45.jpg"></p>    <p>移动应用的发展与繁荣催生了移动应用性能管理(mAPM)需求爆发,而关注移动应用性能可有效持续提升用户体验。透视宝mobile APM正是为此而生,通过端到端一体化全业务流程监控平台,及时发现和解决移动应用业务运维和运营过程中遇到的性能问题,帮助开发人员进行可持续性研发迭代,降低IT运维成本,减少用户流失。</p>    <h2><strong>透视宝mobile APM 从五个维度解析移动应用性能</strong></h2>    <p>移动应用发布上线之后,由于缺少有效的性能监控手段,对于开发和运维来说其架构、应用、程序代码的执行情况都是一个黑盒,无法感知到用户使用APP的真实感受,无法预知应用发生了什么样的问题,看不到业务具体数据到底是怎么执行,更没办法准确定位到问题,而APM通过以下五个重要维度能够帮你把黑盒子打开。</p>    <p><strong>1、 用户从哪来,网络接入性能怎么样?</strong></p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/8e2d080ebdfdaee22edd2a71e49d8f80.jpg"></p>    <p>地域分析可以了解我们的用户都是从哪些地域使用APP,各地的响应时间是否存在很大差异,耗时较高的地区根据网络节点调整CDN,优化网站层面的性能问题。</p>    <p><strong>2、 用户使用的什么设备,不同运营商对网络响应是否达标?</strong></p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/4f5f802b8d44f03a67c32ac1a0c2c205.jpg"></p>    <p>设备、运营商、APP版本分析可以帮忙了解用户使用什么品牌的手机访问APP应用,不同的终端存在不同的差异,据统计iPhone6 使用较为稳定,而华为设备发生崩溃几率最低,通过这些设备、运营商、APP版本的分析结果可以在新版本迭代中安排重点测试和适配。</p>    <p><strong>3、用户做了什么操作,在APP中的用户体验如何?</strong></p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/fe7aae2a6c5d43f524cce834dabaf21d.jpg"></p>    <p>用户行为监控能够将所有用户在APP中的点击操作与性能数据关联,通过HTTP请求响应耗时,请求错误,崩溃等维度分析APP中最影响用户体验的用户行为。并且可分析受到影响的每一位用户的在APP中行为操作路径及流程,协助研发人员还原用户使用场景从而精准定位问题发生的原因。</p>    <p style="text-align: center;"><strong>4、APP中是否发生了崩溃、ANR等问题? </strong><img src="https://simg.open-open.com/show/389028c5a38125b1ef392b066f35bb95.jpg"></p>    <p>崩溃是APP应用中最影响用户体验的问题之一,而且是移动开发者最大的痛点,所以收集崩溃日志、快速定位问题根源是最好的解决办法。崩溃分析可按APP版本,崩溃趋势,设备型号,运营商,接入方式,地域崩溃等多个维度分析和统计。</p>    <p>崩溃分析可以解析崩溃堆栈定位发生崩溃的代码片段和行数,并且通过收集分析用户发生崩溃的设备,接入方式,内存,CPU使用率以及用户操作的行为轨迹助研发人员能快速定位问题,还原案发现场。</p>    <p><strong>5、APP前端页面交互性能和Service端代码执行效率</strong></p>    <p>APP中的性能问题大体可以分为两部分:1、前端页面交互性能 2、后端Service端代码效率。</p>    <p>前端页面交互性能主要体现在页面加载缓慢,请求错误异常,卡顿等方面,而目前webview在APP开发中已经非常普遍,对性能的监控需求也越来越强烈。我们提出白屏时间、页面请求耗时分解、页面加载资源耗时分解等性能指标,可以对页面请求耗时诊断慢在哪个环节。</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/dd0d1eadc7e87b23bb3c4446e9f6631e.jpg"></p>    <p>页面响应时间分解,将整个H5页面加载的耗时分解到网络请求的每一个细节上去。如上图主要包括:重定向起止时间、缓存起止时间、域名解析起止时间、TCP传输起止时间、请求起止时间、响应起止时间、Dom加载起止时间、页面渲染起止时间等。</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/d5884b3d1609f2932b54cb168acd6b82.jpg"></p>    <p style="text-align: center;">图:H5页面加载的资源时序图,明确告知每一个资源类型的加载时间</p>    <p>APP端除了前端的性能问题需要关注,Service端的性能问题同样不容忽视,由于篇幅问题本文不做重点介绍,但是需要介绍mobile端与service的端到端的特性。</p>    <h2><strong>端到端事物分析</strong></h2>    <p>针对移动端的事务我们可以诊断定位到耗时情况,除了前端页面的资源加载占用大量资源外,Service代码造成缓慢的因素同样需要准确定位,所以透视宝提供了端到端事务分析。</p>    <p>移动端嵌入SDK,Service端部署Smart Agent,通过UUID关联了APP中所有的请求事务,定位后端的代码执行最慢的方法,SQL语言,参数等指标。</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/fb61e8dac786a5e4ecb6576756615058.jpg"></p>    <p>针对HTTP的网络数据收集主要分为以下指标:请求时间、网络吞吐量和网络错误,劫持分析等。</p>    <p>请求时间 是指一个http请求从发起请求到接收到服务端的响应,这期间所经历的时间。这个指标可以跟踪后台接口的响应是否正常,网络环境是否正常。</p>    <p>网络错误 主要是跟踪url请求过程中的错误,分为http本身的错误和因网络状况出现的错误。</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/18114986a148b277b807522fde25866e.jpg"></p>    <p>某个客户的一个杭州用户在6分钟内发起了5800多次请求,请求错误率高达98%以上。当时他们不相信那是真实的,后来与客户交流发现该网络请求接口存在循环调用,请求接口在请求成功之前会一直循环调用,直到请求成功或是断网,而这正是造成导致循环请求的真正原因。</p>    <p>还是这个客户,我们通过Smart SDK发现他的很多API报错是找不到主机,经确认APP开发在版本迭代过程中把很多接口废弃了,但客户端还在调用,通过HTTP错误和网络错误请求分析就可以帮助客户清理废弃的API,保证我们的业务正常服务。</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/c3c7d4c5d27ee6e9ca5793d7dd404c72.jpg"></p>    <p>而做到这一切,只需要嵌入透视宝Smart SDK的两行代码, 5分钟轻松实现对APP应用的性能监控,业务运营监控的需求。以往通过用户投诉和反馈,开发总是最后一个发现问题,而被动的解决问题更是费时费力。使用透视宝后可以提前发现应用性能问题,快速迭代修复问题,避免用户流失提升用户体验。</p>    <h2><strong>移动端性能管理技术实现</strong></h2>    <p>透视宝Smart SDK自动收集性能数据是不需要埋点的。iOS和And</p>    <p>roid由于平台的差异性实现方式是不同的。Objective-C语言具有动态运行时的特性,因此iOS平台使用Hook机制来拦截方法的执行,从而收集性能数据;而Java语言不具备这样的特性,但能够对字节码进行改写,因此Android平台使用ASM框架动态注入代码到相关的方法中收集性能数据。</p>    <h2><strong>iOS Smart SDK技术原理</strong></h2>    <p>iOS的Hook技术使用Objective-C提供的Method Swizzling机制。Method Swizzling是Objective-C的运行时技术,指的是改变一个已存在的选择器对应的实现的过程,它依赖于Objectvie-C中方法的调用能够在运行时进行改变——通过改变类的调度表(dispatch table)中选择器到最终函数间的映射关系。</p>    <p>原理图如下:</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/fa5aed7632285f428da2712ef6bc672a.jpg"></p>    <p>每一个Selector(选择器)对应一个IMP(实现体),通过Method Swizzling可以动态改变这种对应关系。Swizzling通常被程序开发认为是一种巫术,容易导致不可预料的行为和结果。但是如果采取下面这些措施,Method Swizzling还是很安全的:</p>    <p>1、Swizzling应该在+load方法中实现。</p>    <p>Objective-C中每个类都有+load和+initialize两个方法。这两个方法会被Objective-C运行时系统自动调用,+load是在一个类最开始加载时调用,+initialize是在应用中第一次调用该类或它的实例的方式之前调用。这两个方法都是可选的,都是只有实现了才会被执行。</p>    <p>因为method swizzling会影响全局,所以减少冒险情况就很重要。+load能够保证在类初始化的时候就会被加载,这为改变系统行为提供了一些统一性。但+initialize并不能保证在什么时候被调用——事实上也有可能永远也不会被调用,例如应用程序从未直接的给该类发送消息。</p>    <p>2、Swizzling应该在dispatch_once中实现。还是因为swizzling会改变全局,我们需要在运行时采取所有可用的防范措施。保障原子性就是一个措施,它确保代码即使在多线程环境下也只会被执行一次。GCD中的diapatch_once就可以提供这些保障。</p>    <p>3、始终调用方法的原始实现。系统提供的 API为输入和输出提供规约,但它里面具体的实现其实是个黑匣子,在Method Swizzling过程中不调用它原始的实现可能会破坏一些私有状态,导致程序运行不稳定。</p>    <h2><strong>Android Smart SDK技术原理</strong></h2>    <p>首先来看看Android app的打包流程</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/89b6d7ecf8fdedd7bc45570ecc47855c.jpg"></p>    <p>该图翻译成技术语言,如下图</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/ff95f663b2b188a62501ab64663edf71.jpg"></p>    <p>图中标明了sdk中代码的工作位置:</p>    <p>就是在 .class文件转化成.dex文件的过程中,通过ASM框架改写代码的。通过对字节码的读写,找到感兴趣的方法,加入收集信息的代码。原生的网络请求、用户行为、页面加载等方面的性能数据,就是以这样的方式收集到的。</p>    <p>透视宝端到端技术方案重点不在移动端,而在于后端。移动端只是针对从APP中发出的每一条网络请求打上标记,后端节点上的Agent接收到该条请求,解析标记,记录下来,并且打上该节点的标记。最终通过这些标记画出该条请求所经过的所有节点的拓扑图,将节点上的应用的性能指标关联起来。</p>    <h2><strong>崩溃信息收集和分析</strong></h2>    <p>iOS的崩溃信息收集和分析原理</p>    <p>崩溃信息收集,通过系统提供的异常信息捕获接口,设置回调函数,捕获异常信息。通过解析解码文件,将解码文件与崩溃信息里的内存地址结合起来,分析出发生崩溃的位置和代码段</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/c483c4306989a782633e0816f496644d.jpg"></p>    <p>通过设置系统异常信息收集的回调函数到系统相应的接口上,捕获系统的异常信息。</p>    <p>崩溃信息解析过程:</p>    <p>1、从崩溃日志中获取代码行的地址偏移量。(已从前面的崩溃信息中获取到)</p>    <p>1、 解析符号表文件dSYM, 使用dwarfdump命令从dSYM文件中抽取相关信息。比较常用的两个命令:</p>    <p>dwarfdump -e --debug-info YourPath/YourApp.dSYM/Contents/Resources/DWARF > info.txt</p>    <p>dwarfdump -e --debug-line YourPath/YourApp.dSYM/Contents/Resources/DWARF > line.txt</p>    <p>其中,info.txt文件中包含类文件的具体信息,比如类名,方法名,方法的内存起止地址等。</p>    <p>如下图所示:</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/ff95f663b2b188a62501ab64663edf71.jpg"></p>    <p>由图中可知,只要偏移量在0x000681c0和0x00068454之间的代码,就对应JKBOverviewVC.m文件的 -[JKBOverviewVC tableView:cellForRowAtIndexPath:]函数。</p>    <p>具体崩溃在该函数的哪一行,通过line.txt文件中的内容得出</p>    <p>line.txt文件中,包含函数中每一行代码的内存地址对应的相关信息,如下图所示</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/85c18eb044bb562fffea184524623c44.jpg"></p>    <p>崩溃日志中,偏移量在0x000681c0和0x00068454之间的代码,在这个line.txt文件中就能找到具体的代码行。至此,iOS的崩溃日志就分析完成了。</p>    <p><strong>Android的崩溃收集原理</strong></p>    <p>1. Java层的崩溃信息收集原理,如下图所示:</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/cc5a93d99d4c086823fde48115ed0910.jpg"></p>    <p>Java 层的堆栈信息收集是通过定义一个Handler类去实现Thread.UncaughtExceptionHandler接口的void uncaughtException(Thread t, Throwable e)方法,然后,通过在Thread.setDefaultUncaughtExceptionHandler方法将我们定义的Handler和APP的线程关联起来。在uncaughtException方法中去收集APP 没有捕获的异常,然后将其堆栈信息收集起来。</p>    <p>2.  Native层的崩溃信息收集原理,如下图所示:</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/54e3b9feedc1c729152c420ceaa6a6dd.jpg"></p>    <p>由于Android 底层使用的是linux内核,所以Native层先通过sigaction()函数去注册我们需要监视的Crash相关的信号量,同时,修改相关信号量发生的回调函数,在回调函数中我们通过Android的libcorkscrew.so(5.0版本以下) 和 libunwind.so(5.0及以上版本)提供的打印堆栈函数去收集Native层的堆栈信息,然后通过JNI层回调到Java层,从而收集到native的信息和Java层当前的所有线程信息。</p>    <h2><strong>H5页面的性能数据收集方案</strong></h2>    <p>H5页面的性能数据采集通过注入js代码来实现的。移动端的H5页面展示过程,主要分为数据请求、数据加载、页面渲染等。而其中数据加载是将H5页面的数据从服务端一段一段的load到移动端,JS代码的注入就是在这个阶段完成的。</p>    <p>在数据load阶段注入js代码,待数据load完成之后渲染,这样做既不会影响客户代码的执行,也能保证JS代码能够监控H5页面的整个生命周期中的用户操作和页面加载情况。</p>    <p>由于移动端操作系统对webkit进行了高度封装,开放出来的API还少,以致不能获取H5页面的详细加载情况,只能借助于JS代码,获取页面的performance参数,从而获取到页面加载的各个性能指标。</p>    <p><strong>Q1:对于react native 是否也能支持?对于原生应用和这种类型的应用支持的程度是否一致?</strong></p>    <p>A:我们目前没有对React Native的应用做过测试。网络请求的支持应该是与原生应用的支持一致的;系统级别的崩溃,支持程度也是一致的;加载H5和用户行为事件的追踪,支持程度应该有限。</p>    <p>以上结论,需要测试确定。假如不支持,我们也将经过迭代,解决该兼容问题。</p>    <p><strong>Q2、H5中注入脚本会影响性能么?</strong></p>    <p>A:JS代码是存在SDK本地的,没有新的网络请求产生,JS代码在H5页面上工作的时候,并不是现场计算,而是直接取得performance的接口;同时修改系统的click事件冒泡取得标签值和url,而不是进行click方法的动态bound,所以工作过程中的性能损耗微乎其微。</p>    <p><strong>Q 3、除了UI上带来的用户体检,耗电、网络流量也是用户所关注的。不是埋点式的行为数据收集,产生的数据量是怎么控的?</strong></p>    <p>A:1、耗电量可以通过系统接口获取到 2、网络流量目前有统计,我们的每一条请求都会统计发送字节和接收字节。3、目前行为数据不支持可配置,只要是有事件发生的行为,都会记录。下一步会支持用户行为可配置。接下来我们会支持APP中用户重点关心的用户行为配置,而且这个建模过程会更加清晰简单,只需要操作APP就可以完成这个过程了。</p>    <p> </p>    <p> </p>    <p>来自:http://mp.weixin.qq.com/s?__biz=MzAwNzA0NTMzMQ==&mid=2653201857&idx=1&sn=f1084dc107abef0f4e706fa081d26cd9&scene=4#wechat_redirect</p>    <p> </p>