内容简介:原文:作者:Brody Eller译者:kmyhy
原文: Background Modes Tutorial: Getting Started
作者:Brody Eller
译者:kmyhy
在本教程中,你将创建一个 app,这个 app 使用到这 4 种常见的背景模式:音频播放、位置更新、普通任务、后台抓取。
更新说明:Brody Eller 将本教程升级至 Xcode 10 和 Swift 4.2。原文作者是 Chris Wagner。
苹果在 2010 年 iOS 4 中引入了多任务。
开发者和用户经常搞不懂 iOS 多任务系统到底是干嘛的。苹果为了提升用户体验和延长电池寿命对后台任务进行了限制。你的 app 只能在几种有限的情况下使用后台任务。例如,播放音频、监听位置变化或者从服务器抓取最新数据。
如果你的任务不属于这几种类型,就不能使用后台任务。如果你试图通过欺骗系统的方式滥用后台模式,App Store 会拒绝你的 app,因此请谨慎行事。
你可以用顶部或底部的 Download Materials 按钮下载本项目。UI 和部分与本主题无关的代码是已经写好的。
在进入项目之前,先来看一下 iOS 中有效的后台模式有哪些。在 Xcode 中,你可以在 app target 的 Capabilities 中看到这个列表:
要看到这个列表,需要经过以下几个步骤:
- 从项目导航窗口选中本项目。
- 点击 app 的 target。
- 点击 Capabilities 标签页。
- 打开 Background Modes 开关。
在本教程中将介绍其中 4 种后台模式:
- Play audio: 允许 app 在后台持续播放/录制音频。
- Receive location updates: 允许 app 持续获得位置更新通知。
- Perform finite-length tasks: 一般的“其它”情况,允许 app 运行有限时长的任意代码。
- Background Fetch: 允许在 iOS 的调度下获取最新数据。
如果你只对其中某种或某几种模式感兴趣,你可以跳着阅读!
在运行项目之前,你必须修改你的开发团队:
运行示例项目看看。app 中有 4 个 tab,每个对应了一种后台模式:
注:为了获得最佳效果,你应当使用真机。如果你忘记了某些配置,在模拟器上 app 仍然能够运行正常,但切换到真机上就不一定行了。
播放音频
首先是后台音频。
在 iOS 中有几种播放音频的方法,它们大部分都必须实现一些回调方法,以便提供播放所需的音频数据。
如果你想从流数据中播放音频,你可以打开网络连接,实现回调方法以提供源源不断的音频数据。
当你激活后台音频模式时,当 app 进入后台时,iOS 仍然会继续运行这些回调方法。没错——后台音频模式几乎是自动的。你只需要开启它,然后提供一些必要的支持即可。
打开 AudioViewController.swift。
这个 app 使用 AVQueuePlayer 来添加歌曲,然后一首接一首地播放它们。 view controller 会监听播放器的 currentItem 值的改变。
项目中使用的音频来自于 incompetech.com ,一个我很喜欢的免费音乐网站。通过积分,你就可以免费使用它的音乐。它们是:
-
“Feelin Good” Kevin MacLeod ( incompetech.com )
非商业性署名协议: 署名 3.0 http://creativecommons.org/licenses/by/3.0/
-
“Iron Bacon” Kevin MacLeod ( incompetech.com )
非商业性署名协议: 署名 3.0 http://creativecommons.org/licenses/by/3.0/
-
“What You Want” Kevin MacLeod ( incompetech.com )
非商业性署名协议: 署名 3.0 http://creativecommons.org/licenses/by/3.0/
谢谢 Kevin!
当 app 在 active 状态(前台)时,它会显示歌名,当它进入后台,它会将歌名打印到控制台。在后台时,label 上的歌名仍然会改变,当这里仅仅是为了演示 app 在后台中仍然能够收到通知而已。
注意:关于 app 的 active 状态和其它状态,请阅读 Execution States for Apps 。
Build & run,你会看到:
点击 Play,音乐开始播放。
测试后台播放
当你在真机上运行时,点击 Home 按钮,歌曲会停止播放。why? 呃,有一些非常关键的工作还没有做。
对于大部分后台模式(“其它”模式例外),你都需要在 Capabilities 中开启对应的后台模式。
回到 Xcode,执行下列操作:
- 在项目导航窗口中点击该项目。
- 选择 TheBackgrounder 这个 target。
- 点击 Capabilities 标签页。
- 找到 Background Modes,将 switch 打开。
- 勾上 Audio, AirPlay and Picture in Picture。
再次 build & run,开始放歌,然后按下 Home 键。这回歌曲仍然会继续播放,虽然 app 已经进入到后台。
从 Xcode 控制台中,你还会看到时间在变化,说明你的代码仍然在后台运行。
哇,如果你已经写好了一个音乐播放器,那么让它能后台播放也太简单了!好了,搞定一种模式了。如果你想看完整个教程,那么还有 3 种模式等着你呢!
接收位置更新
当你开启 Location background 模式后,你的 app 会在后台接收到用户的位置更新信息。你可以控制位置更新的精度,甚至在 app 处于后台时修改位置更新的精度。
第二个 tab 是位置更新,因此请打开 LocationViewController.swift。
在这个控制器中,你会找到 CLLocationManager。要接收位置更新,你需要创建和配置一个 CLLocationMananger 对象。这里,app 会在用户点击屏幕的 UISwitch 控件之后开始监听位置。当 app 收到位置更新通知,app 会在地图上绘制出大头钉。当 app 进入后台,你会看到控制台中打印出坐标。
开启位置更新
需要注意的是 CLLocationManager对象的 requestAlwaysAuthorization() 方法。自从 iOS 8 开始,这就是必须的了,它会弹出一个对话框,询问用户是否允许在后台接收位置更新。
现在,你应该熟悉了后台模式,不会再犯之前的错误了吧!勾上 Location updates,让 iOS 知道,你的 app 需要在后台持续接收位置更新!
除此之外,iOS 8 还需要你在 Info.plist 文件中增加一个 key,让用户知道你需要使用位置更新的理由。如果你没有添加这个 key,位置更新会默默地失败。这个项目已经添加过这个 key,你可以在这里看见它:
- 在 Xcode 中选择项目。
- 点击 Info 标签页下的 TheBackgrounder 这个 target。
- 找到 Privacy — Location Always and When In Use Usage Description 和 Privacy — Location When In Use Usage Description。
- 在这里写上你需要使用位置信息的充足理由。
现在,build & run! 点开第二个 tab,将 switch 开关拖到 on。
当你第一次操作时,你会看到一条信息,显示了你刚才在 key 中描述的原因。点击 Always Allow,随意走动一下或者走出房子(不要走得太远并尝试去抓一只精灵)。你会看到开始位置更新了,哪怕是模拟器。
一段时间后,你会看到:
在后台测试位置更新
如果进入后台,你会看到控制台中打印出位置信息。再次打开 app,你会看到地图上在后台期间改变的大头钉也会刷新。
哪怕你使用的是模拟器,你也可以模拟移动过程!打开 Debug ▸ Location 菜单:
简单吧?接下来是第三个 tab 和第三种后台模式!
执行有限时间的任务……或者 Whatever
接下来是执行有限长度的后台任务。好绕口啊,还是 Whatever 更好记点~
从技术上将,这根本不能算是后台模式,因为你根本不需要在 Capabilities 中声明这种模式。它只是一种 API,允许你在 app 进入后台前执行任意代码一小段时间。好吧……也就是所谓的 whatever(任意)!
什么时候使用有限时长的任务
使用这种模式的一个非常有用的场景就是完成某种长耗时任务,比如显示或保存一个视频到相册中。
这只是其中一个例子。因为可以是任意代码,你可以用这个 API 做很多事情:进行长时间运算(本教程中的例子),对多张图片应用滤镜,渲染复杂 3D 网格……什么都可以!想象力是你唯一的限制,只不过你必须牢记你只有有限的时间,而非无限时间。
由 iOS 决定你的 app 进入后台后还会有多长时间。这个时间无法保证,但你可以通过 UIApplication.shared 的 backgroundTimeRemaining 来检查这个时间。它会告诉你还剩下多少时间。
通常,通过观察这个时长一般是 3 分钟。但是这不是绝对的,API 文档中甚至没有给出精确的数字——因此不要相信这个数字。你可能会得到 5 分钟或 5 秒,因此你的 app 要对此有所认识……whatever 嘛!
这里有一个很普通的任务,每个计算机学科的学生都应该熟悉了:计算斐波那契序列。唯一特殊点的地方就是,你需要在后台计算斐波那契额序列。
创建一个有限时长的任务
打开 WhateverViewController.swift 看一下里面有些什么。当前,这个视图会计算斐波那契序列并显示出结果。如果你将 app 切到后台,计算会终止,当 app 回到前台时又会从中断的地方继续计算。你是任务是创建一个后台任务,让计算不会中断知道 iOS 说 “时间到!”。
首先需要在 WhateverViewController 中添加一个属性:
var backgroundTask: UIBackgroundTaskIdentifier = .invalid
这个属性用于唯一标识要申请后台执行的任务的请求。
然后添加 2 个方法:
func registerBackgroundTask() { backgroundTask = UIApplication.shared.beginBackgroundTask { [weak self] in self?.endBackgroundTask() } assert(backgroundTask != .invalid) } func endBackgroundTask() { print("Background task ended.") UIApplication.shared.endBackgroundTask(backgroundTask) backgroundTask = .invalid }
registerBackgroundTask() 方法告诉 iOS 当 app 进入后台时你还需要更多计算时间。这个方法调用后,如果 app 进入后台后,仍然能够获得 CPU 时间,直到你调用 endBackgroundTask()。
差不多就这样了。如果你在后台一段时间后不调用 endBackgroundTask(),iOS 会调用你在 beginBackgroundTask(expirationHandler:) 中指定的那个闭包,以便你能够终止代码的执行。因此,最好是通过 endBackgroundTask() 来告诉 iOS 你已经做完了。如果你不这样做并继续执行这个代码块之后的代码,iOS 会终止你的 app!
注册和结束后台任务
现在,重要的地方来了。你必须修改 didTapPlayPause(_:smiley: 去注册和停止后台任务。在这个方法中有两处注释,就是你需要添加代码的地方。
在注释“register background task” 之后调用 registerBackgroundTask 方法:
registerBackgroundTask()
现在,当计算开始,我们调用了 registerBackgroundTask(),这样你就可以在进入后台后继续计算斐波那契序列了。
然后在“end background task” 这一句注释之后添加:
if backgroundTask != .invalid { endBackgroundTask() }
现在,当用户点击停止计算,你会调用 endBackgroundTask() 方法告诉 iOS 你不再需要多余的 CPU 时间了。
这一点很重要,当你每次调用 beginBAckgroundTask() 的时候都要调用 endBackgroundTask()。如果你在这个任务中调用了两次 beginBackgroundTask(expirationHandler:) 而只调用了一次 endBackgroundTask() ,那么你会继续获得 CPU 时间直到你在第二个后台任务 ID 上第二次调用endBackgroundTask()。这也是为什么要使 backgroundTask 的原因。
更新 UI
现在修改 calculateNextNumber() 方法,根据 app 的状态进行不同的动作。
将最后一句 resultsLabel.text = resultsMessage 修改为:
switch UIApplication.shared.applicationState { case .active: resultsLabel.text = resultsMessage case .background: print("App is backgrounded. Next number = \(resultsMessage)") print("Background time remaining = " + "\(UIApplication.shared.backgroundTimeRemaining) seconds") case .inactive: break }
你只会在 app active 的时候更新 labe。当 app 进入后台,用控制台打印来替代,以显示新的数字和后台时间还剩多少。
Build & run,切换到第三个 tab。
点击 Play,你会看到 app 正在计算的数值。现在,点击 Home 键,观察控制台输出。你会看到 app 继续刷新数字和剩余的后台时间。
绝大多数情况下,这个时间从 180(180秒=3分钟)开始,一直到 5 秒。如果你等时间结束,到达 5 秒(也可能是另外一个值,取决于你的具体情况),iOS 会调用 expiration 块,app 停止输出。然后,如果你回到 app,定时器再次触发,计算继续进行。
通过后台任务处理定时器过期
代码中有一个 bug。如果你切到后台,等后台时间用完。这时,你的 app 会调用 expireation 块和 endBackgroundTask(),结束后台时间。
如果你返回 app,定时器会继续触发。但如果你再次让 app 进入后台,你却根本无法获得后台时间。why?因为在时间用完和 app 返回后台之间,app 不会调用 beginBackgroundTask(expirationHandler:)。
如何解决这个问题?有很多方法,其中一种就是使用状态改变通知。
你可以从官方文档 Execution States for Apps 中找到具体描述。
具体来解决这个 bug。首先,添加一个方法 reinstateBackgroundTask():
@objc func reinstateBackgroundTask() { if updateTimer != nil && backgroundTask == .invalid { registerBackgroundTask() } }
你只需要在定时器正在运行且后台任务 invalid 的情况下重新初始化后台任务即可。应该将代码分成小的只做一件事情的 工具 函数。在这个函数中,你只需要调用 registerBackgroundTask() 即可。
现在,重写 viewDidLoad() 并订阅 UIApplicationDidBecomeActiveNotification 通知:
override func viewDidLoad() { super.viewDidLoad() NotificationCenter.default .addObserver(self, selector: #selector(reinstateBackgroundTask), name: UIApplication.didBecomeActiveNotification, object: nil) }
这会告诉 iOS 在 app 变成 active 状态时调用你的新方法reinstateBackgroundTask()。
无论何时,当你订阅通知时,都应该考虑取消订阅。你可以在 deinit 方法中做这个:
deinit { NotificationCenter.default.removeObserver(self) }
注:尽管 iOS 不再需要你手动移除观察者,但这种做法仍然值得提倡。
就是这样了;你现在可以做任何你想做的事,至少只要 iOS 说 OK 就行。
然后是本文最后一种后台模式:后台下载。
后台下载
后台下载是 iOS 7 引入的模式,允许你的 app 在尽量不影响电池寿命的情况下展现实时刷新的内容。例如,假设你的 app 实现了一个新闻列表。在后台下载出现之前,你必须在 viewWillAppar(_:smiley: 方法中获取新数据。
这种方式的缺点是,你的用户会在新数据下完之前至少有几秒钟看到的是老的数据。为什么不能在他们打开 app 的同时就看到新的数据呢?这就是后台下载。
当使用后台下载时,系统会使用某种方式判断什么时候才是后台下载的最佳时机。例如,如果你的用户在每天早上 9 点打开 app,它很可能在那个时间之前进行后台下载。系统自动决定后台下载的最佳时间,因此,你不应当用后台下载来进行重要的数据刷新。
创建后台下载
为了实现后台下载,你必须做 3 件事情:
- 在 app 的 Capabilities 中勾选 Background fetch。
- 通过 setMinimumBackgroundFetchInterval(_:smiley: 方法设置一个和 app 相称的时间周期。
- 实现 application(_:performFetchWithCompletionHandler:) 委托方法,进行后台下载。
如名称所示的,后台下载通常会从外部比如网络加载数据。对于本教程来说,你将获取系统当前时间,而不通过网络。
和有限任务相比,后台下载只有几秒的时间可用——这个数值公认的是 30 秒,但越少越好。如果你需要下载大量数据,你可能需要使用 URLSession 的后台传输服务。
实现细节
让我们开始吧。首先,打开 FetchViewController.swift 看看它做了些什么。
fetch(_:smiley: 方法用于简单地演示你从外部资源下载数据的情况(比如一个 JSON 或 XML RESTful 服务)。因为下载和解析过程需要点时间才能完成,所以需要传递一个完成块,当任务完成时执行。你会在后面看到这一点很重要。
updateUI() 用于格式化时间并显示。guard 语句包裹了 updateLabel,是为了确保 view 已经完成加载。time 是一个可选类型,如果它没有值,它会显示 “Not updated yet.”。
当视图第一次加载时,你不会下载,而是直接 updateUI(),也就是说第一次显示的是 “Not yet updated” 字样。最后,当用户点击 Update,它会进行一次下载,在完成块中刷新 UI。
这个 view controller 是可以工作的,你不需要修改它。
当然,后台下载还没有开启。
开启后台下载 - 步骤 1
首先是开启后台下载,即在 app 的 Capabilities 中勾上 Background fetch。这对于你已经是很熟悉的操作了。请自己完成这个操作。
开启后台下载 - 步骤 2
然后,打开 AppDelegate.swift 在application(_:didFinishLaunchingWithOptions:) 中添加:
UIApplication.shared.setMinimumBackgroundFetchInterval( UIApplication.backgroundFetchIntervalMinimum)
通过设置 minimum background fetch interval,请求后台下载。默认的 interval 是 UIApplicationBackgroundFetchIntervalNever,这个值你可能还要切换回来,因为你的用户退出登录或者不再需要更新时。你也可以设置为具体的秒数。系统在进行一次后台下载之前会至少等待多少秒。
注意不要设置的太小,因为有可能增加不必要的电量消耗,或者对你的服务器造成伤害。最后,确切时间将由系统决定,但在执行之前至少要等待这个时间间隔。一般,UIApplicationBackgroundFetchIntervalMinimum 是一个很好的选择。
开启后台下载 - 步骤 3
最后,必须实现 application(_:performFetchWithCompletionHandler:)。在 AppDelegate 中加入:
// 后台下载 func application( _ application: UIApplication, performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { //1 if let tabBarController = window?.rootViewController as? UITabBarController, let viewControllers = tabBarController.viewControllers { //2 for viewController in viewControllers { if let fetchViewController = viewController as? FetchViewController { //3 fetchViewController.fetch { //4 fetchViewController.updateUI() completionHandler(.newData) } } } } }
上面的代码解释如下:
- 对 rootViewController 解包为 UITabBarController,因为对于不同的 app 来说 rootViewController 不一定就是 UITabBarController。当然,在这个 app 中来说,rootViewController 肯定就是一个 UITabBarController,也就是说这句代码总是返回 true。
- 遍历 tab bar controller 的有 view controller,找出其中的 FetchViewController。在这个 APP 中,你知道它就是最后一个 view controller,所以你也可以硬编码它,但为了保险起见你最好是遍历一下,以免后面你又会移除或添加一个 tab。
- 调用 Call fetch(_:smiley:。
- 当 fetch(_:smiley: 完成,更新 UI 并调用以参数形式传入的 completionHandler 块。在操作完成后调用 completionHandler 这点很重要。第一个参数中需要指定 fetch 期间所发生的事情,可选值是 .newData、.noData 和 .failed。
为了简单起见,这里总是用 .newData 代替,因为获取系统事件不会失败,并且总是不同的值。iOS 会用这个值来更好地调度后台下载。系统知道,现在可以从 app 截取一张快照作为 app 切换工具的 card view 展示。这就是全部工作了。
注:比起传入一个 completion 闭包而言,你可能想将它保存为一个属性变量,当你下载完成时调用。不要这样做。如果你多次调用 application(_:performFetchWithCompletionHandler:),前面的 handler 会被覆盖,从而永远不会被调。将 handler 整个传入参数并调用会更好一些。
测试后台下载
测试后台下载的一种方式是做下来等系统决定做什么。可能需要等很长时间。幸好,Xcode 允许你模拟后台下载。你需要测试两种场景。一种是当 app 进入后台,另一种是当你的 app 从后台返回前台。第一种方式最简单,只需要一个菜单操作。
- 在真机上打开 app(不要用模拟器)。
- 点开 Fetch tab。
- 注意,你会看到 Not yet updated 字样。
- 在 Xcode 的 debug 菜单,选择 Simulate Background Fetch。
- Xcode 会将 app 切到后台。如果调试器中断了 app 的执行,请点击 continue。
- 重新切回 app。
- 注意,时间更新了有没有?
测试当 app 从挂起到恢复
有一个 launch 选项,允许你直接将你的 app 进入到挂起状态。由于你可能经常性地对这种情况进行测试,所以最好是将该选项始设置为一个新的 Scheme。在 Xcode 中这很简单。
首先,打开 Manage Schemes 菜单。
选中列表中唯一的一个 scheme,点击小齿轮图标,选择 Duplicate。
最后,将 scheme 重命名为其它名称,比如 “Background Fetch,”,然后勾选 Launch due to a background fetch event。
用这个 scheme 运行你的 app。你会看到 app 并不会真正打开,而是直接进入到挂起状态。选择,手动点开它,跳到 Fetch tab。你会看到 app 显示了一个刷新后的时间,而不是 Not yet updated 了。
通过后台下载模式,你的用户能够无缝地获得最新内容。
接下来去哪儿?
你可以点击底部 Download Materials 下载完成后的文件。
如果你想阅读苹果的后台模式文档,最好先看看 Background Execution 。这篇文档介绍了所有的后台模式,并为每一种模式进行了链接。
这篇文档中一个很有意思的地方是讨论了什么是一个可靠的后台 app。有许多内容可能和你的 app 有关或者无关,是在你将一个后台 app 提交之前必须要知道的。
最后,如果你准备在后台进行大数据量的网络传输,请一定要看一下 URLSession 。
我们希望你喜欢这篇后台模式教程,如果有任何问题或建议,请到论坛留言。
以上所述就是小编给大家介绍的《[译]后台模式教程:开始》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- iOS后台模式借助位置更新实现
- 如何在后台(脱离模式下)运行Docker容器
- WPF中在MVVM模式下,后台绑定ListCollectionView事件触发问题
- iOS BLE 开发小记[4] 如何实现 CoreBluetooth 后台运行模式
- vue+iview-admin 利用适配器模式改造eova(伊娃管理后台)菜单及路由
- 飞特后台管理系统 3.0:不仅仅是后台,还有商城模块
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Effective C++
[美]Scott Meyers / 侯捷 / 电子工业出版社 / 2006-7 / 58.00元
《Effective C++:改善程序与设计的55个具体做法》(中文版)(第3版)一共组织55个准则,每一条准则描述一个编写出更好的C++的方式。每一个条款的背后都有具体范例支撑。第三版有一半以上的篇幅是崭新内容,包括讨论资源管理和模板(templates)运用的两个新章。为反映出现代设计考虑,对第二版论题做了广泛的修订,包括异常(exceptions)、设计模式(design patterns)......一起来看看 《Effective C++》 这本书的介绍吧!