味精大佬带你看WWDC19:Advances in App Background Execution

来自:折腾范儿の味精

WWDC2019 Session 707 : Advances in App Background Execution[1]

上面是 session 视频原 link ,大概 40 min,整个视频分为两部分,重点介绍了在 App 的开发中使用 Background Execution 的一些场景与建议,以及新的 BackgroundTask Framework

  • Background Execution 介绍

    • 谨慎考虑后台执行任务

    • 立刻执行的短时间后台任务

    • VoIP 推送的后台任务

    • 静默 APNS 推送的后台任务

    • 后台下载任务 Background URL Session

  • 新的 BackgroundTask Framework

    • 新框架简介

    • 后台任务时间管理

    • 代码详解

Background Execution 介绍

所有的后台执行任务一般由2种机制触发

  • 一种是 App 的主动请求,当 App 进入后台的时候,主动请求延续前台未执行完的操作

  • 一种是 Event 被动触发,一些设备的特殊事件触发了后台任务执行,比如用户进入了某些区域等

谨慎考虑后台执行任务

apple 希望开发者慎重考虑,设计后台要执行的任务。因为后台任务会直接影响到用户的

  • 电量

  • 性能

  • 隐私

从这样的一幅图可以看出来,每一行都是一个 App 的时间轴,从早晨开始使用手机到晚上手机充电,FG 代表这 App 在前台运行,BG 代表 App 在后台执行。

  • 电量:FG 代表着用户的主动选择,但每个 App 各自开格子的 BG ,就会导致额外的电量消耗,从而增加用户整体的手机电量消耗

  • 性能:当一个 App 在前台 FG 执行的时候,可能其他 App 在后台正在执行 BG ,当过多的 App 的 BG 在同一时间进行执行,势必消耗更多的 CPU ,从而影响前台 App FG 的性能表现

  • 隐私:后台执行任务是用户不可感知的,所以要谨慎在后台任务中处理那些用户隐私敏感的数据

立刻执行的短时间后台任务(Sending Message)

一般用于在用户主动进入后台的时候,给 App 一些额外的时间,允许在后台运行一些操作(例如磁盘存储,信息发送等),这类后台任务一般都是开始于 App 的前台运行,结束终止在 App 的后台任务。

func send(_ message: Message) {
    let sendOperation = SendOperation(message: message)
    var identifier: UIBackgroundTaskIdentifier!
    identifier = UIApplication.shared.beginBackgroundTask(expirationHandler: {
        sendOperation.cancel()
        postUserNotification("Message not sent, please resend")
        // Background task will be ended in the operation's completion block below
    })
    sendOperation.completionBlock = {
        UIApplication.shared.endBackgroundTask(identifier)
    }
    operationQueue.addOperation(sendOperation)
}

在后台任务执行完毕后,要确保执行 endBackgroundTask 来告知系统终止任务,如果任务执行时间过长会触发 expirationHandler ,可以写一条本地推送来告知用户“消息后台发送失败,请打开 App 重新发送”

VoIP 推送的后台任务

这类后台任务可以使用 VoIP 的这种推送的方式触发执行,VoIP 基于 PushKit 相比于 APNS 好处是可以通过后台推送,立刻直接唤醒 App 去执行一些后台任务,

VoIP 只有在网络电话这样的功能才能申请,多用于配合 CallKit 直接在收到推送的时候,发起通话界面

func registerForVoIPPushes() {
    self.voipRegistry = PKPushRegistry(queue: nil)
    self.voipRegistry.delegate = self
    self.voipRegistry.desiredPushTypes = [.voIP]
}

let provider = CXProvider(configuration: providerConfiguration)
func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload:
PKPushPayload, for type: PKPushType, completion: @escaping () -> Void) {
    if type == .voIP {
        if let handle = payload.dictionaryPayload["handle"] as? String {
            let callUpdate = CXCallUpdate()
            callUpdate.remoteHandle = CXHandle(type: .phoneNumber, value: handle)
            let callUUID = UUID()
            provider.reportNewIncomingCall(with: callUUID, update: callUpdate) { _ in
                completion()
            }
            establishConnection(for: callUUID)
        }
     }
}

上面就是一个基本的 VoIP 的代码,先注册 PKPushRegistry ,然后在 didReceiveIncomingPushWith 的时候唤起 CallKit 执行网络电话功能

静默 APNS 推送的后台任务

除了 VoIP 这种推送方式,还可以使用 APNS 这种我们最熟悉的推送,苹果有 Background Fetch & Remote Notification 这些能力通过 APNS 提供后台任务执行的机会

  • 首先配置一个静音的,没有提示的推送

  • 服务器下发这个“静音”推送

  • 当 App 收到这个“静音”推送的时候

  • 系统会选择恰当的时机,触发并执行后台任务(下载一些内容数据)

  • 用户回到前台,看到最新的界面数据

后台下载任务 Background URL Session

普通的网络请求大家都用过是 URLSession ,还有一种可以专门在后台期间执行下载的网络请求即 Background URLSession

// Set up background URL session
let config = URLSessionConfiguration.background(withIdentifier: "com.app.attachments")
let session = URLSession(configuration: config, delegate: ..., delegateQueue: ...)
// Set discretionary
config.discretionary = true

通过 URLSessionConfiguration.background 来创建的 URLSession 就具有后台下载的能力,而设置 discretionary 可以更进一步定义后台下载的时机触发准则(并发精准触发条件,而是由系统衡量在合适的时机进行下载)

// 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
task.earliestBeginDate = Date(timeIntervalSinceNow: 2 * 60 * 60)
// Set workload size
task.countOfBytesClientExpectsToSend = 160
task.countOfBytesClientExpectsToReceive = 4096
task.resume()

可以通过 earliestBeginDate 来设置后台下载任务不早于一定时间开始,countOfBytesClientExpectsToSend 和 countOfBytesClientExpectsToReceive 可以设置后台下载任务大概所消耗的流量,从而让系统更合适的安排后台下载的触发

新的 BackgroundTask Framework

新框架简介

全新的后台任务框架,可以更精准的规划后台任务的触发时间点,可以对所有后台任务规划一个时间表,根据不同任务的配置,来实现更灵活的后台任务触发,甚至可以实现一些后台任务,只有在充电时间才会执行

新的 BackgroundTask 有两种类型

  • Background Processing Tasks 后台处理型任务

    • 可以执行一些大型数据维护等工作

    • 也可以执行 CoreML 等机器学习的训练

    • 在系统认为合适的不同时机,触发执行几分钟

    • 可以关闭 CPU 的监控来执行密集工作

  • Background App Refresh Tasks

    • 30 秒钟的执行时间

    • 可以让 App 全天保持数据最新

苹果系统会记录并学习用户使用 App 的日常习惯,然后在判断用户将要打开 App 之前,触发 Background App Refresh Tasks 。如果 App 用户平时使用频率很低,将不会触发

后台任务时间管理

如图所示:

可以在 App 前台,输入法扩展,分享扩展的前台代码中提交 BackgroundTask 给 BGTaskScheduler 这个后台任务管理器。

BGTaskScheduler 会根据 电量状态/时间/联网状态/设置 来判断适合的时间来执行对应的任务,当任务需要执行来,会激活 App 并执行该后台任务

代码详解

后面的视频中有详细的一整套 demo ,代领大家体验 Background Processing Tasks 与 Background App Refresh Tasks 两种 Task。整个视频介绍内容覆盖很全面,包括:

  • 工程配置

  • Demo 编写

  • 运行展示

  • 编译调试

  • 使用建议

参考

[1]https://developer.apple.com/videos/play/wwdc2019/707/

推荐↓↓↓
iOS开发
上一篇:大型 SDK 组件化拆分的一些思考 下一篇:SwiftUI vs. Flutter