1.后台应用的性能问题是影响用户体验的重要因素之一。性能问题主要包括:崩溃、网络请求错误或超时、UI响应慢、主线程卡顿、CPU和内存占用率高、功耗高等。大部分问题是开发者使用线程、锁、系统函数、编程引起的规格问题、数据结构等不正确。解决这个问题的关键是尽早发现和定位问题。360作为一家注重用户体验的公司,APP性能问题无疑是关注的焦点,我们也总结出了一套自己的APP性能监控体系。在平时的开发和用户反馈的问题中,我们总结了性能问题,总结了五个问题:资源文件如何控制,版本质量如何保证,线上问题如何排查,开发阶段如何防止性能下降,性能是否监控能真实反映用户体验。同时学习了业界比较完备的性能监控平台的功能原理。于是有了360在iOS移动端的在线性能监控方案——QDAS-APM。2.功能与原理QDAS-APM实现了如下监控功能:页面渲染时间主线程冻结网络错误FPS大文件存储CPU内存占用Crash启动时间下面按功能详细介绍实现细节和原理。另外,用户在使用APP时会感知到性能问题,我们可以将其转化为具体的性能监控指标。一、页面渲染时间什么是页面渲染时间?页面渲染时间实际上是从页面初始化到用户可以看到页面效果的时间长度。需要理解的指标是:生命周期系统方法执行时间页面类名启动类型执行耗时插件名称关键衡量指标是执行耗时,不同方法和步骤产生的耗时考虑在可接受范围内用户是合理的。其他指标起到关联和定位的作用。直接hookUIViewController的方法显然是行不通的,因为它只作用于UIViewController的方法上,而大部分app都是采用继承UIViewController的方法。这里有两个可行的解决方案:使用KVO,我们知道在对任何对象进行KVO操作时,系统都会帮你动态创建一个复制类,同时实现settergetter函数的覆盖和功能实现。使用runtime遍历UIViewController的所有子类,然后进行动态替换。这两种方式比较推荐第一种,出于兼容性,性能,以及能够直接获取UIViewController子类的IMP。那么具体如何实现呢?可以概括为三步:创建一个类UIViewController,对UIViewController实例进行KVO。目的是让KVO创建一个需要监控的UIViewController的子类。添加需要监听的方法,在KVO创建的子类中添加需要Swizzle的方法对应的SEL及其IMP。目的是为了控制调用原类方法的时机。UIViewController实例销毁时,在dealloc方法中去掉KVO监听,否则会导致Crash。例如:我们以监控qh_viewDidLoad方法为例:*func)(UIViewController*,SEL)=(void(*)(UIViewController*,SEL))origin_imp;CFAbsoluteTimestartTime=CACurrentMediaTime();func(kvo_self,_sel);CFAbsoluteTimeendTime=CACurrentMediaTime();NSTimeIntervalduration=(endTime-startTime)*1000;NSLog(@"Class%@cost%ginviewDidLoad",[kvo_selfclass],duration);}会有一种特殊情况,如果KVO生成的类中对应的类没有实现监听方法,会导致有什么后果呢,KVO内部生成的NSKVONotifying_ViewController其实是继承了ViewController,所以直接把对应的IMP调用取出来好了,以上就是UIViewControllercl执行时间的统计屁股法。我们还想知道真实页面跳转后,用户看到pin页面图片的时间是怎么统计的?UIViewController类的init+loadView+viewDidLoad+viewWillAppear+viewDidAppear方法的执行时间加起来等于页面渲染时间吗?答案是不。下面是三个反面例子:如何判断屏幕渲染完成?能否间接获取屏幕渲染时间?对于异步回调和异步渲染这两个方法,使用上面提到的5个方法执行次数的总和是不适用的。接下来,我们看看如何计数和计算比较准确。页面渲染时间和页面布局时间在未来的某个时间点会保持一致。获取页面渲染时间,可以间接参考页面布局完成时间。在UIViewController的生命周期方法中,有一个方法叫做viewDidLayoutSubviews。它有什么作用?它实际上告诉控制器的子视图布局完成的时间点。一般情况下会被调用两次,不同的操作系统版本调用次数不同。2、主线程卡顿分析主线程卡顿直接影响用户体验,体现在页面的流畅运行上。首先介绍一个概念FPS(FramesPerSecond):每秒显示连续图片的帧数。每秒帧数越多,UI操作就越流畅。一般应用保持每秒50-60帧的帧率,会给用户带来流畅的感觉,否则,用户会感觉到卡顿。那为什么主线程会卡住呢?首先了解在屏幕上显示每一帧图像的原理。这是触摸屏显示器的原理流程图。CPU负责计算显示内容,包括视图创建、布局计算、图片解码、文字绘制等,CPU将计算结果提交给GPU。GPU经过变换、合成、渲染后,将渲染结果提交给帧缓冲区。当下一个垂直同步信号到达时,视频控制器从缓冲区中获取视图并将其显示在屏幕上。了解了屏幕显示的原理之后,我们来看看为什么会出现卡顿。图中提到的V-Sync是什么,为什么会在iPhone的显示过程中引入?iPhone中使用的是双缓冲机制,即上图中的FrameBuffer有两个缓冲区,双缓冲的引入是为了提高显示效率,但同时他引入了一个新的问题.当videocontroller还没有读完,比如屏幕内容刚显示一半时,GPU会向framebuffer提交一帧新的内容,交换两个buffer后,videocontroller会显示下半部分屏幕上出现一帧新的数据,造成画面撕裂。V-Sync就是为了解决画面撕裂的问题。打开V-Sync最后,GPU将在显示器发出V-Sync信号后渲染新帧并更新缓冲区。搞清楚了iPhone的屏幕显示原理之后,我们就来看看为什么iPhone会出现卡顿现象。上文提到,在图像真正显示在屏幕上之前,CPU和GPU需要完成各自的任务,如果完成则错过下一次V-Sync的时间(通常是1000/60=16.67ms),所以显示画面还是上一帧的内容,这就是界面卡顿的原因。不难发现,无论是CPU还是GPU导致的V-Sync信号丢失,都会导致界面卡顿。3.网络监控网络监控一般是通过NSURLProtocol和代码注入(Hook)来实现的。由于使用NSURLProtocol作为上层接口,使用起来更加方便。NSURLProtocol属于URLLoadingSystem体系,应用层协议支持有限。它支持FTP、HTTP、HTTPS等多种应用层协议,但对使用其他协议的流量无能为力,因此存在一定的局限性。监控底层网络库CFNetwork没有这个限制。如果本地有https证书验证,则不适用于NSURLProtocol这种方式。容易造成业务数据丢失。(1)NSURLProtocol上图是基于NSURLProtocol协议实现的,继承自NSURLProtocol并注册。通过代理和自身方法获取网络请求相关指标。(2)HOOK方法——NSProxyNSProxy是一个抽象超类,它为充当其他对象或尚不存在的对象的替身对象定义了一个API。通常,给代理的消息被转发给真实对象或导致代理加载(或将其自身转换为)真实对象。NSProxy的子类可用于实现透明的分布式消息传递(例如,NSDistantObject)或用于创建昂贵的对象的惰性实例化。以上英文是苹果官方文档中NSProxy的定义,NSProxy和NSObject是同一个根类,是一个抽象类,可以继承它并重写-forwardInvocation:和-methodSignatureForSelector:方法来实现消息转发到另一个实例.综上所述,NSProxy的作用就是负责将消息转发给真正的目标代理类。那为什么我们不需要指定类名而不是Methodswizzling呢?因为NSURLConnectionDelegate和NSURLSessionDelegate是业务方指定的,通常是不确定的,所以这种场景不适合使用Methodswizzling。可以使用NSProxy解决。具体实现:proxydelegate代替原来的NSURLConnection和NSURLSession的delegate。当代理delegate接收到回调,如果是要hook的方法,则调用代理的实现。代理的实现最终会调用原来的委托。;反之,通过消息转发机制将消息转发给原来的delegate。下图说明了整个运行过程:通过hookNSURLConnection、NSURLSession和CFNetwork这三个类的关键方法获取上报指标。具体hook方法见下图:将hook方法中获取的相关指标整理成需要的格式,上报给服务器。服务器通过数据处理和拆分指标,汇总计算生成最终报表。3、QDAS-APM在集成使用上的便利性由于sdk功能基本使用的是主动采集功能,不需要二次开发,也不需要引入额外的系统库。所以集成使用起来非常方便。在sdk的集成中,只需要三步:导入sdk库,导入sdk头文件,在app的didFinishLauchingWithOptions中初始化sdk,传入appkey。【本文为栏目组织360科技、微信公众号《360科技(id:qihoo_tech)》原创文章】点此阅读更多本作者好文
