iOS性能优化-卡顿

2022-12-11  本文已影响0人  wuyukobe

前言:本文旨在介绍iOS性能优化中有关页面卡顿的产生、优化以及监控。

一、屏幕的显示

图片加载到显示的过程:通常计算机在显示图片都是CPU与GPU协同合作完成一次渲染工作。在屏幕成像的过程中CPU与GPU起着至关重要的作用。

1、CPU(Central Processing Unit,中央处理器)

CPU负责对象的创建和销毁、对象属性的调整、布局计算、文本的计算和排版、图片的格式转换和解码以及图像的绘制。

2、GPU(Graphics Processing Unit,图形处理器)

GPU负责纹理的渲染


二、卡顿原因

1、产生卡顿的原因:

2、图片撕裂原理:

页面撕裂:图片渲染是需要经过CPU和GPU的共同处理,但是如果CPU和GPU流水线耗时过长,可能出现的情况是,当扫描完当前帧缓存区的图片之后,下一帧还未渲染完成,这时候显示器会从帧缓存区继续扫描旧的图片,因为扫描过程是一行一行扫描的,而如果在旧的图片扫到一部分时发现下一帧新图片来了,然后显示器就会立即扫描下一帧的图片,此时就会出现上半部图片是上一帧的图片,下半部是新的一帧,导致图片看起来是撕裂的,也就是出现了图片撕裂的问题。

3、卡顿解决方法

三、CPU层面的卡顿优化

四、GPU层面的卡顿优化

五、离屏渲染

1、在OpenGL中,GPU有2种渲染方式
2、离屏渲染消耗性能的原因
3、哪些操作会触发离屏渲染?

六、RunLoop监控卡顿原理

相关代码参考(类名 APMANRMonitorPlugin)
// 创建一个Observer来监听RunLoop
- (void)addRunLoopObserver {
    NSRunLoop *curRunLoop = [NSRunLoop mainRunLoop];
    
    //1.注册default RunloopMode
    //监听开始的observer
    CFRunLoopObserverContext context = {0, (__bridge void *) self, NULL, NULL, NULL};
    CFRunLoopObserverRef beginObserver = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, LONG_MIN, &RunLoopBeginCallback, &context);
    CFRetain(beginObserver);
    //监听结束的observer
    CFRunLoopObserverRef endObserver = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, LONG_MAX, &RunLoopEndCallback, &context);
    CFRetain(endObserver);
    //监听监听
    CFRunLoopAddObserver([curRunLoop getCFRunLoop], beginObserver, kCFRunLoopCommonModes);
    CFRunLoopAddObserver([curRunLoop getCFRunLoop], endObserver, kCFRunLoopCommonModes);
    //保留observer
    runLoopBeginObserver = beginObserver;
    runLoopEndObserver = endObserver;
}
// RunLoop的状态
//最高优先级
void RunLoopBeginCallback(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) {
    g_RunloopActivity = activity;
    g_blockArised = NO;
  
    switch (activity) {
        case kCFRunLoopEntry:
            g_Run = YES;
            break;
        case kCFRunLoopBeforeTimers:
            if (g_Run == NO) {
                gettimeofday(&g_TimevalMark, NULL);
            }
            g_Run = YES;
            break;
        case kCFRunLoopBeforeSources:
            if (g_Run == NO) {
                gettimeofday(&g_TimevalMark, NULL);
            }
            g_Run = YES;
            break;
        case kCFRunLoopAfterWaiting:
            if (g_Run == NO) {
                gettimeofday(&g_TimevalMark, NULL);
            }
            g_Run = YES;
            break;
        case kCFRunLoopAllActivities:
            break;
        default:
            break;
    }
}
// 开启一个子线程,在while循环中监控RunLoop的状态
- (void)addMonitorThread {
    monitorThread = [[NSThread alloc] initWithTarget:self selector:@selector(threadMonitoringProcess) object:nil];
    [monitorThread start];
}
- (void)threadMonitoringProcess {
    //保存堆栈信息
    g_BlockStartTimeval = {0,0};
    APMDumpType reportDumpType = APMDumpTypeNormal;
    
    // 卡死卡顿信息打包上报
    [self packageBlockToKillReport];
    
    //重置监控状态
    [self resetANRMonitorStatus];
    
    while (YES) {
        @autoreleasepool {
            //退出策略
            if (_bStop) { break; }
            
            APMDumpType dumpType = [self checkDumpType];
                        
            if (g_Run == YES && dumpType != APMDumpTypeNormal) {
                if (g_BlockReportPath.length <= 0) {
                    g_BlockReportPath = [APMANRMonitorTool dumpReportWithType:dumpType];
                    g_BlockStartTimeval = g_TimevalMark;
                    g_blockArised = YES;
                }
                reportDumpType = dumpType;
            }else{
                if (g_blockArised == NO && g_BlockReportPath.length > 0 && VALID_TIMEVAL(g_BlockStartTimeval)) {
                    //保存当前堆栈信息并上传
                    struct timeval cur;
                    gettimeofday(&cur, NULL);
                    [self recordBlockReportWithStartTimeval:g_BlockStartTimeval
                                                 endTimeval:cur
                                                   dumpType:reportDumpType];
                    //重置监控状态
                    [self resetANRMonitorStatus];
                }
            }
            
            //睡一会
            usleep(_defaultCheckPeriodTime);
            //退出策略
            if (_bStop) { break; }
        }
    }
}

七、Runloop监听卡顿为什么是beforesource,afterwaiting这两个?

 ----------BeforeTimers
 ----------BeforeSources
 ++++++++++ 处理Block
 ++++++++++ 处理Source0(非port),手动处理
 ++++++++++ 如果Source0处理成功,则处理Block
 ==== 如果有Source1事件,直接跳转到HandleMSG

 ----------BeforeWaitting
 ==== 
  唤醒条件【即有任务待处理】:
  runloop时间超时,timer触发,Port有消息出发, 被手动唤醒
 ====
 ----------AfterWaiting

 ==== HandleMSG【被唤醒后需要处理的任务】
 ++++++++++++ 处理计时器事件
 ++++++++++++ 处理主异步到主队列的消息
 ++++++++++++ 处理source1事件
 ++++++++++++ 处理block事件

从上面任务执行流程可以看出:其主要执行任务的时机为:BeforeSources和AfterWaiting。


以上是有关卡顿的介绍,欢迎补充和指正。
上一篇下一篇

猜你喜欢

热点阅读