WWDC Quick Look 💓 By SwiftGGTeam
Power down: Improve battery consumption

Power down: Improve battery consumption

观看原视频

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 让默认文字、背景、表单控件和滚动条匹配系统外观。
  • CanvasCanvasText 是系统颜色,会随当前配色方案变化。
  • 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

关键点:

  • audioEngineAVAudioEngine 实例。
  • 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 告诉系统两小时后再开始也可以。
  • countOfBytesClientExpectsToSendcountOfBytesClientExpectsToReceive 提供预期工作量,帮助系统做负载均衡。
  • 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,设置 isDiscretionaryearliestBeginDate 和预期字节数,让系统选择插电或 Wi-Fi 等更合适的窗口。

  • 为浅色大背景页面补齐深色资源:先找出占屏面积最大的背景色和图片,再改成动态颜色或成对图片资源。OLED 设备上,大面积深色内容能直接降低显示功耗。

  • 给服务端推送加优先级规则:把严重告警、即时聊天保留为高优先级;把摘要、推荐、同步提醒改成 apns-priority: 5。这样能减少设备被非紧急消息唤醒的次数。

关联 Session

评论

GitHub Issues · utterances