iOS 后臺刷新介紹

對于依賴于實時信息、位置服務(wù)或與外部設(shè)備通信的 iOS App ,開發(fā)者可以用后臺刷新來提高用戶體驗,允許 App 在后臺執(zhí)行任務(wù)。特別是在下載或上傳大量數(shù)據(jù)時,后臺執(zhí)行網(wǎng)絡(luò)請求會相當有幫助。
iOS 限制 App 在后臺運行,也很有道理。如果 App 沒處于活動狀態(tài),就不應(yīng)該使用大量系統(tǒng)資源,尤其是在涉及數(shù)據(jù)傳輸時。但隨著 App 越來越多地與后端服務(wù)連接,后臺獲取數(shù)據(jù)對于良好的用戶體驗已經(jīng)變得更加重要。
不幸的是,并沒有一種實現(xiàn)網(wǎng)絡(luò)后臺請求的最佳方式。最新的 iOS SDK 提供了很多選項,熟悉不同的后臺抓取 API 有助于決定使用哪個技術(shù)。
由于不受控制的后臺任務(wù)可能導致設(shè)備的電池壽命大量消耗,并且很難復現(xiàn),正確使用 iOS 后臺刷新 API 很關(guān)鍵。本文介紹了相關(guān)問題,并且介紹了一些常見的坑。

理解 iOS App 執(zhí)行狀態(tài)

大多數(shù) iOS 用戶都熟悉 iOS 9 中的多任務(wù)界面,雙擊 home 鍵的時候會顯示最近使用的 App 列表。向上滑動會強制關(guān)閉它。但是,多任務(wù)界面里顯示的 app 并不一定在執(zhí)行代碼或獲取數(shù)據(jù)。它們可能被暫停或根本沒有在運行(這長期困擾了想節(jié)省電量的 iOS 用戶)。


iOS 9 多任務(wù)界面
iOS 9 多任務(wù)界面

使用 Swift, App 的執(zhí)行狀態(tài)可以這么獲得:

UIApplication.sharedApplication().applicationState

如果狀態(tài)是 active,應(yīng)用在屏幕上是可見的,準備好接收事件。不可見的話可能是 background 或 inactive。蘋果開發(fā)者網(wǎng)站上有一張很棒的全狀態(tài)示意圖 。
大多數(shù)開發(fā)者使用 UIApplication 里的代理方法或借助大量通知類型來響應(yīng)狀態(tài)的改變。Xcode 7 的 iOS 模板包含了這些用來響應(yīng)改變的代理方法:

// App 準備好運行了
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool
// App 即將從活躍到非活躍狀態(tài)
func applicationWillResignActive(application: UIApplication)
// 后臺模式剛被激活
func applicationDidEnterBackground(application: UIApplication)
// App 現(xiàn)在可見了,并可以接收事件
func applicationDidBecomeActive(application: UIApplication)
// App 即將終止
func applicationWillTerminate(application: UIApplication)

默認情況下,當應(yīng)用進入后臺時,沒什么值得興奮的——它只是在 app 被暫停之間的短暫過渡而已。甚至可以禁用后臺狀態(tài)(但蘋果不鼓勵這么做 )。盡管后臺刷新有幾種不同的使用情境,包括和藍牙設(shè)備的通信、播放音頻等,但許多應(yīng)用使用后臺刷新來下載東西。

使用 NSURLSession 在后臺下載和上傳

當 App 需要上傳或下載數(shù)據(jù)時,如果用戶發(fā)送短信和切換到其它應(yīng)用,操作最好繼續(xù)。幸運的是,當應(yīng)用程序變得不活動時,NSURLSession 類可以移交下載和上傳到操作系統(tǒng)。與幾乎所有后臺執(zhí)行 API 一樣,如果用戶從多任務(wù)界面強行退出,后臺操作會終止。(注意如果 App 在追蹤位置,用戶強退了,它會重新啟動。)
要使 NSURLSession 具有后臺能力,需要實例化有后臺初始化方法和標識符(重用于所有后臺會話)的 NSURLSessionConfiguration 對象:

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

如果一個 App 特別禮貌,在 NSURLSessionConfiguation 中有一個標志稱為“discretionary”,允許 iOS 優(yōu)化性能的請求,因此在某些情況下(如電池電量不足時不好的連接),請求不會真正發(fā)生。

backgroundSessionConfig.discretionary = true

只要應(yīng)用程序發(fā)出 HTTP 或 HTTPS 請求,那么 NSURLSession 需要使用配置對象和委托來實例化,以便在下載或上傳完成時接收通知。 這里 有一些其它限制:

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

例如為了下載靜態(tài) PDF 文件,具有后臺配置的會話可以在標準下載任務(wù)中使用:

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

當操作完成或者有錯誤時,NSURLSession 委托方法會被調(diào)用。會有一個磁盤上的臨時文件的路徑,可以打開以讀取或移動到另一個位置。
關(guān)于 NSURLSession 的最后一點:它從 iOS 9 開始支持 HTTP/2。關(guān)于使用API ??的更多細節(jié)可以在 蘋果的開發(fā)者網(wǎng)站 上獲得。

選擇機會下載東西

在 iOS 7 里,蘋果添加了對后臺抓取的支持——智能、每個 App 都有機會被喚醒。沒有辦法強制后臺抓取在指定的時間執(zhí)行。iOS 在調(diào)度未來的執(zhí)行時會檢查早之前的后臺抓取中使用的數(shù)據(jù)和電池用量。
添加支持要編輯應(yīng)用程序的 property list(參閱 UIBackgroundModes)并在App 生命周期的早期設(shè)置獲取間隔:

application.setMinimumBackgroundFetchInterval(UIApplicationBackgroundFetchIntervalMinimum)

當 iOS 決定開始后臺抓取時,會調(diào)用此 UIApplicationDelegate 方法:

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

這個方法有大約 30 秒時間將一個 UIBackgroundFetchResult 返回給 completionHandler 函數(shù),然后 App 就會終止。UIBackgroundFetchResult 的值用于確定何時再次調(diào)用后臺抓取委托方法。如果在特定時間頻繁需要數(shù)據(jù)(例如,清晨的新聞 App),這有助于 iOS 了解何時執(zhí)行后臺抓?。?/p>

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

后臺抓取也可以由遠程推送通知觸發(fā),并且具有非常類似的委托方法,帶有相同的 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 年初,開發(fā)者發(fā)現(xiàn)是用 iOS 模擬器測試后臺抓取會有問題,所以最好是 clean 之后把 App 安裝在真機上。
此外,Xcode 調(diào)試器改變了操作系統(tǒng)掛起應(yīng)用程序的方式,并且還可能在 測試非活動狀態(tài)時出現(xiàn)問題 。在沒有連接調(diào)試器的設(shè)備上進行測試(正如用戶和 App 交互一樣)有時是唯一可靠地再現(xiàn)某些狀態(tài)的方法。

App 終止的特殊情況

用戶在多任務(wù)界面強退可能是出現(xiàn)不可復現(xiàn)的崩潰的根源。如果 App 被殺死且沒有任何通知的話就可能發(fā)生。例如,App 被掛起了,但系統(tǒng)由于內(nèi)存不足而終止了它,就不會發(fā)送任何通知。只有 iOS 想要終止未暫停并處于后臺狀態(tài)的 App 時,才會調(diào)用 applicationWillTerminate。

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

在 iOS 9 中,App 不應(yīng)該依賴于 applicationWillTerminate: 的調(diào)用。最好在 applicationDidEnterBackground: 中保存狀態(tài)并執(zhí)行清理。
然而,重要的是 applicationWillTerminate 被調(diào)用的時候清理和終止所有正在運行的后臺任務(wù),因為如果 iOS 必須強制殺死正在運行的后臺任務(wù),可能會導致崩潰。這有時是難以復現(xiàn)的 bug 的來源。
出于性能和電池壽命的考慮,iOS 限制了后臺的時間量。在后臺執(zhí)行狀態(tài)中剩余的時間量可從以下獲?。?/p>

UIApplication.sharedApplication().backgroundTimeRemaining

backgroundTimeRemaining 的數(shù)量并不總是正確。強制退出將停止任何后臺任務(wù),無論剩余多少時間。
關(guān)于執(zhí)行狀態(tài)的代理方法總是被調(diào)用(甚至是按照特定順序)的假設(shè)實際上也并不一定。仔細檢查建設(shè)一個執(zhí)行狀態(tài)總是發(fā)生在另一個狀態(tài)之前的代碼。

總結(jié)

當編寫在后臺執(zhí)行的 iOS 代碼時:

  • 確定要使用哪個后臺刷新 API。對于需要很多秒才能完成的網(wǎng)絡(luò)請求,NSURLSession 會很有幫助。使用 iOS 提供的機會性后臺抓取代理對于需要按計劃獲取內(nèi)容的 app 會很有幫助。
  • 遠程推送通知可以是觸發(fā)后臺刷新的有效機制。
    Log 執(zhí)行狀態(tài)的變更,在有和沒有連接調(diào)試器的真機上測試,小心模擬器帶來的奇怪問題。是用開源的 iOS logging 庫,例如 CocoaLumberjackXCGLogger 會很有幫助。
  • 訪問鑰匙串或使用 iOS 數(shù)據(jù)保護功能時要小心。后臺刷新可能發(fā)生在鎖屏時,可能導致讀寫受保護的資源出現(xiàn)問題。
  • 高性能后臺代碼很關(guān)鍵:iOS 會優(yōu)先處理前臺的 App,嚴格限制 App 完成后臺任務(wù)的資源和時間。
    隨著移動數(shù)據(jù)使用量的增加和新的 iOS 9 功能(如 iPad 上的多任務(wù)處理拆分視圖),管理應(yīng)用執(zhí)行狀態(tài)對于構(gòu)建高質(zhì)量應(yīng)用程序非常重要——App 打開時持續(xù)不斷的進度指示條肯定會讓用戶很煩。后臺刷新是蘋果對開發(fā)人員的妥協(xié),旨在平衡用戶體驗與使用數(shù)據(jù)網(wǎng)絡(luò)和高網(wǎng)絡(luò)延遲時導致的電池消耗。利用后臺抓取 API 保持信息最新,并注意避免常見的坑,這有助于滿足用戶對 App 始終快速且永不崩潰的期望。

附錄

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 一個習慣站在潮頭的,怎么習慣被人忽視,你可以說這是虛榮,但阿爾帕西諾曾經(jīng)說過一句臺詞:“虛榮,我最愛的原罪?!?/div>
    HeyCoco閱讀 120評論 0 0
  • 或許有這樣的情況,我們最初的時候,在幣圈投資,想要獲得巨大的回報,但卻事與愿違,被割了韭菜;在網(wǎng)上開了淘寶店,希望...
    湛然_8d72閱讀 353評論 0 0
  • 我已經(jīng)很久很久沒有寫過東西了,上了大學之后,除了發(fā)說說和朋友圈平時基本沒有完整的去組織過一段話,直到現(xiàn)在,我發(fā)現(xiàn),...
    雨后無邪閱讀 1,172評論 0 1

友情鏈接更多精彩內(nèi)容