前几天iOS组内review的时候,提到安卓接入了这个工具,有不少价值模块,并提升了效率。刚好我们需要在卡顿上下功夫,研究一下这个工具好用不好用。
Doraemon工具是滴滴出行开源的一个功能面板集合,集成了不少调试工具,也可以自定义,支持多端。主要有以下功能:

可以看到左边有一个小浮窗,这个页面就是点击小浮窗后展现的页面。常用工具中其实就是我们日常开发中,在xcode中调试的数据,其中有一个我比较好奇,就是子线程渲染UI的功能。
子线程刷新UI检测
我去看了一下源码,doraemon是开源的,是可以看到源码的。发现他hook了UIVew的 setNeedsLayout ,setNeedsDisplay,setNeedsDisplayInRect: 三个绘制方法,然后在其中判断当前线程是否是主线程,如果不是就打印出当前的线程和相关view的信息。
- (void)doraemon_setNeedsDisplayInRect:(CGRect)rect{
[self doraemon_setNeedsDisplayInRect:rect];
[self uiCheck];
}
- (void)uiCheck{
if([[DoraemonCacheManager sharedInstance] subThreadUICheckSwitch]){
if(![NSThread isMainThread]){
NSString *report = [BSBacktraceLogger bs_backtraceOfCurrentThread];
NSDictionary *dic = @{
@"title":[DoraemonUtil dateFormatNow],
@"content":report
};
[[DoraemonSubThreadUICheckManager sharedInstance].checkArray addObject:dic];
}
}
}
性能检测
这个才是我们关注的关键,可以看到这里有一些我们非常关心的属性,例如CPU,帧数,内存,流量等。我在我们项目中尝试了一下,体验效果如下图:

我打开了帧率,内存和CPU检测。他的展示如上图,我们可以在使用过程中,实时的看到这些变化,比较方便我在使用中看到什么情况下,什么页面比较耗费性能。内存和cpu在我们xcode连接调试的时候,是能看到实时变化的。

它提供了更直观的呈现。我也通过这个工具,发现选课精选页面在滚动的时候,CPU的占用起伏还是比较大的,查了一下代码应该是布局代码在滚动时一直在计算有关系,通过优化,CPU的占有率下降了一些。
其中,帧数展示是xcode本身不具备的功能,首先我们得知道帧数是啥意思。
帧数
其实我们一般所说的帧数,都指的是FPS,frame per second,顾名思义,就是每一秒刷新多少帧,每一帧都是静止的图像,所以帧数越高越流畅。
这个功能我也去看了看他实现的方式。通过源码和官方给出的分析,他是这么做的:
它是基于CADisplayLink这个类做FPS计算的,CADisplayLink是CoreAnimation提供的另一个类似于NSTimer的类,它会在屏幕每次刷新回调一次。既然CADisplayLink可以以屏幕刷新的频率调用指定selector,而且iOS系统中正常的屏幕刷新率为60Hz(60次每秒),那只要在这个方法里面统计每秒这个方法执行的次数,通过次数/时间就可以得出当前屏幕的刷新率了。
- (void)startRecord{
if (_link) {
_link.paused = NO;
}else{
_link = [CADisplayLink displayLinkWithTarget:self selector:@selector(trigger:)];
[_link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
_record = [DoraemonRecordModel instanceWithType:DoraemonRecordTypeFPS];
_record.startTime = [[NSDate date] timeIntervalSince1970];
}
}
- (void)trigger:(CADisplayLink *)link{
if (_lastTime == 0) {
_lastTime = link.timestamp;
return;
}
_count++;
NSTimeInterval delta = link.timestamp - _lastTime;
if (delta < 1) return;
_lastTime = link.timestamp;
CGFloat fps = _count / delta;
_count = 0;
NSInteger intFps = (NSInteger)(fps+0.5);
// 0~60 对应 高度0~200
[self.record addRecordValue:fps time:[[NSDate date] timeIntervalSince1970]];
[_oscillogramView addHeightValue:fps*200./60. andTipValue:[NSString stringWithFormat:@"%zi",intFps]];
}
卡顿
卡顿检测的实现方式我也一直比较好奇,刚好参考一下他们卡顿检测的原理。看了源码之后,发现卡顿的检测比我想象中简单很多。卡顿的检测,一般都是检测主线程是否有卡顿。看了他们的源码,发现思路大概是这样的:
单独开一个检测卡顿的线程,然后每隔一个时间间隔,去ping一下主线程,能收到回调,就算不卡,收不到回调就算卡顿,并上报。看了下源码,他们ping主线程的机制非常简单却有效:
while (!self.cancelled) {
if (_isApplicationInActive) {
self.mainThreadBlock = YES;
self.reportInfo = @"";
self.startTimeValue = floor([[NSDate date] timeIntervalSince1970] * 1000);
dispatch_async(dispatch_get_main_queue(), ^{
self.mainThreadBlock = NO;
verifyReport();
dispatch_semaphore_signal(self.semaphore);
});
[NSThread sleepForTimeInterval:self.threshold];
if (self.isMainThreadBlock) {
self.reportInfo = [BSBacktraceLogger bs_backtraceOfMainThread];
}
dispatch_semaphore_wait(self.semaphore, dispatch_time(DISPATCH_TIME_NOW, 5.0 * NSEC_PER_SEC));
{
//卡顿超时情况;
verifyReport();
}
} else {
[NSThread sleepForTimeInterval:self.threshold];
}
}
在ping线程,写一个BOOL值,默认为YES,来标识主线程是否有卡顿,ping线程每隔一个时间间隔,休眠一次,也就是多久检测一次卡顿,doraemon默认是1秒,在线程苏醒的时候,去主线程,把这个BOOL值置为NO,当下一个周期是,如果这个BOOL值被改为NO了,说明没有卡顿,如果没有被置为NO,说明主线程卡顿,在一个时间间隔内没有空闲,没来得及重置BOOL值,此时上报卡顿信息。其实思路还挺简单的。
崩溃
其实开发中,这个功能是没啥作用的,因为是线下,我们调试的时候崩溃是立马就能看到的,而且还有bugly的捕获,这个功能对我们来说比较鸡肋。但是就想看看他们是怎么捕获崩溃的。查看源码发现,其实iOS是提供了相关API回调,去抓获崩溃状态的:
有一个C语言函数来去捕获崩溃异常,NSSetUncaughtExceptionHandler
这个API设置了异常处理函数之后,就可以在程序终止前的最后一刻进行日志的记录。这个功能正是我们想要的:
FOUNDATION_EXPORT void NSSetUncaughtExceptionHandler(NSUncaughtExceptionHandler * _Nullable);
需要给他传一个c语言函数,就可以捕获异常了:
NSSetUncaughtExceptionHandler(&DoraemonUncaughtExceptionHandler);
// 崩溃时的回调函数
static void DoraemonUncaughtExceptionHandler(NSException * exception) {
// 异常的堆栈信息
NSArray * stackArray = [exception callStackSymbols];
// 出现异常的原因
NSString * reason = [exception reason];
// 异常名称
NSString * name = [exception name];
NSString * exceptionInfo = [NSString stringWithFormat:@"========uncaughtException异常错误报告========\nname:%@\nreason:\n%@\ncallStackSymbols:\n%@", name, reason, [stackArray componentsJoinedByString:@"\n"]];
// 保存崩溃日志到沙盒cache目录
[DoraemonCrashTool saveCrashLog:exceptionInfo fileName:@"Crash(Uncaught)"];
// 调用之前崩溃的回调函数
if (previousUncaughtExceptionHandler) {
previousUncaughtExceptionHandler(exception);
}
// 杀掉程序,这样可以防止同时抛出的SIGABRT被SignalException捕获
kill(getpid(), SIGKILL);
}
大图检测

可以设置一个阈值,大于多少k的网络图片,需要被显示出来,预警大图,默认是50k。我大概浏览了下我们项目,类似的封面,大部分都在180-300k之间,部分帖子详情中的图,更大一点。虽然webp可以大幅减少网络传输,但是原图很大依然影响不小。这个功能也很好用,可以知道哪些图过大,导致加载过慢的问题,可以联系修改封面之类的。
源码我也看了一下,hook了NSURLSessionConfiguration的两个方法,defaultSessionConfiguration,ephemeralSessionConfiguration,hook了网络请求,然后再返回数据类型为image的请求做了处理,显示在对应的imageView上,还是非常巧妙的。
使用中发现这个功能会使页面掉帧,变得不流畅。
颜色吸管
这个功能对我们来说其实是非常便利的一个功能,因为我们经常在开发过程中,UI设计师就过来,问我们说项目中某个地方的色值给他发一下,我们需要需找对应的代码,查看对应的色值给他,对于冷门或者不熟悉的模块,找这个颜色其实是很费时费力的事情。有了这个就可以实时的看到对应的颜色16进制色值,有效的节约了时间。

以上就是我觉得这个框架几个比较实用和我觉得比较重要的几个功能。那么如果接入呢?其实很简单。
接入方法
直接cocoapod接入就好了,他提供了只在debug模式下加入,所以大可放心,可直接接入项目,不会影响线上的。
pod 'DoraemonKit/Core', '~> 1.2.3', :configurations => ['Debug']
最后附上这个工具的github地址和简介,有1W+星了。
https://github.com/didi/DoraemonKit
一、常用工具
【App 信息查看】 快速查看手机信息,App 信息,权限信息的渠道,避免去手机设置查找或者查看项目源代码的麻烦;
【沙盒浏览】 App 内部文件浏览的功能,支持删除和预览, 并且能通过 AirDrop 或者其他分享方式上传到 PC 中,进行更加细致的操作;
【MockGPS】 App 能定位到全国各地,支持地图地位和手动输入经纬度;
【H5任意门】 开发测试同学可以快速输入 H5 页面地址,查看该页面效果;
【Crash查看】 方便本地打印出出现 Crash 的堆栈;
【子线程UI】 快速定位哪一些 UI 操作在非主线程中进行渲染,避免不必要的问题;(iOS独有)
【清除本地数据】 一键删除沙盒中所有数据;
【NSLog】 把所有 NSLog 信息打印到UI界面,避免没有开发证书无法调试的尴尬;
【Lumberjack】 每一条 CocoaLumberjack 的日志信息,都在在 App 的界面中显示出来,再也不需要导出日志这么麻烦;(iOS独有)
【DBView】 通过网页方便快捷的操作应用内数据库,让数据库的调试变得非常优雅;
【模拟弱网】 限制网速,模拟弱网环境下App的运行情况。(android独有)
二、性能检测
【帧率】 App 帧率信息提供波形图查看功能,让帧率监控的趋势更加明显;
【CPU】 App CPU 使用率信息提供波形图查看功能,让 CPU 监控的趋势更加形象;
【内存】 App 内存使用量信息提供波形图查看功能,让内存监控的趋势更加鲜明;
【流量】 拦截 App 内部流量信息,提供波形图展示、流量概要展示、流量列表展示、流量筛选、流量详情,对流量信息统一拦截,成为我们 App 中自带的 “Charles”;
【卡顿】 锁定 App 出现卡顿的时刻,打印出对应的代码调用堆栈;
【大图检测】 通过流量监测,找出所有的大小超标的图片,避免下载大图造成的流量浪费和渲染大图带来的CPU消耗。
【自定义】 可以选择你要监控的选项,包括 FPS、CPU、内存、流量。监控完毕之后,把数据保存到本地,按照页面进行维度进行分析;
【启动耗时】 无侵入的统计出App启动过程的总共耗时;
【UI层级检查】 检查出每一个页面中层级最深的元素;
【函数耗时】 从函数级别分析app性能瓶颈;
【Load】 找出所有的Load方法,并给出耗时分析;(iOS独有)
【内存泄漏】 找出App中所有的内存泄漏的问题。
三、视觉工具
【颜色吸管】 方便设计师 UI 捉虫的时候,查看每一个组件的颜色值是否设置正确;
【组件检查】 可以抓取任意一个UI控件,查看它们的详细信息,包括控件名称、控件位置、背景色、字体颜色、字体大小;
【对齐标尺】 参考 Android 系统自带测试工具,能够实时捕获屏幕坐标,并且可以查看组件是否对齐;
【元素边框线】 绘制出每一个 UI 组件的边框,对于组件布局有一定的参考意义。
四、Weex专项工具(CML专项工具)
【console日志查看】 方便在端上查看每一个Weex文件中的console日志,提供分级和搜索功能;
【storage缓存查看】 将Weex中的storage模块的本地缓存数据可视化展示;
【容器信息】 查看每一个打开的Weex页面的基本信息和性能数据;
【DevTool】 快速开启Weex DevTool的扫码入口。
tips :如果使用我们滴滴优秀的开源跨端方案 chameleon 也可以集成该工具集合
五、支持自定义的业务工具集成到面板中
统一维护和管理所有的测试模块,详见接入手册
六、微信小程序专项工具
拖地先生,从事互联网技术工作,在这里每周两篇文章,一起聊聊日常的技术点滴和管理心得。

如果对你有帮助,让大家也看看呗~




