背景
开发一款app,我们必须关注这款应用的性能问题,这自然就涉及到一个词APM。
APM 的全称是Application performance management,即应用性能管理,通过对应用的可靠性、稳定性等方面的监控,进而达到可以快速修复问题、提高用户体验的目的。
国内各大公司都有自己的一套监控体系,这个系统可能是自己研发,也可能是第三方提供,当然对于这个数据为王的时代,很多有实力的公司倾向于自主研发,掌握核心数据。比较有代表性的 APM 产品有:听云、阿里百川、腾讯 bugly、NewRelic、OneAPM、网易云捕等
本文只对流量监测这一块进行着重研究。
几种具体流量监测方案
1.统计设备流量总量的方案
参考stackoverflow一篇关于流量统计的问题,得到以下demo, 可以分别获取WiFi 和 WWAN网络环境下上行和下行流量总数。
首先需要引入这几个系统库
#include <arpa/inet.h>
#include <net/if.h>
#include <ifaddrs.h>
#include <net/if_dl.h>
/**
* WiFiSent WiFi发送流量
* WiFiReceived WiFi接收流量
* WWANSent 移动网络发送流量
* WWANReceived 移动网络接收流量
*/
+ (NSDictionary *)getTrafficMonitorings {
NSDictionary * trafficDict = [[NSDictionary alloc] init];
BOOL success;
struct ifaddrs *addrs;
const struct ifaddrs *cursor;
const struct if_data *networkStatisc;
int WiFiSent = 0;
int WiFiReceived = 0;
int WWANSent = 0;
int WWANReceived = 0;
NSString *name=[[NSString alloc]init];
success = getifaddrs(&addrs) == 0;
if (success) {
cursor = addrs;
while (cursor != NULL) {
name=[NSString stringWithFormat:@"%s",cursor->ifa_name];
if (cursor->ifa_addr->sa_family == AF_LINK) {
//wifi消耗流量
if ([name hasPrefix:@"en"]) {
networkStatisc = (const struct if_data *) cursor->ifa_data;
WiFiSent+=networkStatisc->ifi_obytes;
WiFiReceived+=networkStatisc->ifi_ibytes;
}
//移动网络消耗流量
if ([name hasPrefix:@"pdp_ip0"]) {
networkStatisc = (const struct if_data *) cursor->ifa_data;
WWANSent+=networkStatisc->ifi_obytes;
WWANReceived+=networkStatisc->ifi_ibytes;
}
}
cursor = cursor->ifa_next;
}
freeifaddrs(addrs);
}
NSString *WiFiSentTraffic = [NSString stringWithFormat:@"%d",WiFiSent];
NSString *WiFiReceivedTraffic = [NSString stringWithFormat:@"%d",WiFiReceived];
NSString *WiFiTotalTraffic = [NSString stringWithFormat:@"%d",WiFiSent + WiFiReceived];
NSString *WWANSentTraffic = [NSString stringWithFormat:@"%d",WWANSent];
NSString *WWANReceivedTraffic = [NSString stringWithFormat:@"%d",WWANReceived];
NSString *WWANTotalTraffic = [NSString stringWithFormat:@"%d",WWANSent+WWANReceived];
trafficDict = @{
@"WiFiSentTraffic":WiFiSentTraffic,
@"WiFiReceivedTraffic":WiFiReceivedTraffic,
@"WiFiTotalTraffic":WiFiTotalTraffic,
@"WWANSentTraffic":WWANSentTraffic,
@"WWANReceivedTraffic":WWANReceivedTraffic,
@"WWANTotalTraffic":WWANTotalTraffic
};
return trafficDict;
}
这个方案有几个问题需要注意:
- iOS是无法像Android一样监听到开机广播和关机广播的,立刻关机再开机,上面的方式监测的移动网络下流量会默认清空。
- 应用在后台运行时,突然被杀死,无法对流量统计,需要在应用进入后台时先保存流量使用情况
2.本地统计各网络接口流量
本地统计流量,主要通过两种方式来做:
- 针对
URLConnection
、CFNetwork
、NSURLSession
三种网络做Hook,hook的具体技术可以是method swizzle 也可以是Proxy - 也可以使用
NSURLProtocol
对网络请求的拦截,进而得到流量、响应时间等信息,但是NSURLProtocol有自己的局限,比如NSURLProtocol只能拦截NSURLSession,NSURLConnection以及UIWebView,但是对于CFNetwork则无能为力
由于以上两种方式的局限性,需要将两种方式结合起来:
1.针对URLConnection
、CFNetwork
、NSURLSession
三种网络做Hook。
具体来说就是将流量监测代码插入系统方法实现,method swizzling可以达到这个效果。例如,对于网络请求开始,我们通过监测-[NSURLConnection start]方法就可以统计这一次请求的数据大小。
通过这种方式,可以监控指定类的指定方法,我们可以取得方法调用的时机, 但是程序中除了方法调用还存在方法回调,这是不适合用这种方式监控的情况。
例如NSURLConnection的构造方法和start方法可以通过Method Swizzling监控到, 但是回调消息的接收者delegate的类名不固定,可能是任意一个页面实例, 如果还要使用Method Swizzling的方法来监控,会面对未知个数的页面的delegate方法,不是一个好办法。
解决方法是构造一个回调消息的转发者作为代理,在转发者中收集数据,再转发给用户。
2.NSURLProtocol
拦截是监控UIWebView请求最普遍的解决方案。具体可以参考美团技术团队的实现。