C#速成指南:从入门到进阶,实战WPF与Unity3D开发无密

493 阅读2分钟

download:C#速成指南:从入门到进阶,实战WPF与Unity3D开发无密

VSync信号到来后,系统图形服务会通过 CADisplayLink 等机制通知 App,App 主线程开始在CPU中计算显示内容。随后 CPU 会将计算好的内容提交到 GPU 去,由GPU进行变换、合成、渲染。随后 GPU 会把渲染结果提交到帧缓冲区去,等待下一次 VSync 信号到来时显示到屏幕上。由于垂直同步的机制,如果在一个 VSync 时间内,CPU 或者 GPU 没有完成内容提交,则那一帧就会被丢弃,等待下一次机会再显示,而这时显示屏会保留之前的内容不变。所以可以简单理解掉帧过时不候

如下图所示,是一个显示过程,第1帧在VSync到来前,处理完成,正常显示,第2帧在VSync到来后,仍在处理中,此时屏幕不刷新,依旧显示第1帧,此时就出现了掉帧情况,渲染时就会出现明显的卡顿现象

image.png

从图中可以看出,CPU和GPU不论是哪个阻碍了显示流程,都会造成掉帧现象,所以为了给用户提供更好的体验,在开发中,我们需要进行卡顿检测以及相应的优化

卡顿监控

卡顿监控的方案一般有两种:

  • FPS监控:为了保持流程的UI交互,App的刷新拼搏应该保持在60fps左右,其原因是因为iOS设备默认的刷新频率是60次/秒,而1次刷新(即VSync信号发出)的间隔是 1000ms/60 = 16.67ms,所以如果在16.67ms内没有准备好下一帧数据,就会产生卡顿
  • 主线程卡顿监控:通过子线程监测主线程的RunLoop,判断两个状态(kCFRunLoopBeforeSourceskCFRunLoopAfterWaiting)之间的耗时是否达到一定阈值

FPS监控

FPS的监控,参照YYKit中的YYFPSLabel,主要是通过CADisplayLink实现。借助link的时间差,来计算一次刷新刷新所需的时间,然后通过 刷新次数 / 时间差 得到刷新频次,并判断是否其范围,通过显示不同的文字颜色来表示卡顿严重程度。代码实现如下:

class CJLFPSLabel: UILabel {

    fileprivate var link: CADisplayLink = {
        let link = CADisplayLink.init()
        return link
    }()
    
    fileprivate var count: Int = 0
    fileprivate var lastTime: TimeInterval = 0.0
    fileprivate var fpsColor: UIColor = {
        return UIColor.green
    }()
    fileprivate var fps: Double = 0.0
    
    override init(frame: CGRect) {
        var f = frame
        if f.size == CGSize.zero {
            f.size = CGSize(width: 80.0, height: 22.0)
        }
        
        super.init(frame: f)
        
        self.textColor = UIColor.white
        self.textAlignment = .center
        self.font = UIFont.init(name: "Menlo", size: 12)
        self.backgroundColor = UIColor.lightGray
        //通过虚拟类
        link = CADisplayLink.init(target: CJLWeakProxy(target:self), selector: #selector(tick(_:)))
        link.add(to: RunLoop.current, forMode: RunLoop.Mode.common)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    deinit {
        link.invalidate()
    }
    
    @objc func tick(_ link: CADisplayLink){
        guard lastTime != 0 else {
            lastTime = link.timestamp
            return
        }
        
        count += 1
        //时间差
        let detla = link.timestamp - lastTime
        guard detla >= 1.0 else {
            return
        }
        
        lastTime = link.timestamp
        //刷新次数 / 时间差 = 刷新频次
        fps = Double(count) / detla
        let fpsText = "(String.init(format: "%.2f", fps)) FPS"
        count = 0
        
        let attrMStr = NSMutableAttributedString(attributedString: NSAttributedString(string: fpsText))
        if fps > 55.0 {
            //流畅
            fpsColor = UIColor.green
        }else if (fps >= 50.0 && fps <= 55.0){
            //一般
            fpsColor = UIColor.yellow
        }else{
            //卡顿
            fpsColor = UIColor.red
        }
        
        attrMStr.setAttributes([NSAttributedString.Key.foregroundColor: fpsColor], range: NSMakeRange(0, attrMStr.length - 3))
        attrMStr.setAttributes([NSAttributedString.Key.foregroundColor: UIColor.white], range: NSMakeRange(attrMStr.length - 3, 3))
        
        DispatchQueue.main.async {
            self.attributedText = attrMStr
        }
    }

}