创建 CADisplayLink,配置其 preferredFramesPerSecond 为 120,然后将其添加到 UITrackingRunLoopMode 中 。CADisplayLink *dp = ...dp.preferredFramesPerSecond = 120;// 或者dp.preferredFrameRateRange = CAFrameRateRangeMake(120.0, 120.0, 0.0);[dp addToRunLoop:[NSRunLoop mainRunLoop] forMode:UITrackingRunLoopMode];在滑动中,该 CADisplayLink 被激活,系统锁定当前帧率为更高 120Hz(仅在内容中高速变化时生效) 。停止滑动时则恢复正常帧率 。
添加 CADisplayLink 至 CommonModes 中,分别在开始/停止滑动时启用/暂停 CADisplayLink,并修改对应的 preferredFramesPerSecond等属性,触发帧率变化 。CADisplayLink *dp = ...dp.paused = YES;[dp addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];CFRunLoopAddObserver(CFRunLoopGetMain(),CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopEntry | kCFRunLoopExit, YES, 0,^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {if (activity == kCFRunLoopEntry) {dp.paused = NO;dp.preferredFramePerSecond = 120;} else {dp.paused = YES;dp.preferredFramePerSecond = 0;}}), (__bridge CFStringRef)UITrackingRunLoopMode);在实践中,由于也存在需要在非滑动状态下解锁帧率上限的情况,所以方案 2 的通用性会更好 。
CAAnimation 设置动态帧率目前苹果只提供了修改 CAAnimation 动画帧率的 API,设置 CAAnimation.preferredFrameRateRange 即可改变其对屏幕刷新率的影响 。
对于用户感知明显的,如转场动画,可以设置为 120Hz 。对于感知不明显的,如旋转动画,可以降低其帧率,比如设置为 30Hz 。但是,和 DisplayLink 相同,过上述 API 的设置虽然会“影响”系统的动态帧率的选择,但这种影响并不是绝对的 。在实际使用中,笔者发现屏幕选择的刷新率和 CAAnimation 在屏幕上变化的速度有关 。
关于此点,以 iPhone 13 Pro 为例,笔者使用了一个简单的、偏好帧率为固定 120Hz 平移动画进行说明:
CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:@"transform.translation.y"];CGFloat speed = 170.0/330.0;anim.toValue = https://www.juguize.com/b/@(100);anim.fromValue = @(0);anim.duration = 10.0;anim.repeatCount = FLT_MAX;anim.preferredFrameRateRange = CAFrameRateRangeMake(120, 120, 120);其中 speed 变量为平移的速度,单位为 pt/s,试验发现:
speed 取 (0, 160] 时,屏幕刷新率为 60Hzspeed 取 [161, 320] 时,屏幕刷新率为 80Hzspeed 取 [321, +∞) 时,屏幕刷新率为 120Hz笔者仅在 iPhone 13 Pro 上测试了平移动画的场景,以上数据仅供参考 。
最后,对于其他的常见的动画 API,例如 UIView.animateWithDuration、UIViewPropertyAnimator 等,则没有提供对应 API 进行修改 。理论上也可以通过某些手段拿到这些上层 API 所创建的 CAAnimation 对象来实现修改 。
手势/转场等其他场景解锁 120Hz其他场景需要控制动态帧率的也可以通过手动修改 CADisplayLink 的 preferredFramePerSecond/preferredFrameRateRange 属性来实现,其实现和通过监听 RunLoop 来修改滑动帧率基本相同 。
UIGestureRecognizer 常被用于实现的交互式动画 。经过测试,发现在触发手势回调的同时启用一个解锁了频率的 CADisplayLink 也可以间接提高 UIGestureRecognizer 的回调频率,从而实现更高帧率的交互动画 。
对于转场的场景,一个简单的方案是 swizzle UIViewController 的生命周期消息,在出现/消失的节点启用/停用 CADisplayLink 帧率的解锁,从而实现通用的页面转场动画帧率解锁方案 。
Flutter 官方也计划提供类似 API 让应用侧可以针对不同的场景(滑动、动画 etc)动态切换屏幕刷新率:https://github.com/flutter/flutter/issues/90675
上线收益基于上述思路,笔者所在团队在国际化短视频业务落地了优化项目,经过实验验证:
大盘滑动帧率 P50 从 81.57 上升至 112.2核心业务指标也有一定收益结语近年来,Apple 生态中软硬件的发展日新月异,有软件层的 dyld 的持续优化和 iOS 15 新引入的 Prewarm 机制,也有新的 ProMotion 屏幕,可以看到 Apple 一直致力于打造更丝滑流畅的用户体验 。
Apple 提供的系统级优化方案一般通用而无感知,但通用往往也意味着一定的局限性,可能预留了额外优化空间,应用开发者们可以进一步去研究如何更好地适配 。
例如本文中,笔者通过研究新引入的 ProMotion 屏幕背后的机制,透过表象/深入汇编管中窥豹看到一部分本质,最终落地了监控 + 优化的方案,让大盘滑动帧率 P50 从 80 上升至 112 左右,取得了额外的业务收益 。
最后,笔者认为,我们普通开发者作为 Apple 生态链中的一环,在享受系统级别优化自动带来的收益的同时,也应该主动去了解上述优化背后的底层原理 。一方面,了解与学习 Apple 的成熟优化思路可以提升我们作为工程师的眼界 。另一方面,对系统底层原理的了解可以拓充我们的“弹药库”,对业务价值交付的全链路了解越广越深,越有可能抓住潜在的优化点,从而在性能优化工程师这条职业道路上走得更远更好 。
推荐阅读
- 小米直播是以前的黑金直播吗
- 小米十的手机屏到什么级别
- 小米9怎么开机方法 小米9怎么开机
- 电池不耐用三招教你手机起死回生,如何让手机电池起死回生
- 小米50寸电视尺寸 50寸电视尺寸
- 有渣食物有哪些
- 南昌小米售后服务点 小米售后服务点
- 小米路由器限制王者荣耀游戏 路由器怎么限制王者荣耀游戏
- 小米MI5,小米mi5x配置
- 南瓜小米粥的功效与作用禁忌 南瓜小米粥