[轉載]iOS 后臺任務設計指導

原文地址:http://dxjia.cn/2016/05/26/ios-background-executions/?utm_source=tuicool&utm_medium=referral

上一篇 文章里介紹了 iOS APP 的狀態切換,其中一個狀態是 Background, 也就是后臺,在這個狀態,程序只被允許執行非常有限的一點點時間,然后就會隨時被掛起,不再執行任何代碼,但顯然是有應用場景需要不斷地在后臺執行一些任務,比如 音樂播放 APP, 健康記步軟件等。本文就來介紹 iOS 所提供的實現這些場景的技術。

后臺任務分類

首先 Apple 官方為我們界定了 **3
** 類后臺執行任務的場景:

  • Background Tasks:APP 在前臺時啟動某項任務,然后在未結束之前突然 切換到了后臺,那么 APP 可以在切換回調里使用某些 API 來繼續向系統請求一些時間來繼續完成這個任務;完成之后通知系統,之后系統會將 APP 掛起;
  • Downloading:在后臺啟動從網絡下載文件的任務 – 對于文件下載,iOS 有專門的機制;
  • Specific Backgournd Tasks:應用需要在后臺一直執行代碼;

這三種類型的后臺任務,在實現時各有不同,下面來一一介紹。

Background Tasks

Apple 文檔建議,如果要啟動一個后臺任務(異步任務),可以使用 API beginBackgroundTaskWithExpirationHandler來指定,即使啟動任務的時候,程序是處在前臺的,也沒有關系,當位于前臺時,該方法請求得到的時間是DBL_MAX
,也就是 double 數據類型最大值,你可以認為是無限大,當任務執行過程中 APP 被切換到后臺時,任務還沒有完成,這個時間又會自動調整為一個時間片段(具體多少我沒找到文檔說明,都是說可以通過backgroundTimeRemaining屬性得到)。需要注意的是, 這個方法是成對使用的,對于一個固定 task ,每次調用 beginBackgroundTaskWithExpirationHandler,都會產生一個 token 值(UIBackgroundTaskIdentifier實際是個整型),必須在任務執行結束時,調用 endBackgroundTask并傳遞這個 token,來結束后臺任務。另外,作為最佳實踐,都應該傳遞一個 超時 handler,以防申請到的時間片段內,還是沒能完成任務的話,做最后的清理和標注工作!如果不傳的話,那么結果就是 iOS 直接 kill 掉你的APP,閃退咯,因為它覺得我們騙了它嘛,哈哈。。。下面是一段在進入后臺時啟動異步任務的例子;

// 在某處定義一個 token 變量
UIBackgroundTaskIdentifier _bgTaskToken;

// 進入后臺 委派方法回調 
- (void)applicationDidEnterBackground:(UIApplication *)application
{
    _bgTaskToken = [application beginBackgroundTaskWithName:@"MyTask" expirationHandler:^{
        // 時間到了,任務還沒完成,只能清理
        ...
        // 取消后臺任務
        [application endBackgroundTask:_bgTaskToken];
        _bgTaskToken = UIBackgroundTaskInvalid;
    }];
 
    // 異步啟動任務,這樣不會阻塞 本委派方法回調
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // 巴拉巴拉,做自己的任務
        ...
        
        // 任務在時間限制內結束啦,取消后臺任務 
        [application endBackgroundTask:_bgTaskToken];
        _bgTaskToken = UIBackgroundTaskInvalid;
    });
}

Background Downloading

這類后臺任務,必須使用 iOS 指定的機制才可以,那就是 NSURLSession。使用 NSURLSession 建立的下載任務,會被系統直接在另外一個獨立的系統進程里進行管理,不會因 APP 進入后臺或掛起等而受到影響,iOS 會統一管理所有的下載任務。并且,即使你的 APP 已經掛掉啦,下載任務還是會繼續,等到下載完成啦,系統會喚起你的 APP 進程,并通知你,但如果是用戶主動殺掉的你的進程,那么系統會自動取消下載任務。具體使用方法:

  • 1, 使用 NSURLSessionConfiguration類的 backgroundSessionConfigurationWithIdentifier方法創建一個 NSURLSessionConfiguration 對象,參數為一個字串,作為一個 token ,完成時會用到,不能為空或 nil;
  • 2, 設置上一步創建出的對象的 sessionSendsLaunchEvents屬性為 YES;
  • 3, 如果開啟下載任務時,是位于前臺的,將 discretionary屬性也設置為 YES;
  • 4, 設置你需要的其他屬性值;
  • 5,使用配置好的 NSURLSessionConfiguration對象,作為參數,創建 NSURLSession實例對象;
  • 6,使用 NSURLSession開始下載task,這個這里不細講啦,需要看 《URL Session Programming Guide》;

如果在下載完成之前,你的APP已經掛起或者死掉啦,那么當系統完成下載之后,系統會喚醒你的 APP,并回調 你的 app 委托方法 application:handleEventsForBackgroundURLSession:completionHandler:,在這其中,參數會傳進來一個 token,這個就是你第一步里 傳入的 字符串,使用這個 字符串,再重新創建一個 NSURLSessionConfiguration,并進行與開始任務之前一樣的配置,那么就可以使用這些對象來獲取已經完成的任務的詳細情況了。

Background Long-Running Tasks

在 iOS 里只有特定的一些應用類型才會被允許可以在后臺一直運行,APP 必須顯式的聲明一些特定權限,才可以在后臺進行長時間運行而不被掛起。一些應用類型有 6 種:

  • 需要在后臺播放音頻 – 如 Music Player;
  • 需要在后臺錄音;
  • 在后臺時也需要不斷通知用戶位置變動的,比如導航;
  • 支持 VoIP 電話的 – 如 skype 網絡電話;
  • 需要在后臺有規律的下載和處理網絡內容的;
  • 在后臺有規律的從其他外設(第三方配件)獲取并更新數據的;

要實現這些類型服務的 APP,需要進行專門的聲明,這樣系統才會采取相應的操作。
先來看看怎么聲明。

聲明后臺服務類型

通過 XCode 的 project setting 里就可以配置類型,選擇之后會自動 在你 工程的 Info.plist 文件里 增加 UIBackgroundModes鍵值對;一個 APP 可以同時聲明多種支持的后臺長期任務類型,在 XCode 里勾選上即可;下表給出了所有 在 XCode 可選的 類型 及 具體含義;

Xcode background mode UIBackgroundModes 值 描述
Audio and AirPlay audio 應用可以在后臺播放或錄制音頻,包括 Apple 自家的 AirPlay 流媒體音視頻;對于錄制,需要在APP 第一次運行時,用戶授予權限才可進行。
Location updates location APP 不斷更新 GPS 位置信息,并通知給用戶,即使 APP 處于后臺
Voice over IP voip APP 提供通過網絡連接來打電話的功能
Newsstand downloads newsstand-content 雜志應用,可以在后臺下載雜志并處理
External accessory communication external-accessory 一些外設控制 APP, 比如一些控制 第三方 MFI 配件的應用,聲明這種 類型,可以讓APP 在后臺不斷的與 外設進行溝通
Uses Bluetooth LE accessories bluetooth-central iPhone 作為藍牙中心設備使用,也就是做為 server;需要在后臺不斷更新藍牙狀態的
Acts as a Bluetooth LE accessory bluetooth-peripheral iPhone 作為藍牙外圍設備使用,也就是做 client,需要在后臺不斷的訪問其他藍牙設備獲取數據的
Background fetch fetch APP 需要在后臺不斷地 頻繁有規律的從網絡獲取數據
Remote notifications remote-notification APP 先在后臺關注某個 push 推送,但這個 push 推送到達的時候,及時在后臺開始對應的下載任務,以盡可能減少用戶直接點開 通知 后 查看內容的等待時間

Playing and Recording Background Audio

一些典型的應用例子:

  • 音樂播放軟件
  • 錄音APP
  • 支持 AirPlay 音視頻播放的APP
  • 網絡通話軟件

當你在 Info.plist 里聲明了 UIBackgroundModes為 audio的時候,在后臺進行 audio 的相關操作時,系統 audio API 會自動阻止系統將你的 APP 進程掛起,所以不需要 APP 自己再進行其他額外的處理,只需要處理自己的軟件邏輯即可。
【Note】:手機上是有可能會有多個 APP 同時擁有后臺 audio 操作權限的,這時候系統會根據 每個 APP 開始操作音頻時的 audio session 配置來決定如何進行操作,而且你應該非常小心的處理一些中斷事件,如來電,其他系統提示音等,這些都有相關的 API 和機制,可以參考 《Audio Session Programming Guide》

Tracking the User’s Location

有三種方式來實現 位置的訪問:

  • The significant-change location service(這也是官方推薦的方式)
  • Foreground-only location services
  • Background location services

前兩種都不需要在 Info.plist 里聲明 UIBackgroundModes,只有最后一種需要。
The significant-change location service,字面理解,就是只有位置有變化時才會發出通知,有人說這個時機是依據基站,切換了基站時,就會發出一次通知,所以頻率會受基站的密度影響,所以市區更新頻率會比郊區高。但好處是這個服務不管你的 APP 是在前臺還是后臺,不管是否已經被掛起,或已經死掉了,他都會喚醒你的進程進行相應處理,所以應該是最省電的。
后兩種都是標準的定位服務,只不過一個只能工作在前臺,而一個可以在后臺工作;【Note】:官方對于使用后臺定位服務的 APP 審核是非常嚴格的,所以使用時一定要小心,并提供足夠的說明和解釋。
至于如何實現一個定位 APP ,請看 《Location and Maps Programming Guide 》

Implementing a VoIP App

iOS8之前:
網絡通話軟件,skype 就是其中一個。這樣的軟件使用 internet 連接來進行語音通話,為了提供健全的電話功能,這類軟件必須一直保持一個長期的網絡連接,以便監聽到來電。實現類似的功能,APP 自己并非一直在后臺不被掛起,而是交由系統監聽 網絡連接,有數據進來時,系統會喚醒 APP,并將 socket 轉交給 APP 進行處理;大致步驟:
在 Info.plist 里進行UIBackgroundModes配置;

  • 配置一個 socket 連接用于 VoIP;
  • 在進入后臺時,調用 setKeepAliveTimeout:handler:
  • 方法傳遞一個回調,用來處理事件;
  • 配置要使用到的 audio session;

【Note】:貌似對于 VoIP 的實現, iOS 8 有變化,改為使用 remote notification 的方式來做啦,誰說 iOS 沒有碎片化的啊,有!具體實現請參考 Tips for Developing a VoIP App 簡單說就是服務器發個特殊的通知,系統喚醒app來處理,連socket都不用保留了.

Downloading Newsstand Content in the Background

雜志應用,居然還有專門的處理。但我看介紹,跟前面講解的 后臺下載文件沒啥區別啊!!另外好像也是用 通知推送 觸發啊。About Newsstand Kit Framework

Communicating with an External Accessory

外設設備有很多,比如一些心率監控器,會在必要的時候向手機推送數據。聲明了UIBackgroundModes為 external-accessory 后,系統就不會主動關閉 APP 與 外設之間的連接,而是替 APP 監視這個連接,但有數據過來時,會喚醒 APP 進行處理,每次喚醒 APP 只有 10 S鐘時間進行數據處理,所以應當越快越好,萬不得已,如果10S不夠,需要使用 beginBackgroundTaskWithExpirationHandler:方法再申請一段時間進行處理;
【Note】:Apple 要求此類應用 需要提供一個 開啟 和 關閉 連接的界面供用戶使用;

Communicating with a Bluetooth Accessory

類似上一節的 配件,如果心率監控器跟 手機之間使用的連接方式是藍牙,那么就一模一樣啦,連 喚醒的時間限制都一樣,都是 10 S
!!!略啦。。。

Fetching Small Amounts of Content Opportunistically

有人依靠這種手段來實現后臺永存,但現在不好使啦,除非你是真的每次都在下載東西,而且每次時間都很短。用戶的流量啊。因為聲明了這個 mode 之后,并不保證 系統一定會給你分配時間來執行后臺任務,因為它自己有一套邏輯,如果你經常性喚醒,但卻每次都耗時很久,又沒有做從網絡下載東西的操作,那么以后你被分配給喚醒的幾率就會越來越小。另外還有審核!!!!
正常情況下,聲明了這個類型之后,系統在你的 APP 進入后臺后,會間隔性的給機會將你的 APP 喚醒,并回調你的 委托方法 application:performFetchWithCompletionHandler:,你需要在這個回調里檢查是否有新內容可用,如果有,就開啟后臺下載,推薦使用 NSURLSession來建立,下載完成后,你必須調用這個方法出入 的 completionHandler并傳入一個 整型值 來表示 你的處理是否正常,UI是否已經更新,讓系統來決定更新 snapshot等;

Using Push Notifications to Initiate a Download

這個方式,是你的應用中包含通知功能時,你在服務端推送的通知內容里加入 鍵值對 content-available= 1,那么 手機收到這個通知后,會自動啟動 APP 到后臺,或 喚醒(依舊保持后臺執行),并回調 委托方法 application:didReceiveRemoteNotification:fetchCompletionHandler:,在這個方法里進行內容下載。
【Note】:需要服務端推送配合

哪些情況系統會喚醒掛起進程

當一些特定事件發生時,系統會喚醒已經被掛起的進程,轉換到后臺運行狀態,這些事件針對不同類型的APP 有所不同:

location apps

  • 系統產生了符合 APP 配置的定位要求的位置更新;
  • 設備進入或離開了一個網絡注冊的區域,你可以理解為基站;

audio apps

  • audio framework 需要 app 處理數據的時候–任何 播放、錄制;

Bluetooth apps

  • 當手機扮演中心設備時,收到了其他藍牙設備發來的數據;
  • 當手機扮演外圍設備時,收到了藍牙服務端發來的數據;

background download apps

  • 本應用的一個包含 content-available= 1的推送通知到達了手機;
    background fetch 類型,系統給予了 APP 喚醒的機會;
  • 使用 NSURLSession進行后臺下載的APP,在下載過程完成或出現問題時,系統會主動喚醒對應 APP;
  • 雜志應用,下載完成時喚醒 APP;

【Note】:絕大多數情況下,系統不會重啟被用戶手動強制關閉的 APP,但在 iOS 8 之后, location apps 是個例外。其他的所有被用戶手動強制關閉的APP 都不會被系統主動喚起,直到 用戶再次 主動啟動這個 APP,或者手機重啟并在用戶輸入了解鎖密碼之后才會恢復機制。

做一個盡責的后臺APP

  • Apple 教育我們,如果你要實現一個后臺 APP,應該做一個有責任的APP,不要亂搞,哈哈。
  • 不要在后臺調用任何 OpenGL ES 接口,在進入后臺之前也要保證這些調用都已結束,否則你的 APP 將直接被 kill;
  • 取消所有 Bonjour相關的操作,還不清楚這個是啥東西,不過 Apple 說即使你不取消,它在把你掛起之前也會都給你取消;
  • 如果有網絡操作,做好容錯處理;
  • 保存 APP 狀態,進入后臺前持久化一些數據,以便恢復;
  • 盡可能多的釋放內存,尤其是強引用;
  • 停止使用共享的系統資源,比如 電話本,日歷等,進入后臺前,release他們;
  • 不要在后臺進行 UI 的更新操作;
  • 做好對外設配件的 連接 和斷開 事件的響應;這個是 外設編程的機制啦,需要 參考 External Accessory Programming Topics
  • 關閉彈出窗口和彈出菜單等;
  • 移除窗口上的一些敏感信息;
  • 在后臺的執行盡可能小的任務;

最后, Apple 建議能不后臺就不后臺,那當然。。。

Reference

[1] App Programming Guide for iOS - Background Execution

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,182評論 6 543
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,489評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,290評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,776評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,510評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,866評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,860評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,036評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,585評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,331評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,536評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,058評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,754評論 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,154評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,469評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,273評論 3 399
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,505評論 2 379

推薦閱讀更多精彩內容

  • 文檔app在后臺時會被暫停,暫停的apps會提高電池的使用壽命,并且會讓系統將重要的系統資源投入到引起用戶注意的前...
    zziazm閱讀 4,672評論 0 5
  • 蘋果官網地址 Background Execution (后臺執行)當用于沒有-啟動應用,系統移到后臺狀態。對于很...
    helinyu閱讀 7,773評論 0 9
  • 自從古老的iOS4以來,當用戶點擊home建的時候,你可以使你的APP們在內存中處于suspended(掛起)狀態...
    木易林1閱讀 3,201評論 1 4
  • 跟蹤用戶的位置 這里有幾種方式在后臺跟蹤用戶的位置,其中大部分都不會要求你的應用在后臺繼續運行。 位置重大改變(s...
    raingu24閱讀 655評論 0 0
  • 如果在你面前擺上半杯水,你認為這杯水是半空,還是半滿? 習慣負面思考的人會說:“真糟糕,只有半杯水了。”習慣正面思...
    未__央閱讀 762評論 0 0