今天要實現一個需求,當用戶觸摸HOME鍵,將應用切換到後臺時,啟動自動備份的任務。這涉及到ios的後臺任務處理,本文簡單總結一下
首先,ios app有5種狀態,分別是:not running, inactive, active, background, suspended,詳情請看官方的guide:
apple guide
機制
如果應用處於background狀態,又希望它繼續做一些事的話,ios提供瞭幾種途徑:
推送
蘋果提供的PUSH機制,叫APNS。騰訊的QQ和微信就是使用這種方式。實際上,使用長連接會更好,但是蘋果不支持。我理解其實應用已經suspended,但是當接收到push的數據以後,會短暫地回到background進行處理,處理完畢以後又回到suspended狀態
從ios7開始,分為local push和remote push,我們應用現在還沒用到,暫不深究
智能調度
太玄乎瞭,不太瞭解
特定的多任務
某些特定的任務可以在後臺長時間運行,比如VOIP,location service等,隻有特定類型的任務,才能用這種方式,適用性不強
後臺上傳下載
類似於特定多任務,隻有特殊的任務才能用
task completion
這種方式的適用性比較強,我最後也是采取這種方式來實現的。因此本文重點介紹這種
通常情況下,應用在進入background之後,很快就會轉到suspended狀態。但是,如果應用有需要的話(比如我們這個需求),可以向系統申請一點額外的時間來完成當前的任務
代碼:
- (void)applicationDidEnterBackground:(UIApplication *)application { __block UIBackgroundTaskIdentifier bgTask;// 後臺任務標識 // 結束後臺任務 void (^endBackgroundTask)() = ^(){ [application endBackgroundTask:bgTask]; bgTask = UIBackgroundTaskInvalid; }; bgTask = [application beginBackgroundTaskWithExpirationHandler:^{ endBackgroundTask(); }]; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ double start_time = application.backgroundTimeRemaining;// 記錄後臺任務開始時間 BOOL networkAvailable = [YLSGlobalUtils isNetworkAvailable]; if(!networkAvailable){ NSLog(@"網絡不可用,取消自動備份"); endBackgroundTask(); return; } BOOL need = [backupService checkNeedBackup]; if(!need){ NSLog(@"無需備份"); endBackgroundTask(); return; } [backupService doBackupProcessHandler:^(float done, float total){ // nothing to do with progress } CompletionHandler:^(NSError* error, NSArray* statistics){ double done_time = application.backgroundTimeRemaining; double spent_time = start_time - done_time; NSLog(@"後臺備份完成,耗時: %f秒", spent_time); endBackgroundTask(); }]; }); }
核心是這個方法:
- (UIBackgroundTaskIdentifier)beginBackgroundTaskWithExpirationHandler:(void(^)(void))handler
註冊一個後臺任務,這個任務最多隻有10分鐘時間,如果超時,則會調用參數中的block,在此block中,必須調用這個方法:
- (void)endBackgroundTask:(UIBackgroundTaskIdentifier)identifier
否則,應用會crash。隻要調用瞭beginBackgroundTaskWithExpirationHandler:方法,就必須在handler裡相應地調用endBackgroundTask:方法
實際的邏輯,在下面的block裡完成,這裡沒什麼特別的,隻是如果提前結束瞭任務,也調用一次endBackgroundTask:方法,這樣就不會超時,前面的expirationHandler就不會被執行
另外,通過UIApplication的backgroundTimeRemaining屬性,可以獲取此後臺任務還剩餘的時間(當此值變成0,expirationHandler就被執行)
使用時機
這段代碼,是寫在ApplicationDelegate的生命周期方法裡:
- (void)applicationDidEnterBackground:(UIApplication *)application
這主要是因為,我們就是希望僅當應用被切換到後臺時才開始自動備份。但是後臺任務並不是隻能在這種情況下啟動,如果應用中有一些關鍵性的任務,希望即使被切換到後臺也要先完成再suspend,就可以隨時調用
- (UIBackgroundTaskIdentifier)beginBackgroundTaskWithExpirationHandler:(void(^)(void))handler
以確保此任務完成,寫法隻要參考上面的代碼就行瞭