Highlight
Apple 软件功耗团队把电池优化拆成四个可执行动作:支持深色模式、审查动画帧率、结束后台位置和音频会话、把非紧急工作交给系统延后执行。
核心内容
用户打开一个 App,不会先问它用了多少 CPU。用户只会感到手机发热、掉电快,然后关闭它。
这场 session 的切入点很直接:App 提供功能,但功能会消耗电池。Apple 软件功耗团队长期研究系统组件的功耗,在这里给出四类开发者能直接控制的改动(00:37)。
第一类改动发生在屏幕上。OLED 设备中,每个像素单独耗电,深色内容比浅色内容耗电少。Food Truck 示例里,大面积背景切到深色后,Apple 预计可节省最高 70% 的显示功耗(02:24)。
第二类改动发生在动画里。ProMotion 设备上,屏幕刷新率受 App 中最高帧率动画影响。主内容只需要 30fps,如果一个次要滚动文字跑到 60fps,整屏都会被拉到 60fps。把这个次要动画降到 30fps,示例中可节省最高 20% 的电池消耗(05:42)。
第三类改动发生在后台。位置和音频服务在后台持续运行,会让设备保持活跃。用户看不见你的 App,但系统仍在为它耗电。session 的原则是:不用时明确告诉系统结束(09:21)。
第四类改动发生在调度上。机器学习、备份、遥测上传、下一集下载,并不一定要马上做。把这些工作推迟到设备充电、连上 Wi-Fi 或处于合适窗口时执行,可以减少电池竞争(13:18)。
详细内容
用深色模式降低 OLED 显示功耗
(01:24)OLED 屏幕上,深色像素需要更少功率。显示又经常是系统功耗的大头,所以大面积浅色背景的 App 最容易从深色模式受益。
原生 App 的做法是避免硬编码颜色,改用动态颜色,让系统在 Light Mode 和 Dark Mode 之间切换正确的背景、文字和图片资源(03:18)。
// 概念示例:用动态颜色代替硬编码浅色背景
view.backgroundColor = UIColor.systemBackground
label.textColor = UIColor.label
关键点:
UIColor.systemBackground会随系统外观变化,深色模式下变为深色背景。UIColor.label同样是动态颜色,能在浅色和深色模式下保持文字可读。- 这段代码是基于 transcript 中“使用动态颜色支持背景、图片和文字”的概念示例,不是 session Code tab 原始片段。
(04:00)Web 内容也要单独处理。Safari 不会自动把网页变暗。网页需要声明 color-scheme,并用样式变量管理颜色。
:root {
color-scheme: light dark;
--page-background: Canvas;
--page-text: CanvasText;
}
body {
background: var(--page-background);
color: var(--page-text);
}
关键点:
color-scheme: light dark让默认文字、背景、表单控件和滚动条匹配系统外观。Canvas和CanvasText是系统颜色,会随当前配色方案变化。var(--page-background)和var(--page-text)把颜色集中到变量里,便于为不同模式提供不同值。- 这段 CSS 是基于 transcript 中
color-scheme和 stylesheet variables 的概念示例。
用 Instruments 找到过高的次要动画帧率
(05:11)高刷新率会提高功耗。问题常出在次要元素:主内容只需要 30fps,角落里的装饰动画却以 60fps 刷新。
Apple 建议先用 Instruments 的 CoreAnimation FPS instrument 看时间线,确认主流程中是否有元素以超出需要的帧率渲染(06:36)。如果你的自定义动画使用 CADisplayLink,可以给系统一个帧率范围。
// Create a display link
func createDisplayLink() {
let displayLink = CADisplayLink(target: self, selector: #selector(step))
// Configure your desired refresh rate by calling preferredFrameRateRange
displayLink.preferredFrameRateRange = CAFrameRateRange(minimum: 10,
maximum: 60,
preferred: 30)
// then activate your CADisplayLink by adding it to the main runloop.
displayLink.add(to: .current, forMode: .defaultRunLoopMode)
}
关键点:
CADisplayLink(target:selector:)创建一个和显示刷新同步的计时器。#selector(step)指向每次刷新时要执行的自定义动画逻辑。preferredFrameRateRange告诉系统最低、最高和首选帧率。minimum: 10表示系统可在需要时降到 10fps。maximum: 60表示这段动画不要求超过 60fps。preferred: 30表示内容的理想帧率是 30fps。add(to:forMode:)把 display link 加入当前 run loop 后,动画才会开始接收刷新回调。
后台位置和音频:用完就停
(09:49)后台位置会让设备持续流式获取定位。session 明确指出,不再需要位置会话时,要调用 stopUpdatingLocation()。
// 概念示例:后台定位任务结束后停止位置更新
locationManager.stopUpdatingLocation()
关键点:
locationManager是你的CLLocationManager实例。stopUpdatingLocation()结束持续位置更新,避免 App 不可见时继续消耗电池。- 这段代码对应 transcript 中直接提到的 API 调用。
(10:25)排查手段分阶段使用。开发时看 Xcode gauges 的 CPU、Network、Location 和 energy timeline。测试阶段用 MetricKit 的 cumulativeBackgroundLocationTime 查看一天使用中后台位置累计时长。iOS 16 起,用户也能在 Control Center 看到当前使用位置服务的 App。
音频也遵循同一个原则。用户停止播放后,App 不应只暂停声音,还要停止或暂停 Audio Engine,避免音频硬件空转(12:03)。
// 概念示例:让 AVAudioEngine 空闲后自动关闭音频硬件
audioEngine.autoShutdownEnabled = true
关键点:
audioEngine是AVAudioEngine实例。autoShutdownEnabled开启后,音频引擎会持续检测空闲状态。- 空闲一段时间后,系统会关闭音频硬件;音源再次活跃时,再动态启动。
- watchOS 上,auto shutdown 是强制行为。
把非紧急工作延后给系统调度
(13:37)session 把可延后的工作举成三类:机器学习任务、上传 analytics、备份。它们不直接服务用户当前操作,可以交给系统选择更合适的时间。
BGProcessingTask 适合长时间处理任务,例如数据库清理、备份、机器学习训练。创建请求时提供任务标识符,再说明是否需要外接电源或网络,系统会在合适窗口唤醒 App,并给几分钟后台运行时间(14:22)。
// 概念示例:提交一个可延后的长时间处理任务
let request = BGProcessingTaskRequest(identifier: "com.app.cleanup")
request.requiresExternalPower = true
request.requiresNetworkConnectivity = false
try BGTaskScheduler.shared.submit(request)
关键点:
BGProcessingTaskRequest表示一个长时间后台处理请求。identifier要和 App 注册的后台任务标识符一致。requiresExternalPower告诉系统这项工作适合在充电时执行。requiresNetworkConnectivity告诉系统这项工作是否依赖网络。submit把请求交给系统调度;具体执行时机由系统决定。- 这段代码是基于 transcript 中 BGProcessingTaskRequest、外接电源和网络需求说明的概念示例。
(15:12)可延后的网络任务使用 background URLSession 加 discretionary flag。系统可以在设备插电、连 Wi-Fi 等更优条件下执行,而且网络事务交给系统后,App 不需要一直运行。
// Set up background URL session
let config = URLSessionConfiguration.background(withIdentifier: "com.app.attachments")
let session = URLSession(configuration: config, delegate: ..., delegateQueue: ...)
// Set discretionary
config.isDiscretionary = true
// Set timeout intervals
config.timeoutIntervalForResource = 24 * 60 * 60
config.timeoutIntervalForRequest = 60
// Create request and task
var request = URLRequest(url: url)
request.addValue("...", forHTTPHeaderField: "...")
let task = session.downloadTask(with: request)
// Set time window of two hours
task.earliestBeginDate = Date(timeIntervalSinceNow: 2 * 60 * 60)
// Set workload size
task.countOfBytesClientExpectsToSend = 160
task.countOfBytesClientExpectsToReceive = 4096
task.resume()
关键点:
URLSessionConfiguration.background(withIdentifier:)创建后台 URL session 配置。URLSession(configuration:delegate:delegateQueue:)用这个配置创建 session。isDiscretionary = true表示网络事务可由系统延后到更合适的时间。timeoutIntervalForResource限制整个资源传输的最长时间,避免无限下载。timeoutIntervalForRequest限制单次请求等待时间。URLRequest(url:)创建下载请求。downloadTask(with:)创建后台下载任务。earliestBeginDate告诉系统两小时后再开始也可以。countOfBytesClientExpectsToSend和countOfBytesClientExpectsToReceive提供预期工作量,帮助系统做负载均衡。resume()启动任务。
(16:48)推送也有紧急程度。严重天气警告适合高优先级;不紧急的被动通知可以用低优先级,让服务器延后到设备唤醒或其他合适时机发送。
apns-priority: 5
关键点:
apns-priority是 APNs 请求头。- 值
5表示低优先级推送。 - 低优先级推送适合可延后的消息,减少设备从睡眠中被唤醒的次数。
核心启发
-
做一个“后台定位体检”调试页:显示当前定位会话是否仍在运行、最近一次停止时间、MetricKit 中的后台定位累计时长。这样能在测试阶段发现忘记调用
stopUpdatingLocation()的路径。入口 API 是CLLocationManager.stopUpdatingLocation()和 MetricKit 的cumulativeBackgroundLocationTime。 -
给自定义动画加“目标帧率”配置:把装饰动画、跑马灯、粒子效果的目标帧率降到内容需要的范围。先用 Instruments 的 CoreAnimation FPS instrument 找出过高帧率,再在
CADisplayLink.preferredFrameRateRange中设置preferred值。 -
把大文件下载改成可延后任务:播客、视频、附件预取不必立即抢网络。使用 background
URLSession,设置isDiscretionary、earliestBeginDate和预期字节数,让系统选择插电或 Wi-Fi 等更合适的窗口。 -
为浅色大背景页面补齐深色资源:先找出占屏面积最大的背景色和图片,再改成动态颜色或成对图片资源。OLED 设备上,大面积深色内容能直接降低显示功耗。
-
给服务端推送加优先级规则:把严重告警、即时聊天保留为高优先级;把摘要、推荐、同步提醒改成
apns-priority: 5。这样能减少设备被非紧急消息唤醒的次数。
关联 Session
- Optimize for variable refresh rate displays — 继续讲动态显示时序和 display link,适合深入理解
preferredFrameRateRange。 - Efficiency awaits: Background tasks in SwiftUI — 讲 SwiftUI 中注册和处理后台任务,和本 session 的延后执行策略互补。
- What’s new in MetricKit — 讲如何收集功耗和性能指标,适合验证后台位置、动画和网络优化是否产生效果。
评论
GitHub Issues · utterances