This post was updated 2010 days ago and some of the ideas may be out of date.
一、前言:
APP的性能大家应该都比较关注,那么咱们的自己APP性能如何呢?如何在拿不到源码的情况下去调试第三方应用的性能呢?下面我就简单实践一下。就以比较著名的今日头条和贝壳找房为例吧!大家看看自己的APP和他们还有多少差距。
二、分析启动时和启动后的cpu和内存占用
下面我们对这个指标以2个APP进行性能分析,分别是贝壳找房2.6.1版本、今日头条7.2.2版本。手机我们使用12.2系统的iPhoneX。我们借助于Instruments中的Activity Monitor工具进行分析。首先我们去拿到这几个ipa的脱壳包(这里可以借鉴这两篇文章,传送门一->点击我, 传送门二->点击我;或者可以去越狱市场直接下载脱壳好的包),脱壳后使用development的证书进行重签名(可以参考:传送门->点击我)。然后我们将这两个包安装到手机中去。
如图:
以下我们分别对这2个APP测试APP启动前5秒的CPU峰值以及对应的内存占用和启动后某一平稳时段的CPU占用以及对应的内存。
2.1
先看今日头条的数据:
如图:
今日头条在前5秒的CPU峰值达到了148.1%,此时的内存占用是63.66M
然后我们再看看启动后的平稳数据
如图:
CPU是3.9%,此时的内存占用是102.06M
2.2
我们再看贝壳找房的数据:
如图:
贝壳找房在前5秒的CPU峰值达到了116.4%,此时的内存占用是77.30M
然后我们再看看启动后的平稳数据
如图:
CPU是2.9%,此时的内存占用是77.02M
从以上数据看,两个APP的突发峰值都还好,平峰比较稳定,大家可以对比看下自己的APP是否和他们差不多,我们再看下一个指标,APP启动时间。
三、分析APP启动时常
下面我们使用 MonkeyDev (传送门->点击我) 工具将上面的那两个包运行起来,在Environment Variables中加入DYLD_PRINT_STATISTICS变量,得到Total pre-main time。我们对每个APP分别测量3次,取平均值。
如图:
3.1
我们先看今日头条的3次测量数据
第一次:
第二次:
第三次:
得到最终今日头条的平均值是:1.3s
3.2
然后我们再看贝壳找房的3次数据
第一次:
第二次:
第三次:
得到最终贝壳找房的平均值是:954.66ms
从以上数据分析,各个APP之间的pre-main之间耗费的时间差别并不多,我们看下各个内容的含义。
- dylib loading time
载入动态库,这个过程中,会去装载app使用的动态库,而每一个动态库有它自己的依赖关系,所以会消耗时间去查找和读取。对于Apple提供的的系统动态库,做了高度的优化。而对于开发者定义导入的动态库,则需要在花费更多的时间。Apple官方建议尽量少的使用自定义的动态库,或者考虑合并多个动态库,其中一个建议是当大于6个的时候,则需要考虑合并它们 -
rebase/binding time
重构和绑定,rebase会修正调整处理图像的指针,并且会设置指向绑定(binding)外部的图像指针。所以为了加快rebase/binding,则需要更少的做指针修复。当你的app当中有太多的Objective-C的类,方法选择器,和类别会增加这一部分的启动时间。有一个数据当大于20000个时候,会增加800ms的时间。另一点:当你的app中使用了很少的C++的虚拟函数,使用Swift会更加高效 -
ObjC setup time
在Objective-C的运行时(runtime),需要对类(class),类别(category)进行注册,以及选择器的分配,所以参照rebase/binding time,尽量减少类的数量,可以达到减少这一部分的时间 -
initializer time
这一份指代的是执行+initialize方法的时间。如果你执行了+load方法(不建议),尽量使用+initialize代替。 -
slowest intializers
这个列出的是最慢的几个dylib文件。
从以上数据来看,他们大致的耗时操作都差不多,需要从以上几点进行一下优化。不过我看来优化启动的时间应该从pre-main之后入手,测试main到第一个viewcontroller的viewDidAppear的时间。
四、分析APP第一个控制器的启动时间
下面我以贝壳找房进行详细描述,用FLEXible获取贝壳找房的首页控制器名称
如图:
然后我们注入如下代码:获取首页控制器的viewDidLoad到viewDidAppear的执行时间
CHDeclareClass(LJHomePageViewController)
CHOptimizedMethod1(self, void, LJHomePageViewController, viewDidAppear, BOOL, animated)
{
CHSuper1(LJHomePageViewController, viewDidAppear, animated);
NSLog(@"第一个视图完毕");
// 得到加载时间
NSTimeInterval duration = [[NSDate date] timeIntervalSinceDate:self.testDate];
NSLog(@"????????????%g", duration);
}
CHOptimizedMethod0(self, void, LJHomePageViewController, viewDidLoad)
{
CHSuper0(LJHomePageViewController, viewDidLoad);
YYFPSLabel *fpsLabel = [YYFPSLabel new];
fpsLabel.frame = CGRectMake(180, 200, 120, 30);
[fpsLabel sizeToFit];
[[UIApplication sharedApplication].delegate.window addSubview:fpsLabel];
NSDate *date = [NSDate date];
// 保存开始时间
self.testDate = date;
}
////add new property
CHPropertyRetainNonatomic(LJHomePageViewController, NSDate*, testDate, setTestDate);
CHConstructor{
CHLoadLateClass(LJHomePageViewController);
CHClassHook0(LJHomePageViewController, viewDidLoad);
CHClassHook1(LJHomePageViewController, viewDidAppear);
CHHook0(LJHomePageViewController, testDate);
CHHook1(LJHomePageViewController, setTestDate);
}
可以看到他第一个控制器的时间约为0.24s,当然网络请求后的渲染时间此处是没有统计的,有兴趣的大家可以再hook网络那部分。
五、分析APP主要列表的FPS
这里我以今日头条为例子,使用FLEXible获取今日头条的主页面,然后分别对各个主页面的viewDidLoad方法进行Hook,然后在这里加入YYFPSLabel。这里直接对APPDelegate进行Hook也可以。
如图:
以下是hook的代码:
@interface TTFeedCollectionViewController
- (void)viewDidLoad;
@end
CHDeclareClass(TTFeedCollectionViewController)
CHOptimizedMethod0(self, void, TTFeedCollectionViewController, viewDidLoad)
{
CHSuper0(TTFeedCollectionViewController, viewDidLoad);
YYFPSLabel *fpsLabel = [YYFPSLabel new];
fpsLabel.frame = CGRectMake(180, 200, 50, 30);
[fpsLabel sizeToFit];
[[UIApplication sharedApplication].delegate.window addSubview:fpsLabel];
}
CHConstructor{
CHLoadLateClass(TTFeedCollectionViewController);
CHClassHook0(TTFeedCollectionViewController, viewDidLoad);
}
此处页面稳定后的帧率是接近60,可以看出今日头条优化的还是可以的。
六、总结:
以上是以今日头条和贝壳找房进行了下实践,大家感兴趣可以多试试,然后对比自己的应用,看看有没有需要提高的。另外可以借鉴逆向的思维,去调试一些东西,挖掘和大厂APP之间的差距。这就是这篇文章的初衷。
参与讨论