本文主要内容来自WWDC 2019: Advances in App Background Execution
Apple 中很多后台履行都是用户从前台进入后台之后,仍然坚持了一段时刻的活泼,最常见的比方运用UIApplication.shared.beginBackgroundTask()
来恳求更长的代码履行时刻,一起不同的线程后台恳求履行使命的时刻也不同,这一点在我的另一篇文章中亦有深入的探索。
在iOS 13.0之后,Apple出了新的结构 BackgroundTasks
,这个和前者是有很大的不同的,那便是它并不会从前台到后台之后立马履行,而是会规划后台使命履行的时刻,体系主动挑选适宜的时刻来履行该使命,比方手机充电或许搁置的时分。
它总共供给了两个Task来履行,分别是 BGProcessingTask
和 BGAppRefreshTask
。
纵观iOS的后台使命的机制,基本能够分为两类,一类是当即履行的后台使命,比方从前台到后台恳求后台履行时刻完结前台使命、收到后台推送处理内容等等,这一类是当即履行后台使命的类型,还有一类便是延时履行的后台使命,由体系挑选适宜的时刻来履行使命。
当即履行的后台使命
App怎么进入当即履行后台使命的状态呢?也便是当即进入 Background 状态,一般是两种办法:
- App恳求:App想完结某些使命比方下载等等,所以向体系恳求后台履行时刻
- 事件触发:App需求履行后台使命来呼应某些事件,比方音讯推送等等
下面以Message App为例,它涉及到诸多场景都是这种当即履行后台使命的状况。
Send Messages
当服务器呼应很慢的时分,用户或许发送了音讯之后就将手机锁屏了,这种状况需求去保证音讯在后台状态下也能够成功发送。 这种在后台完结前台的使命还有一些场景,比方保存文件到磁盘中、完结用户恳求等等。
这种在前台进入后台后需求额定的时刻来履行使命的场景需求运用 beginBackgroundTask(expirationHandler:)
办法,假如app是在Extension中运转的话,那就需求运用 ProcessInfo.performExpiringActivity(withReason:using:)
办法,代码实例如下:
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)
}
留意 beginBackgroundTask
和 endBackgroundTask
需求成对运用。也有或许在体系分配届时刻内仍然无法完结改使命,那么这个时分就会履行 expirationHandler
,在这里将做失利处理,在样例代码中发送了一条本地告诉,提示用户音讯并未成功发送!
Phone Calls
当有人给你打电话的时分需求向用户呈现来电提示,这个场景运用了 VoIP push notifications
这个API,这是一种特别的推送能够发动App,来让用户接听电话,需求在PK推送注册中注册VoIP类型:
func registerForVoIPPushs() {
self.voipRegistry = PKPushRegistry(queue: nil)
self.voipRegistry.delegate = self
self.voipRegistry.desiredPushTypes = [.voIP]
}
可是在2019年有一个新的改进,那便是在 didReceiveIncomingPush
回调中有必要运用 CallKit
结构来陈述来电,**否则体系将停止杀死App。**假如一向无法处理该告诉,那么体系或许在接收到 VoIP 推送之后再也不会发动App了。那么新的改变如下:
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)
}
}
}
有几点需求留意的细节:
- 推送中带有满足的来电人的信息,能够用来展示UI界面。
- 将
apns-expiration
推送设置为0或许很小的值,这样来电之后的告诉也会是相关通话的告诉,而不是几分钟甚至更久之后,来电结束了才收到告诉。 - 运用标准推送(standard push),不用全屏推送,无需在呼叫UI中全屏展示告诉。
- 还能够运用 Notification Service Extension 来修正内容。
Muted Threads (静音群组)
像微信一样,有多个联系人,以及群聊的时分,有时用户不想让某些群聊的音讯有提示,可是进入App中之后又想要当即查看信息,仅仅不想每次都轰动设备并收到告诉。为了到达这一点,我们需求 Background Pushes 后台推送机制。这个机制能够告诉设备有新数据可用而无需提示用户。
这就需求设置推送 content-available: 1
, 而不是 alert
, sound
, 或许 badge
,从而完成静默推送的意图,体系收到告诉之后会挑选一个适宜的时刻来发动app来下载相关内容,时刻线如下:
一起后台推送功用增加了一些新的机制:
-
apns-priority = 5
,有必要优先级设置为5,否则体系无法后台发动app。 - 比方设置
apns-push-type = background
,这对 watchOS 是有必要的,可是Apple主张一切渠道关于后台静默推送都采用这种办法。
Download Past Attachments(下载之前的附件)
假如用户在一台新的设备上登录了它的账户,需求当即下载回话列表以及最近的音讯记录,可是对玉一些很老的内容,假如能够在设备充电或许搁置时下载的话,何必在前台下载呢?所以这就需求推延后台履行下载的时刻,完成办法是 Discretionary Background URL Session
。
let config = URLSessionConfiguration.background(withIdentifier: "com.app.attachments")
let session = URLSession(configuration: config, delegate: ..., delegateQueue: ...)
// 设置体系自主性:根据功能来决议开始时刻
config.discretionary = true
// 设置时刻距离
config.timeoutIntervalForResource = 24 * 60 * 60
config.timeoutIntervalForRequest = 60
// 创立恳求
var request = URLRequest(url: url)
request.addValue("...", forHTTPHeaderField: "...")
let task = session.downloadTask(with: request)
// 设置恳求组织的最早时刻,这里是两小时后
task.earliestBeginDate = Date(timeIntervalSinceNow: 2 * 60 * 60)
// 设置估计的作业量
task.countOfBytesClientExpectsToSend = 160
task.countOfBytesClientExpectsToReceive = 4096
task.resume()
延时的后台使命
以上都是一些当即履行的后台使命,接下来要介绍的不是马上就履行的后台使命,而是延迟履行的后台使命,会在设备搁置或许充电的时分统一来进行使命的处理:
Background Processing Task的特色
留意,这里是的数据来自Apple的WWDC视频,所以应该要相信它的准确性。
-
体系会在适宜的时分分配几分钟的运转时刻
- 履行可推延的可维护性作业:同步数据、备份、本地数据库清理等等
- Core ML的训练等等
-
关于核算密集型的操作,能够关掉 CPU 的监控使后台使命充分利用硬件功能
其实这便是为了后台进行模型训练来特意整出来的!!!
-
在前台恳求过,那么在后台就能够履行
在运用 BGProcessingTaskRequest
时有几个属性需求留意:
-
requiresNetworkConnectivity
假如在履行后台使命的时分需求运用网络,而不仅仅是本地的操作,那属性就要设置为 true 。
-
requiresExternalPower
后台使命履行核算密集型的操作的时分,想要撤销 CPU 的监控,能够设置改属性为 true 来完成这一点。
Background App Refresh Task的特色
新的API,后台改写使命。
-
该使命供给30秒的运转时刻
-
用于获取新内容使App坚持最新的数据状态
-
后台改写使命履行的机遇取决于用户运用App的办法
假如用户在早中晚运用App,那么它能够在运用之前发动该App的后台使命来获取最新数据。
运用频率不高的状况:会在发动之前,调用后台改写使命
还有一点要留意的是运用新的API之后,不要运用旧的API了,旧的API现已被废弃了:
UIApplication.setMinimumBackgroundFetchInterval(_:)
UIApplicationDelegate.application(_:performFetchWithCompletionHandler:)
运用BackgroundTasks的原理
App以及它的Extension都能够创立 BGTask
,并将其提交给 BGTaskScheduler
,它是一个大局的管理后台使命的进程,它会在适宜的时分挑选履行相应的Task,唤醒App并在后台发动它履行对应的使命,完结使命之后,需求调用 setTaskCompleted
办法,将使命标记为完结并挂起App。
一起Extension提交的使命只会唤醒主App,也便是说 BackgroundTask
永久由主App来履行。 体系也或许挑选后台发动App来一起履行多个使命,可是体系只会按照每次发动来分配一定的时刻来一起履行使命,并不会按照使命来独自分配时刻。
运用Background Task的流程
这个在Apple的文档中讲述的非常得清楚:****Using background tasks to update your app。**主要是有几个重点的步骤:
- 在项意图
capabilities
中开启想要的后台使命:BGAppRefreshTask
以及BGProcessingTask
- 在Target的Info中添加
[BGTaskSchedulerPermittedIdentifiers](https://developer.apple.com/documentation/bundleresources/information_property_list/bgtaskschedulerpermittedidentifiers)
中相应的identifier字符串来标识task,后续需求在代码中注册
- 运用设置好的Identifier注册
BGTaskScheduler
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
let feedVC = (window?.rootViewController as? UINavigationController)?.viewControllers.first as? FeedTableViewController
feedVC?.server = server
PersistentContainer.shared.loadInitialData()
// MARK: Registering Launch Handlers for Tasks
BGTaskScheduler.shared.register(forTaskWithIdentifier: "com.example.apple-samplecode.ColorFeed.refresh", using: nil) { task in
// Downcast the parameter to an app refresh task as this identifier is used for a refresh request.
self.handleAppRefresh(task: task as! BGAppRefreshTask)
}
BGTaskScheduler.shared.register(forTaskWithIdentifier: "com.example.apple-samplecode.ColorFeed.db_cleaning", using: nil) { task in
// Downcast the parameter to a processing task as this identifier is used for a processing request.
self.handleDatabaseCleaning(task: task as! BGProcessingTask)
}
return true
}
- 在适宜的机遇提交相应的Request
func applicationDidEnterBackground(_ application: UIApplication) {
scheduleAppRefresh()
}
// MARK: - Scheduling Tasks
func scheduleAppRefresh() {
let request = BGAppRefreshTaskRequest(identifier: "com.example.apple-samplecode.ColorFeed.refresh")
request.earliestBeginDate = Date(timeIntervalSinceNow: 15 * 60) // Fetch no earlier than 15 minutes from now
do {
try BGTaskScheduler.shared.submit(request)
} catch {
print("Could not schedule app refresh: \(error)")
}
}
另外还有一些额定需求留意的点:
-
不要设置
earliestBeginDate
太远,最好在一周之内 -
保证在设备被锁住的时分,仍然能够访问文件
FileProectionType.completeUntilFirstUserAuthentication
-
UIScene apps较为特别,需求用到
UIApplication.requestSceneSessionRefresh(_:)
API -
BGTaskScheduler.submit
为了运用的简练是设置为一个阻塞的同步调用,所以假如要在发动的时分提交,那么应该在 background queue 中运用, 而非 main queue。
总结:后台使命怎么挑选?
既然上面现已描述了这么多的后台使命,那么究竟该怎么挑选呢?以及是几种常见的场景下的挑选,具体Case来自Apple的官方文档:Choosing Background Strategies for Your App。
1、在后台继续前台使命
运用[beginBackgroundTask(withName:expirationHandler:)]
恳求时刻继续履行前台的使命。
2、延迟履行核算密集型作业
运用 [BGProcessingTask]
,由体系来决议最佳的使命履行时刻点。
3、更新App中的内容
假如App是周期性的从服务器拉取数据,那就能够运用 [BGAppRefreshTask]
,由体系挑选最佳的使命履行时刻点,并且这种办法能够供给最多30秒的后台履行时刻。
4、运用后台推送唤醒App
运用后台推送在后台静默唤醒App,不涉及 alert、sound 以及 badge。这个上述现已介绍过了,就不赘述了。
5、运用后台推送告诉用户
假如app需求在后台履行使命,并且还要向用户展示告诉,那么能够运用 Notification Serverce Extension
。在收到推送告诉之后,这个 service extension
会被唤醒,并且通过 [didReceive(_:withContentHandler:)]
来恳求后台履行时刻。
当 extension 完结使命之后,它有必要调用 content handler
闭包来处理给用户的内容。extension 的履行时刻也是有限的。
问题:App想长时刻在后台运转怎么办?
在Apple官方文档中Preparing your UI to run in the background中总结了App在进入后台之后还能够履行使命的几种状况:
- Audio communication using AirPlay, or Picture in Picture video.
- Location-sensitive services for users.
- Voice over IP.
- Communication with an external accessory.
- Communication with Bluetooth LE accessories, or conversion of the device into a Bluetooth LE accessory.
- Regular updates from a server.
- Support for Apple Push Notification service (APNs).
而假如想一向在后台运转,那就需求继续的在后台履行使命,占据体系资源,一般来说有以下三种状况:
- 播映音频或许视频
- 后台继续定位
- 连接Bluetooth LE accessories
要留意的是,这三种状况都需求在开发的时分设置 Background Modes。
引证
[1] Apple 文档 Preparing your UI to run in the background
[2] Apple 文档 Choosing Background Strategies for Your App
[3] Apple 文档 Using background tasks to update your app
[4] WWDC 2019: Advances in App Background Execution