1. 程式人生 > >iOS下的後臺任務實現方法

iOS下的後臺任務實現方法


後臺任務

場景一

地圖後臺定位  

場景二

後臺播放音樂

場景三 

後臺 更新推送內容 

場景四 

voip  IP電話   視訊通話

關於Background Fetch的更多請參考:<IOS 7四種後臺機制中關於Background Fetch的解釋,這裡只說明怎麼使用Background Fetch

一,開啟Background Fetch支援

XCode->TARGETS->Capabilities->Background Modes開啟並新增Background Fetch.

同時在- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions

方法中新增:

[[UIApplication sharedApplication] setMinimumBackgroundFetchInterval:UIApplicationBackgroundFetchIntervalMinimum];

MinimumBackgroundFetchInterval引數值是兩次Fetch時間間隔,不能保證每隔這個時間間隔都會呼叫。這裡設定為UIApplicationBackgroundFetchIntervalMinimum,意思是儘可能頻繁的呼叫我們的Fetch方法。

二,增加實現Fetch方法

- (void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler{};

每次系統Fetch時都會呼叫該方法,我們可以在該方法中做重新整理資料等操作,操作執行完成以後要呼叫completionHandlerblock(),比如:completionHandler(UIBackgroundFetchResultNewData);文件中說系統會根據completionHandler(執行的時間)來估計此次Fetch的耗電等。如果耗時耗電比較多,可能會降低被呼叫的次數。但這個方法也不是不限時執行的,說是有30s的時間來執行操作。completionHandler有三個引數:

UIBackgroundFetchResultNewData 成功拉取資料

UIBackgroundFetchResultNoData 

沒有新資料

UIBackgroundFetchResultFailed 拉取資料失敗或者超時

三,模擬Fetch事件

在實際的IOS7環境中,Fetch事件是由系統管理的,app開發者無法預先知道Fetch事件達到的時機。但XCode也提供了Fetch事件的除錯辦法,在XCode上執行程式後,在Debug->Simulate Background Fetch.

還有一種情況是app沒有執行(不在前臺也不在後臺),被Fetch事件喚醒執行.這種情況的測試方法如下:

Product->Scheme->Edit scheme Debug模式選中Options,點選Launch due to a background fetch event,執行即可。

[特酷吧]可以觀察到當Fetch事件到來時,app先進入後臺,再執行- (void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler{}

四,判斷裝置是否開啟後臺應用程式重新整理功能

摺疊C/C++ Code複製內容到剪貼簿

if ([[UIApplication sharedApplication] backgroundRefreshStatus] != UIBackgroundRefreshStatusAvailable)

{

UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:nil message:@"您沒有開啟後臺重新整理,請在設定->通用->應用程式後臺重新整理中開啟." delegate:nil cancelButtonTitle:@"確定" otherButtonTitles:nil];

[alertView show];

[alertView release];

}

在 iOS 7 之前,當程式置於後臺之後開發者們對他們程式所能做的事情非常有限。除了 VOIP 和基於地理位置特性以外,唯一能做的地方就是使用後臺任務(background tasks)讓程式碼可以執行幾分鐘。如果你想下載比較大的視訊檔案以便離線瀏覽,亦或者備份使用者的照片到你的伺服器上,你都僅能完成一部分工作。

iOS 7 添加了兩個新的 API 以便你的程式可以在後臺更新介面以及內容。首先是後臺獲取(Background Fetch),它允許你定期地從網路獲取新的內容。第二個 API 就是遠端通知(Remote Notifications),這是一個當事件發生時可以讓推送通知主動提醒應用的新特性,這兩者都為你的應用介面保持最新提供了極大的幫助。在新的後臺傳輸服務 (Background Transfer Service) 中執行定期的任務,也允許你在程序之外可以執行網路傳輸(下載和上傳)工作。

後臺獲取 (Background Fetch) 和遠端通知 (Remote Notification) 基於簡單的 ApplicationDelegate 鉤子,在應用程式掛起之前的 30 秒時鐘時間執行工作。它們不是用於 CPU 頻繁工作或者長時間執行任務,而是用來處理長時間執行的網路請求佇列,例如下載一部很大的電影,或者執行快速的內容更新。

對使用者來說,多工處理有一點顯而易見的改變就是新的應用切換程式 (the new app switcher),它用來呈現應用到後臺時的介面快照。這些快照的存在是有一定理由的--現在你可以在後臺完成工作後更新程式快照,以用來呈現新的內容。社交網路、新聞或者天氣等應用現在都可以直接呈現最新的內容而不需要使用者重新開啟應用。我們稍後會介紹如何更新螢幕快照。

後臺獲取

後臺獲取是一種智慧的輪詢機制,它很適合需要經常更新內容的程式,像社交網路,新聞或天氣的程式。為了在使用者啟動程式前提前觸發後臺獲取,系統會根據使用者行為喚醒應用程式。舉個例子,如果使用者經常在下午 1 點使用某個應用程式,系統會學習,適應並在使用週期前執行後臺獲取。為了減少電池使用,使用裝置無線通訊的所有應用的後臺獲取會被合併,如果你向系統報告新資料無法獲取,iOS 會適應並使用此資訊避免會繼續獲取。

開啟後臺獲取的第一步是在 info plist 檔案中對 UIBackgroundModes 鍵指定特定的值。最簡單的途徑是在 Xcode 5 的 project editor 中新的 Capabilities 標籤頁中設定,這個標籤頁包含了後臺模式部分,可以方便配置多工選項。

或者,你可以手動編輯這個值

<key>UIBackgroundModes</key>

<array>

<string>fetch</string>

</array>

接下來,告訴 iOS 多久進行一次資料獲取

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions

{

    [application setMinimumBackgroundFetchInterval:UIApplicationBackgroundFetchIntervalMinimum];

returnYES;

}

iOS 預設不進行後臺獲取,所以你需要設定一個時間間隔,否則,你的應用程式永遠不能在後臺被喚醒。UIApplicationBackgroundFetchIntervalMinimum 這個值要求系統儘可能頻繁地去管理你的程式到底什麼時候應該被喚醒,但如果你不需要這樣的話,你也應該指定一個你想要的的時間間隔。例如,一個天氣的應用程式,可能只需要幾個小時才更新一次,iOS 將會在後臺獲取之間至少等待你指定的時間間隔。

如果你的應用允許使用者退出登入,那麼就沒有獲取新資料的需要了,你應該把 minimumBackgroundFetchInterval 設定為UIApplicationBackgroundFetchIntervalNever,這樣可以節省資源。

最後一步是在應用程式委託中實現下列方法:

- (void)                application:(UIApplication *)application 

  performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler

{

    NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];

NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfiguration];

NSURL *url = [[NSURL alloc] initWithString:@"http://yourserver.com/data.json"];

NSURLSessionDataTask *task = [session dataTaskWithURL:url 

                                        completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {

if (error) {

            completionHandler(UIBackgroundFetchResultFailed);

return;

        }

// 解析響應/資料以決定新內容是否可用

BOOL hasNewData = ...

if (hasNewData) {

            completionHandler(UIBackgroundFetchResultNewData);

        } else {

            completionHandler(UIBackgroundFetchResultNoData);

        }

    }];

// 開始任務

    [task resume];

}

系統喚醒應用程式後將會執行這個委託方法。需要注意的是,你只有 30 秒的時間來確定獲取的新內容是否可用,然後處理新內容並更新介面。30 秒時間應該足夠去從網路獲取資料和獲取介面的縮圖,但是最多隻有 30 秒。當完成了網路請求和更新介面後,你應該執行完成的回撥。

完成回撥的執行有兩個目的。首先,系統會估量你的程序消耗的電量,並根據你傳遞的 UIBackgroundFetchResult 引數記錄新資料是否可用。其次,當你呼叫完成的處理程式碼時,應用的介面縮圖會被採用,並更新應用程式切換器。當用戶在應用間切換時,使用者將會看到新內容。這種通過 completion handler 來報告並且生成截圖的方法,在新的多工處理 API 中是很常見的。

在實際應用中,你應該將 completionHandler 傳遞到應用程式的子元件,然後在處理完資料和更新介面後呼叫。

在這裡,你可能想知道 iOS 是如何在應用程式後臺執行時獲得介面截圖的,並且想知道應用程式的生命週期與後臺獲取之間有什麼關係。如果應用程式處於掛起狀態,系統會先喚醒應用,然後再呼叫 application: performFetchWithCompletionHandler:。如果應用程式還沒有啟動,系統將會啟動它,然後呼叫常見的委託方法,包括 application: didFinishLaunchingWithOptions:。你可以把這種應用程式執行的方式想像為使用者從 Springboard 啟動這個程式,區別僅僅在於介面是看不見的,在螢幕外渲染的。

大多數情況下,無論應用在後臺啟動或者在前臺,你會執行相同的工作,但你可以通過檢視 UIApplication 的 applicationState 屬性來判斷應用是不是從後臺啟動。

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions

{

NSLog(@"Launched in background %d", UIApplicationStateBackground == application.applicationState);

returnYES;

}

測試後臺資料獲取

有兩種可以模擬後臺獲取的途徑。最簡單是從 Xcode 執行你的應用,當應用執行時,在 Xcode 的 Debug 選單選擇 Simulate Background Fetch.

第二種方法,使用 scheme 更改 Xcode 執行程式的方式。在 Xcode 選單的 Product 選項,選擇 Scheme 然後選擇 Manage Schemes。在這裡,你可以編輯或者新增一個新的 scheme,然後選中 Launch due to a background fetch event 。如下圖:

遠端通知

遠端通知允許你在重要事件發生時,告知你的應用。你可能需要傳送新的即時資訊,突發新聞的提醒,或者使用者喜愛電視的最新劇集已經可以下載以便離線觀看的訊息。遠端通知很適合用於那些偶爾出現,但卻很重要的內容,如果使用後臺獲取模式中在兩次獲取間需要等待的時間是不可接受的話,遠端通知會是一個不錯的選擇。遠端通知會比後臺獲取更有效率,因為應用程式只有在需要的時候才會啟動。

一條遠端通知實際上只是一條普通的帶有 content-available 標誌的推送通知。你可以傳送一條帶有提醒資訊的推送去告訴使用者有事請發生了,同時在後臺對介面進行更新。但遠端通知也可以做到在安靜地,沒有提醒訊息或者任何聲音的情況下,只去更新應用介面或者觸發後臺工作。然後你可以在完成下載或者處理完新內容後,傳送一條本地通知。

靜默的推送通知有速度限制,所以你可以大膽地根據應用程式的需要傳送儘可能多的通知。iOS 和蘋果推送服務會控制推送通知多久被遞送,傳送很多推送通知是沒有問題的。如果你的推送通知達到了限制,推送通知可能會被延遲,直到裝置下次傳送保持活動狀態的資料包,或者收到另外一個通知。

傳送遠端通知

要傳送一條遠端通知,需要在推送通知的有效負載(payload)設定 content-available 標誌。content-available 標誌和用來通知 報刊應用(Newsstand)的健值是一樣的,因此,大多數推送指令碼和庫都已經支援遠端通知。當你傳送一條遠端通知時,你可能還想要包含一些通知有效負載中的資料,讓你應用程式可以引用事件。這可以為你節省一些網路請求,並提高應用程式的響應度。

我建議在開發的時候,使用 Nomad CLI’s Houston 工具傳送推送訊息,當然你也可以使用你喜歡的庫或指令碼。

你可以通過 nomad-cli ruby gem 來安裝 Houston

gem install nomad-cli

然後通過包含在 Nomad 的 apn 實用工具傳送一條通知:

# Send a Push Notification to your Device

apn push <device token> -c /path/to/key-cert.pem -n -d content-id=42

在這裡,-n 標誌指定應該包含 content-available 健值,-d 標誌允許新增我們自定義的資料健值到有效負荷。

通知的有效負荷(payload)結果和下面類似:

{

    "aps" : {

        "content-available" : 1

    },

    "content-id" : 42

}

iOS 7 添加了一個新的應用程式委託方法,當接收到一條帶有 content-available 的推送通知時,下面的方法會被呼叫:

- (void)application:(UIApplication *)application 

  didReceiveRemoteNotification:(NSDictionary *)userInfo 

        fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler

{

NSLog(@"Remote Notification userInfo is %@", userInfo);

NSNumber *contentID = userInfo[@"content-id"];

// 根據 content ID 進行操作

    completionHandler(UIBackgroundFetchResultNewData);

}

和後臺抓取一樣,應用程式進入後臺啟動,也有 30 秒的時間去獲取新內容並更新介面,最後呼叫完成的處理程式碼。我們可以像後臺獲取那樣,執行快速的網路請求,但我們可以使用新的強大的後臺傳輸服務,處理任務佇列,下面看看我們如何在任務完成後更新介面。

NSURLSession 和 後臺傳輸服務(Background Transfer Service)

NSURLSession 是 iOS 7 新增的一個新類,它也是 Foundation networking 中的新技術。作為 NSURLConnection 的替代品,一些熟悉的概念和類都保留下來了,例如 NSURLNSURLRequest 和 NSURLResponse。所以,你可以使用 NSURLSessionTask 這一NSURLConnection 的替代品,來處理網路請求及響應。一共有 3 種會話任務:資料,下載和上傳。每一種都向 NSURLSessionTask添加了語法糖,根據你的需要,適當選擇一種。

一個 NSURLSession 物件協調一個或多個 NSURLSessionTask 物件,並根據 NSURLSessionTask 建立的NSURLSessionConfiguration 實現不同的功能。使用相同的配置,你也可以建立多組具有相關任務的 NSURLSession 物件。要利用後臺傳輸服務,你將會使用 [NSURLSessionConfiguration backgroundSessionConfiguration] 來建立一個會話配置。新增到後臺會話的任務在外部程序執行,即使應用程式被掛起,崩潰,或者被殺死,它依然會執行。

NSURLSessionConfiguration 允許你設定預設的 HTTP 頭,配置快取策略,限制使用蜂窩資料等等。其中一個選項是discretionary 標誌,這個標誌允許系統為分配任務進行效能優化。這意味著只有當裝置有足夠電量時,裝置才通過 Wifi 進行資料傳輸。如果電量低,或者只僅有一個蜂窩連線,傳輸任務是不會執行的。後臺傳輸總是在 discretionary 模式下執行。

目前為止,我們大概瞭解了 NSURLSession,以及一個後臺會話如何進行,接下來,讓我們回到遠端通知的例子,新增一些程式碼來處理後臺傳輸服務的下載佇列。當下載完成後,我們會通知使用者該檔案已經可以使用了。

NSURLSessionDownloadTask

首先,我們先處理一條遠端通知,並把一個 NSURLSessionDownloadTask 新增到後臺傳輸服務的佇列。在 backgroundURLSession方法中,我們根據後臺會話配置,建立一個 NSURLSession 物件,並把 application delegate 作為會話的委託物件。文件不建議對於相同的識別符號 (identifier) 建立多個會話物件,所以我們使用 dispatch_once 來避免潛在的問題:

- (NSURLSession *)backgroundURLSession

{

staticNSURLSession *session = nil;

staticdispatch_once_t onceToken;

dispatch_once(&onceToken, ^{

NSString *identifier = @"io.objc.backgroundTransferExample";

        NSURLSessionConfiguration* sessionConfig = [NSURLSessionConfiguration backgroundSessionConfiguration:identifier];

        session = [NSURLSession sessionWithConfiguration:sessionConfig 

                                                delegate:self

                                           delegateQueue:[NSOperationQueue mainQueue]];

    });

return session;

}

- (void)           application:(UIApplication *)application 

  didReceiveRemoteNotification:(NSDictionary *)userInfo 

        fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler

{

NSLog(@"Received remote notification with userInfo %@", userInfo);

NSNumber *contentID = userInfo[@"content-id"];

NSString *downloadURLString = [NSString stringWithFormat:@"http://yourserver.com/downloads/%d.mp3", [contentID intValue]];

NSURL* downloadURL = [NSURL URLWithString:downloadURLString];

NSURLRequest *request = [NSURLRequest requestWithURL:downloadURL];

NSURLSessionDownloadTask