iOS 后台刷新介绍

对于依赖于实时信息、位置服务或与外部设备通信的 iOS App ,开发者可以用后台刷新来提高用户体验,允许 App 在后台执行任务。特别是在下载或上传大量数据时,后台执行网络请求会相当有帮助。
iOS 限制 App 在后台运行,也很有道理。如果 App 没处于活动状态,就不应该使用大量系统资源,尤其是在涉及数据传输时。但随着 App 越来越多地与后端服务连接,后台获取数据对于良好的用户体验已经变得更加重要。
不幸的是,并没有一种实现网络后台请求的最佳方式。最新的 iOS SDK 提供了很多选项,熟悉不同的后台抓取 API 有助于决定使用哪个技术。
由于不受控制的后台任务可能导致设备的电池寿命大量消耗,并且很难复现,正确使用 iOS 后台刷新 API 很关键。本文介绍了相关问题,并且介绍了一些常见的坑。

理解 iOS App 执行状态

大多数 iOS 用户都熟悉 iOS 9 中的多任务界面,双击 home 键的时候会显示最近使用的 App 列表。向上滑动会强制关闭它。但是,多任务界面里显示的 app 并不一定在执行代码或获取数据。它们可能被暂停或根本没有在运行(这长期困扰了想节省电量的 iOS 用户)。


iOS 9 多任务界面
iOS 9 多任务界面

使用 Swift, App 的执行状态可以这么获得:

UIApplication.sharedApplication().applicationState

如果状态是 active,应用在屏幕上是可见的,准备好接收事件。不可见的话可能是 background 或 inactive。苹果开发者网站上有一张很棒的全状态示意图
大多数开发者使用 UIApplication 里的代理方法或借助大量通知类型来响应状态的改变。Xcode 7 的 iOS 模板包含了这些用来响应改变的代理方法:

// App 准备好运行了
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool
// App 即将从活跃到非活跃状态
func applicationWillResignActive(application: UIApplication)
// 后台模式刚被激活
func applicationDidEnterBackground(application: UIApplication)
// App 现在可见了,并可以接收事件
func applicationDidBecomeActive(application: UIApplication)
// App 即将终止
func applicationWillTerminate(application: UIApplication)

默认情况下,当应用进入后台时,没什么值得兴奋的——它只是在 app 被暂停之间的短暂过渡而已。甚至可以禁用后台状态(但苹果不鼓励这么做 )。尽管后台刷新有几种不同的使用情境,包括和蓝牙设备的通信、播放音频等,但许多应用使用后台刷新来下载东西。

使用 NSURLSession 在后台下载和上传

当 App 需要上传或下载数据时,如果用户发送短信和切换到其它应用,操作最好继续。幸运的是,当应用程序变得不活动时,NSURLSession 类可以移交下载和上传到操作系统。与几乎所有后台执行 API 一样,如果用户从多任务界面强行退出,后台操作会终止。(注意如果 App 在追踪位置,用户强退了,它会重新启动。)
要使 NSURLSession 具有后台能力,需要实例化有后台初始化方法和标识符(重用于所有后台会话)的 NSURLSessionConfiguration 对象:

let sessionConfig = NSURLSessionConfiguration.backgroundSessionConfigurationWithIdentifier("com.newrelic.bgt")

如果一个 App 特别礼貌,在 NSURLSessionConfiguation 中有一个标志称为“discretionary”,允许 iOS 优化性能的请求,因此在某些情况下(如电池电量不足时不好的连接),请求不会真正发生。

backgroundSessionConfig.discretionary = true

只要应用程序发出 HTTP 或 HTTPS 请求,那么 NSURLSession 需要使用配置对象和委托来实例化,以便在下载或上传完成时接收通知。 这里 有一些其它限制:

let session = NSURLSession(configuration: backgroundSessionConfig, delegate: self, delegateQueue: NSOperationQueue.mainQueue())

例如为了下载静态 PDF 文件,具有后台配置的会话可以在标准下载任务中使用:

let downloadTask = session.downloadTaskWithURL(NSURL(string: "https://try.newrelic.com/rs/newrelic/images/nr_getting_started_guide.pdf")!)
downloadTask.resume()

当操作完成或者有错误时,NSURLSession 委托方法会被调用。会有一个磁盘上的临时文件的路径,可以打开以读取或移动到另一个位置。
关于 NSURLSession 的最后一点:它从 iOS 9 开始支持 HTTP/2。关于使用API ​​的更多细节可以在 苹果的开发者网站 上获得。

选择机会下载东西

在 iOS 7 里,苹果添加了对后台抓取的支持——智能、每个 App 都有机会被唤醒。没有办法强制后台抓取在指定的时间执行。iOS 在调度未来的执行时会检查早之前的后台抓取中使用的数据和电池用量。
添加支持要编辑应用程序的 property list(参阅 UIBackgroundModes)并在App 生命周期的早期设置获取间隔:

application.setMinimumBackgroundFetchInterval(UIApplicationBackgroundFetchIntervalMinimum)

当 iOS 决定开始后台抓取时,会调用此 UIApplicationDelegate 方法:

func application(_ application: UIApplication, performFetchWithCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void)

这个方法有大约 30 秒时间将一个 UIBackgroundFetchResult 返回给 completionHandler 函数,然后 App 就会终止。UIBackgroundFetchResult 的值用于确定何时再次调用后台抓取委托方法。如果在特定时间频繁需要数据(例如,清晨的新闻 App),这有助于 iOS 了解何时执行后台抓取:

enum UIBackgroundFetchResult : UInt { case NewData case NoData case Failed}

后台抓取也可以由远程推送通知触发,并且具有非常类似的委托方法,带有相同的 completion handler。
要在 iOS 模拟器中测试后台抓取事件,Xcode 在 Scheme 编辑器中有一个“Launch due to background fetch event”选项,并在 debug menu 下有“Simulate Background Fetch”项。

在 Xcode 中启用 “Launch due to background fetch event”
在 Xcode 中启用 “Launch due to background fetch event”

在 2016 年初,开发者发现是用 iOS 模拟器测试后台抓取会有问题,所以最好是 clean 之后把 App 安装在真机上。
此外,Xcode 调试器改变了操作系统挂起应用程序的方式,并且还可能在 测试非活动状态时出现问题 。在没有连接调试器的设备上进行测试(正如用户和 App 交互一样)有时是唯一可靠地再现某些状态的方法。

App 终止的特殊情况

用户在多任务界面强退可能是出现不可复现的崩溃的根源。如果 App 被杀死且没有任何通知的话就可能发生。例如,App 被挂起了,但系统由于内存不足而终止了它,就不会发送任何通知。只有 iOS 想要终止未暂停并处于后台状态的 App 时,才会调用 applicationWillTerminate

iOS 9 中 App 可能被终止的不同方式
iOS 9 中 App 可能被终止的不同方式

在 iOS 9 中,App 不应该依赖于 applicationWillTerminate: 的调用。最好在 applicationDidEnterBackground: 中保存状态并执行清理。
然而,重要的是 applicationWillTerminate 被调用的时候清理和终止所有正在运行的后台任务,因为如果 iOS 必须强制杀死正在运行的后台任务,可能会导致崩溃。这有时是难以复现的 bug 的来源。
出于性能和电池寿命的考虑,iOS 限制了后台的时间量。在后台执行状态中剩余的时间量可从以下获取:

UIApplication.sharedApplication().backgroundTimeRemaining

backgroundTimeRemaining 的数量并不总是正确。强制退出将停止任何后台任务,无论剩余多少时间。
关于执行状态的代理方法总是被调用(甚至是按照特定顺序)的假设实际上也并不一定。仔细检查建设一个执行状态总是发生在另一个状态之前的代码。

总结

当编写在后台执行的 iOS 代码时:

  • 确定要使用哪个后台刷新 API。对于需要很多秒才能完成的网络请求,NSURLSession 会很有帮助。使用 iOS 提供的机会性后台抓取代理对于需要按计划获取内容的 app 会很有帮助。
  • 远程推送通知可以是触发后台刷新的有效机制。
    Log 执行状态的变更,在有和没有连接调试器的真机上测试,小心模拟器带来的奇怪问题。是用开源的 iOS logging 库,例如 CocoaLumberjackXCGLogger 会很有帮助。
  • 访问钥匙串或使用 iOS 数据保护功能时要小心。后台刷新可能发生在锁屏时,可能导致读写受保护的资源出现问题。
  • 高性能后台代码很关键:iOS 会优先处理前台的 App,严格限制 App 完成后台任务的资源和时间。
    随着移动数据使用量的增加和新的 iOS 9 功能(如 iPad 上的多任务处理拆分视图),管理应用执行状态对于构建高质量应用程序非常重要——App 打开时持续不断的进度指示条肯定会让用户很烦。后台刷新是苹果对开发人员的妥协,旨在平衡用户体验与使用数据网络和高网络延迟时导致的电池消耗。利用后台抓取 API 保持信息最新,并注意避免常见的坑,这有助于满足用户对 App 始终快速且永不崩溃的期望。

附录

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容

  • 一个习惯站在潮头的,怎么习惯被人忽视,你可以说这是虚荣,但阿尔帕西诺曾经说过一句台词:“虚荣,我最爱的原罪。”
    HeyCoco阅读 104评论 0 0
  • 或许有这样的情况,我们最初的时候,在币圈投资,想要获得巨大的回报,但却事与愿违,被割了韭菜;在网上开了淘宝店,希望...
    湛然_8d72阅读 285评论 0 0
  • 我已经很久很久没有写过东西了,上了大学之后,除了发说说和朋友圈平时基本没有完整的去组织过一段话,直到现在,我发现,...
    雨后无邪阅读 1,058评论 0 1