iOS开发中如何监控ANR

395 阅读3分钟

在 iOS 开发中,使用 Objective-C 监控 ANR(Application Not Responding)和卡顿,主要依靠监控主线程的响应能力。由于 iOS 没有明确的 ANR 定义像 Android 那样,通常情况下是监控主线程的阻塞时间来判断应用是否卡顿或无响应。

以下是一些常用的卡顿/ANR 监控和防治手段:

一、监控 ANR/卡顿

  1. RunLoop 监控:

    这是最常用的方法。RunLoop 负责处理 App 中的各种事件(例如用户输入、网络请求、定时器等)。通过监控 RunLoop 的状态,可以判断主线程是否卡顿。

    • 原理: 在主线程 RunLoop 的每个循环开始和结束时记录时间戳。如果两次时间戳的差值超过预设的阈值(例如 5 秒),则认为发生了卡顿。

    • 代码示例:

      #import "ANRMonitor.h"
      
      @interface ANRMonitor ()
      @property (nonatomic, strong) CFRunLoopObserverRef observer;
      @property (nonatomic, assign) CFRunLoopActivity activities;
      @property (nonatomic, assign) NSTimeInterval lastRunLoopStartTime;
      @end
      
      @implementation ANRMonitor
      
      + (instancetype)sharedInstance {
          static ANRMonitor *instance = nil;
          static dispatch_once_t onceToken;
          dispatch_once(&onceToken, ^{
              instance = [[ANRMonitor alloc] init];
          });
          return instance;
      }
      
      - (void)startMonitoring {
          self.lastRunLoopStartTime = 0;
          CFRunLoopObserverContext context = {0, (__bridge void*)self, NULL, NULL, NULL};
          self.observer = CFRunLoopObserverCreate(kCFAllocatorDefault,
                                                 kCFRunLoopAllActivities,
                                                 YES,
                                                 0,
                                                 &runLoopObserverCallBack,
                                                 &context);
          CFRunLoopAddObserver(CFRunLoopGetMain(), self.observer, kCFRunLoopCommonModes);
      }
      
      static void runLoopObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) {
          ANRMonitor *monitor = (__bridge ANRMonitor*)info;
          monitor.activities = activity;
      
          if (activity == kCFRunLoopBeforeSources || activity == kCFRunLoopAfterWaiting) {
              monitor.lastRunLoopStartTime = CACurrentMediaTime();
          } else if (activity == kCFRunLoopBeforeWaiting) {
              NSTimeInterval currentTime = CACurrentMediaTime();
              NSTimeInterval runLoopDuration = currentTime - monitor.lastRunLoopStartTime;
      
              if (runLoopDuration > 5) { // 超过 5 秒视为卡顿
                  NSLog(@"Main thread stuck for %f seconds.", runLoopDuration);
                  // 可以在这里收集卡顿信息,例如堆栈、CPU 使用率等
              }
          }
      }
      
      - (void)stopMonitoring {
          if (self.observer) {
              CFRunLoopRemoveObserver(CFRunLoopGetMain(), self.observer, kCFRunLoopCommonModes);
              CFRelease(self.observer);
              self.observer = NULL;
          }
      }
      
      @end
      
      • 使用方法:AppDelegateapplication:didFinishLaunchingWithOptions: 方法中调用 [[ANRMonitor sharedInstance] startMonitoring]; 开始监控。
  2. 主线程 Ping:

    创建一个子线程,定期向主线程发送消息。如果主线程在一定时间内没有响应消息,则认为主线程卡顿。

  3. 性能监控工具:

    • Instruments: Xcode 自带的 Instruments 工具可以用来分析 CPU 使用率、内存分配、图形渲染等性能指标,帮助定位卡顿原因。
    • 第三方性能监控平台: 像是 Sentry 3 和 AppDynamics [2, 5](docs.appdynamics.com/display/PRM…, docs.appdynamics.com/display/PRM…) 等,可以收集崩溃报告、性能数据,并提供可视化的分析工具。

二、卡顿防治手段

  1. 避免在主线程执行耗时操作:

    • 将耗时任务(例如网络请求、图片加载、复杂计算)放到子线程执行,完成后再回到主线程更新 UI。可以使用 GCD 或 NSOperationQueue 来管理子线程。
  2. 优化 UI 渲染:

    • 减少视图层级,使用 Auto Layout 优化布局计算。
    • 避免离屏渲染,尽量使用不透明视图。
    • 使用预加载和缓存机制,减少不必要的计算和绘制。
  3. 优化数据处理:

    • 使用合适的数据结构和算法,避免不必要的遍历和查找。
    • 使用缓存机制,减少重复计算。
  4. 代码优化:

    • 避免循环引用,及时释放不再使用的对象。
    • 避免使用过多的锁,减少线程竞争。

总结:

监控 ANR/卡顿需要结合多种手段,才能更全面地了解应用的性能状况。选择合适的监控方案和防治手段,可以有效提升应用的流畅度和用户体验。