ios后台机制
后臺(tái)任務(wù)分類
首先 Apple 官方為我們界定了 3 類后臺(tái)執(zhí)行任務(wù)的場(chǎng)景:
- 正常:APP切換到后臺(tái)有5秒時(shí)間
- 短時(shí)任務(wù) Background Tasks:APP 在前臺(tái)時(shí)啟動(dòng)某項(xiàng)任務(wù),然后在未結(jié)束之前突然 切換到了后臺(tái),那么 APP 可以在切換回調(diào)里使用某些 API 來(lái)繼續(xù)向系統(tǒng)請(qǐng)求一些時(shí)間來(lái)繼續(xù)完成這個(gè)任務(wù);完成之后通知系統(tǒng),之后系統(tǒng)會(huì)將 APP 掛起;
- 長(zhǎng)時(shí)任務(wù) Specific Backgournd Tasks:應(yīng)用需要在后臺(tái)一直執(zhí)行代碼;
- 下載 Downloading:在后臺(tái)啟動(dòng)從網(wǎng)絡(luò)下載文件的任務(wù) – 對(duì)于文件下載,iOS 有專門的機(jī)制;
Background Tasks
Apple 文檔建議,如果要啟動(dòng)一個(gè)后臺(tái)任務(wù)(異步任務(wù)),可以使用 API beginBackgroundTaskWithExpirationHandler來(lái)指定,即使啟動(dòng)任務(wù)的時(shí)候,程序是處在前臺(tái)的,也沒(méi)有關(guān)系,當(dāng)位于前臺(tái)時(shí),該方法請(qǐng)求得到的時(shí)間是DBL_MAX,也就是 double 數(shù)據(jù)類型最大值,你可以認(rèn)為是無(wú)限大,當(dāng)任務(wù)執(zhí)行過(guò)程中 APP 被切換到后臺(tái)時(shí),任務(wù)還沒(méi)有完成,這個(gè)時(shí)間又會(huì)自動(dòng)調(diào)整為一個(gè)時(shí)間片段(具體多少我沒(méi)找到文檔說(shuō)明,都是說(shuō)可以通過(guò)backgroundTimeRemaining 屬性得到)。需要注意的是, 這個(gè)方法是成對(duì)使用的,對(duì)于一個(gè)固定 task ,每次調(diào)用 beginBackgroundTaskWithExpirationHandler,都會(huì)產(chǎn)生一個(gè) token 值(UIBackgroundTaskIdentifier 實(shí)際是個(gè)整型),必須在任務(wù)執(zhí)行結(jié)束時(shí),調(diào)用 endBackgroundTask 并傳遞這個(gè) token,來(lái)結(jié)束后臺(tái)任務(wù)。另外,作為最佳實(shí)踐,都應(yīng)該傳遞一個(gè) 超時(shí) handler,以防申請(qǐng)到的時(shí)間片段內(nèi),還是沒(méi)能完成任務(wù)的話,做最后的清理和標(biāo)注工作!如果不傳的話,那么結(jié)果就是 iOS 直接 kill 掉你的APP,閃退咯,因?yàn)樗X(jué)得我們騙了它嘛,哈哈。。。
下面是一段在進(jìn)入后臺(tái)時(shí)啟動(dòng)異步任務(wù)的例子;
Background Downloading
這類后臺(tái)任務(wù),必須使用 iOS 指定的機(jī)制才可以,那就是 NSURLSession。使用 NSURLSession 建立的下載任務(wù),會(huì)被系統(tǒng)直接在另外一個(gè)獨(dú)立的系統(tǒng)進(jìn)程里進(jìn)行管理,不會(huì)因 APP 進(jìn)入后臺(tái)或掛起等而受到影響,iOS 會(huì)統(tǒng)一管理所有的下載任務(wù)。并且,即使你的 APP 已經(jīng)掛掉啦,下載任務(wù)還是會(huì)繼續(xù),等到下載完成啦,系統(tǒng)會(huì)喚起你的 APP 進(jìn)程,并通知你,但如果是用戶主動(dòng)殺掉的你的進(jìn)程,那么系統(tǒng)會(huì)自動(dòng)取消下載任務(wù)。
具體使用方法:
如果在下載完成之前,你的APP已經(jīng)掛起或者死掉啦,那么當(dāng)系統(tǒng)完成下載之后,系統(tǒng)會(huì)喚醒你的 APP,并回調(diào) 你的 app 委托方法 application:handleEventsForBackgroundURLSession:completionHandler:,在這其中,參數(shù)會(huì)傳進(jìn)來(lái)一個(gè) token,這個(gè)就是你第一步里 傳入的 字符串,使用這個(gè) 字符串,再重新創(chuàng)建一個(gè) NSURLSessionConfiguration,并進(jìn)行與開(kāi)始任務(wù)之前一樣的配置,那么就可以使用這些對(duì)象來(lái)獲取已經(jīng)完成的任務(wù)的詳細(xì)情況了。
Background Long-Running Tasks
在 iOS 里只有特定的一些應(yīng)用類型才會(huì)被允許可以在后臺(tái)一直運(yùn)行,APP 必須顯式的聲明一些特定權(quán)限,才可以在后臺(tái)進(jìn)行長(zhǎng)時(shí)間運(yùn)行而不被掛起。
一些應(yīng)用類型有 6 種:
- 需要在后臺(tái)播放音頻 – 如 Music Player;
- 需要在后臺(tái)錄音;
- 在后臺(tái)時(shí)也需要不斷通知用戶位置變動(dòng)的,比如導(dǎo)航;
- 支持 VoIP 電話的 – 如 skype 網(wǎng)絡(luò)電話;
- 需要在后臺(tái)有規(guī)律的下載和處理網(wǎng)絡(luò)內(nèi)容的;
- 在后臺(tái)有規(guī)律的從其他外設(shè)(第三方配件)獲取并更新數(shù)據(jù)的;
要實(shí)現(xiàn)這些類型服務(wù)的 APP,需要進(jìn)行專門的聲明,這樣系統(tǒng)才會(huì)采取相應(yīng)的操作。
先來(lái)看看怎么聲明
聲明后臺(tái)服務(wù)類型
通過(guò) XCode 的 project setting 里就可以配置類型,選擇之后會(huì)自動(dòng) 在你 工程的 Info.plist 文件里 增加 UIBackgroundModes 鍵值對(duì);一個(gè) APP 可以同時(shí)聲明多種支持的后臺(tái)長(zhǎng)期任務(wù)類型,在 XCode 里勾選上即可;
下表給出了所有 在 XCode 可選的 類型 及 具體含義;
| Audio and AirPlay | audio | 應(yīng)用可以在后臺(tái)播放或錄制音頻,包括 Apple 自家的 AirPlay 流媒體音視頻;對(duì)于錄制,需要在APP 第一次運(yùn)行時(shí),用戶授予權(quán)限才可進(jìn)行。 |
| Location updates | location | APP 不斷更新 GPS 位置信息,并通知給用戶,即使 APP 處于后臺(tái) |
| Voice over IP | voip | APP 提供通過(guò)網(wǎng)絡(luò)連接來(lái)打電話的功能 |
| Newsstand downloads | newsstand-content | 雜志應(yīng)用,可以在后臺(tái)下載雜志并處理 |
| External accessory communication | external-accessory | 一些外設(shè)控制 APP, 比如一些控制 第三方 MFI 配件的應(yīng)用,聲明這種 類型,可以讓APP 在后臺(tái)不斷的與 外設(shè)進(jìn)行溝通 |
| Uses Bluetooth LE accessories | bluetooth-central | iPhone 作為藍(lán)牙中心設(shè)備使用,也就是做為 server;需要在后臺(tái)不斷更新藍(lán)牙狀態(tài)的 |
| Acts as a Bluetooth LE accessory | bluetooth-peripheral | iPhone 作為藍(lán)牙外圍設(shè)備使用,也就是做 client,需要在后臺(tái)不斷的訪問(wèn)其他藍(lán)牙設(shè)備獲取數(shù)據(jù)的 |
| Background fetch | fetch | APP 需要在后臺(tái)不斷地 頻繁有規(guī)律的從網(wǎng)絡(luò)獲取數(shù)據(jù) |
| Remote notifications | remote-notification | APP 先在后臺(tái)關(guān)注某個(gè) push 推送,但這個(gè) push 推送到達(dá)的時(shí)候,及時(shí)在后臺(tái)開(kāi)始對(duì)應(yīng)的下載任務(wù),以盡可能減少用戶直接點(diǎn)開(kāi) 通知 后 查看內(nèi)容的等待時(shí)間 |
Playing and Recording Background Audio
一些典型的應(yīng)用例子:
- 音樂(lè)播放軟件
- 錄音APP
- 支持 AirPlay 音視頻播放的APP
- 網(wǎng)絡(luò)通話軟件
當(dāng)你在 Info.plist 里聲明了 UIBackgroundModes 為 audio 的時(shí)候,在后臺(tái)進(jìn)行 audio 的相關(guān)操作時(shí),系統(tǒng) audio API 會(huì)自動(dòng)阻止系統(tǒng)將你的 APP 進(jìn)程掛起,所以不需要 APP 自己再進(jìn)行其他額外的處理,只需要處理自己的軟件邏輯即可。
【Note】:手機(jī)上是有可能會(huì)有多個(gè) APP 同時(shí)擁有后臺(tái) audio 操作權(quán)限的,這時(shí)候系統(tǒng)會(huì)根據(jù) 每個(gè) APP 開(kāi)始操作音頻時(shí)的 audio session 配置來(lái)決定如何進(jìn)行操作,而且你應(yīng)該非常小心的處理一些中斷事件,如來(lái)電,其他系統(tǒng)提示音等,這些都有相關(guān)的 API 和機(jī)制,可以參考 《Audio Session Programming Guide》
Tracking the User’s Location
有三種方式來(lái)實(shí)現(xiàn) 位置的訪問(wèn):
- The significant-change location service(這也是官方推薦的方式)
- Foreground-only location services
- Background location services
前兩種都不需要在 Info.plist 里聲明 UIBackgroundModes ,只有最后一種需要。
The significant-change location service ,字面理解,就是只有位置有變化時(shí)才會(huì)發(fā)出通知,有人說(shuō)這個(gè)時(shí)機(jī)是依據(jù)基站,切換了基站時(shí),就會(huì)發(fā)出一次通知,所以頻率會(huì)受基站的密度影響,所以市區(qū)更新頻率會(huì)比郊區(qū)高。但好處 是這個(gè)服務(wù)不管你的 APP 是在前臺(tái)還是后臺(tái),不管是否已經(jīng)被掛起,或已經(jīng)死掉了,他都會(huì)喚醒你的進(jìn)程進(jìn)行相應(yīng)處理,所以應(yīng)該是最省電的。
后兩種都是標(biāo)準(zhǔn)的定位服務(wù),只不過(guò)一個(gè)只能工作在前臺(tái),而一個(gè)可以在后臺(tái)工作;
【Note】:官方對(duì)于使用后臺(tái)定位服務(wù)的 APP 審核是非常嚴(yán)格的,所以使用時(shí)一定要小心,并提供足夠的說(shuō)明和解釋。
至于如何實(shí)現(xiàn)一個(gè)定位 APP ,請(qǐng)看 《Location and Maps Programming Guide 》
Implementing a VoIP App
網(wǎng)絡(luò)通話軟件,skype 就是其中一個(gè)。這樣的軟件使用 internet 連接來(lái)進(jìn)行語(yǔ)音通話,為了提供健全的電話功能,這類軟件必須一直保持一個(gè)長(zhǎng)期的網(wǎng)絡(luò)連接,以便監(jiān)聽(tīng)到來(lái)電。實(shí)現(xiàn)類似的功能,APP 自己并非一直在后臺(tái)不被掛起,而是交由系統(tǒng)監(jiān)聽(tīng) 網(wǎng)絡(luò)連接,有數(shù)據(jù)進(jìn)來(lái)時(shí),系統(tǒng)會(huì)喚醒 APP,并將 socket 轉(zhuǎn)交給 APP 進(jìn)行處理;
大致步驟:
- 在 Info.plist 里進(jìn)行UIBackgroundModes配置;
- 配置一個(gè) socket 連接用于 VoIP;
- 在進(jìn)入后臺(tái)時(shí),調(diào)用 setKeepAliveTimeout:handler:方法傳遞一個(gè)回調(diào),用來(lái)處理事件;
- 配置要使用到的 audio session;
【Note】:貌似對(duì)于 VoIP 的實(shí)現(xiàn), iOS 8 有變化,改為使用 remote notification 的方式來(lái)做啦,誰(shuí)說(shuō) iOS 沒(méi)有碎片化的啊,有!具體實(shí)現(xiàn)請(qǐng)參考 Tips for Developing a VoIP App
Downloading Newsstand Content in the Background
雜志應(yīng)用,居然還有專門的處理。但我看介紹,跟前面講解的 后臺(tái)下載文件沒(méi)啥區(qū)別啊!!另外好像也是用 通知推送 觸發(fā)啊。About Newsstand Kit Framework
Communicating with an External Accessory
外設(shè)設(shè)備有很多,比如一些心率監(jiān)控器,會(huì)在必要的時(shí)候向手機(jī)推送數(shù)據(jù)。聲明了UIBackgroundModes 為 external-accessory 后,系統(tǒng)就不會(huì)主動(dòng)關(guān)閉 APP 與 外設(shè)之間的連接,而是替 APP 監(jiān)視這個(gè)連接,但有數(shù)據(jù)過(guò)來(lái)時(shí),會(huì)喚醒 APP 進(jìn)行處理,每次喚醒 APP 只有 10 S 種時(shí)間進(jìn)行數(shù)據(jù)處理,所以應(yīng)當(dāng)越快越好,萬(wàn)不得已,如果10S不夠,需要使用 beginBackgroundTaskWithExpirationHandler: 方法再申請(qǐng)一段時(shí)間進(jìn)行處理;
【Note】:Apple 要求此類應(yīng)用 需要提供一個(gè) 開(kāi)啟 和 關(guān)閉 連接的界面供用戶使用
Communicating with a Bluetooth Accessory
類似上一節(jié)的 配件,如果心率監(jiān)控器跟 手機(jī)之間使用的連接方式是藍(lán)牙,那么就一模一樣啦,連 喚醒的時(shí)間限制都一樣,都是 10 S!!!略啦。。。
Fetching Small Amounts of Content Opportunistically
有人依靠這種手段來(lái)實(shí)現(xiàn)后臺(tái)永存,但現(xiàn)在不好使啦,除非你是真的每次都在下載東西,而且每次時(shí)間都很短。用戶的流量啊。因?yàn)槁暶髁诉@個(gè) mode 之后,并不保證 系統(tǒng)一定會(huì)給你分配時(shí)間來(lái)執(zhí)行后臺(tái)任務(wù),因?yàn)樗约河幸惶走壿?#xff0c;如果你經(jīng)常性喚醒,但卻每次都耗時(shí)很久,又沒(méi)有做從網(wǎng)絡(luò)下載東西的操作,那么以后你被分配給喚醒的幾率就會(huì)越來(lái)越小。另外還有審核!!!!
正常情況下,聲明了這個(gè)類型之后,系統(tǒng)在你的 APP 進(jìn)入后臺(tái)后,會(huì)間隔性的給機(jī)會(huì)將你的 APP 喚醒,并回調(diào)你的 委托方法 application:performFetchWithCompletionHandler:,你需要在這個(gè)回調(diào)里檢查是否有新內(nèi)容可用,如果有,就開(kāi)啟后臺(tái)下載,推薦使用 NSURLSession 來(lái)建立,下載完成后,你必須調(diào)用這個(gè)方法出入 的 completionHandler 并傳入一個(gè) 整型值 來(lái)表示 你的處理是否正常,UI是否已經(jīng)更新,讓系統(tǒng)來(lái)決定更新 snapshot等;
Using Push Notifications to Initiate a Download
這個(gè)方式,是你的應(yīng)用中包含通知功能時(shí),你在服務(wù)端推送的通知內(nèi)容里加入 鍵值對(duì) content-available = 1 ,那么 手機(jī)收到這個(gè)通知后,會(huì)自動(dòng)啟動(dòng) APP 到后臺(tái),或 喚醒(依舊保持后臺(tái)執(zhí)行),并回調(diào) 委托方法 application:didReceiveRemoteNotification:fetchCompletionHandler: ,在這個(gè)方法里進(jìn)行內(nèi)容下載。
【Note】:需要服務(wù)端推送配合
哪些情況系統(tǒng)會(huì)喚醒掛起進(jìn)程
當(dāng)一些特定事件發(fā)生時(shí),系統(tǒng)會(huì)喚醒已經(jīng)被掛起的進(jìn)程,轉(zhuǎn)換到后臺(tái)運(yùn)行狀態(tài),這些事件針對(duì)不同類型的APP 有所不同:
location apps
- 系統(tǒng)產(chǎn)生了符合 APP 配置的定位要求的位置更新;
- 設(shè)備進(jìn)入或離開(kāi)了一個(gè)網(wǎng)絡(luò)注冊(cè)的區(qū)域,你可以理解為基站;
audio apps
- audio framework 需要 app 處理數(shù)據(jù)的時(shí)候–任何 播放、錄制;
Bluetooth apps
- 當(dāng)手機(jī)扮演中心設(shè)備時(shí),收到了其他藍(lán)牙設(shè)備發(fā)來(lái)的數(shù)據(jù);
- 當(dāng)手機(jī)扮演外圍設(shè)備時(shí),收到了藍(lán)牙服務(wù)端發(fā)來(lái)的數(shù)據(jù);
background download apps
- 本應(yīng)用的一個(gè)包含 content-available = 1 的推送通知到達(dá)了手機(jī);
- background fetch 類型,系統(tǒng)給予了 APP 喚醒的機(jī)會(huì);
- 使用 NSURLSession 進(jìn)行后臺(tái)下載的APP,在下載過(guò)程完成或出現(xiàn)問(wèn)題時(shí),系統(tǒng)會(huì)主動(dòng)喚醒對(duì)應(yīng) APP;
- 雜志應(yīng)用,下載完成時(shí)喚醒 APP;
【Note】:絕大多數(shù)情況下,系統(tǒng)不會(huì)重啟被用戶手動(dòng)強(qiáng)制關(guān)閉的 APP,但在 iOS 8 之后, location apps 是個(gè)例外。其他的所有被用戶手動(dòng)強(qiáng)制關(guān)閉的APP 都不會(huì)被系統(tǒng)主動(dòng)喚起,直到 用戶再次 主動(dòng)啟動(dòng)這個(gè) APP,或者手機(jī)重啟并在用戶輸入了解鎖密碼之后才會(huì)恢復(fù)機(jī)制。
做一個(gè)盡責(zé)的后臺(tái)APP
Apple 教育我們,如果你要實(shí)現(xiàn)一個(gè)后臺(tái) APP,應(yīng)該做一個(gè)有責(zé)任的APP,不要亂搞,哈哈。
- 不要在后臺(tái)調(diào)用任何 OpenGL ES 接口,在進(jìn)入后臺(tái)之前也要保證這些調(diào)用都已結(jié)束,否則你的 APP 將直接被 kill;
- 取消所有 Bonjour 相關(guān)的操作,還不清楚這個(gè)是啥東西,不過(guò) Apple 說(shuō)即使你不取消,它在把你掛起之前也會(huì)都給你取消;
- 如果有網(wǎng)絡(luò)操作,做好容錯(cuò)處理;
- 保存 APP 狀態(tài),進(jìn)入后臺(tái)前持久化一些數(shù)據(jù),以便恢復(fù);
- 盡可能多的釋放內(nèi)存,尤其是強(qiáng)引用;
- 停止使用共享的系統(tǒng)資源,比如 電話本,日歷等,進(jìn)入后臺(tái)前,release他們;
- 不要在后臺(tái)進(jìn)行 UI 的更新操作;
- 做好對(duì)外設(shè)配件的 連接 和斷開(kāi) 事件的響應(yīng);這個(gè)是 外設(shè)編程的機(jī)制啦,需要 參考 External Accessory Programming Topics ;
- 關(guān)閉彈出窗口和彈出菜單等;
- 移除窗口上的一些敏感信息;
- 在后臺(tái)的執(zhí)行盡可能小的任務(wù);
最后, Apple 建議能不后臺(tái)就不后臺(tái),那當(dāng)然。。。
總結(jié)
- 上一篇: Charles调试Https iOS
- 下一篇: ios企业版更新