在IOS後台執行是本文要介紹的內容,大多數應用程式進入後台狀態不久後轉入暫停狀態。在這種狀態下,應用程式不執行任何代碼,並有可能在任意時候從記憶體中刪除。應用程式提供特定的服務,使用者可以請求後台執行時間,以提供這些服務。
判斷是否支援多線程
UIDevice* device = [UIDevice currentDevice];
BOOL backgroundSupported = NO;
if ([device respondsToSelector:@selector(isMultitaskingSupported)])
backgroundSupported = device.multitaskingSupported;
聲明你需要的背景工作
Info.plist中添加UIBackgroundModes索引值,它包含一個或多個string的值,包括
audio:在後台提供聲音播放功能,包括音頻流和播放視頻時的聲音
location:在後台可以保持使用者的位置資訊
voip:在後台使用VOIP功能
前面的每個value讓系統知道你的應用程式應該在適當的時候被喚醒。例如,一個應用程式,開始播放音樂,然後移動到後台仍然需要執行時間,以填補音訊輸出緩衝區。添加audio鍵用來告訴系統架構,需要繼續播放音頻,並且可以在合適的時間間隔下回調應用程式;如果應用程式不包括此項,任何音頻播放在移到後台後將停止運行。
除了添加索引值的方法,IOS還提供了兩種途徑使應用程式在後台工作:
Task completion—應用程式可以向系統申請額外的時間去完成給定的任務
Local notifications—應用程式可以預先安排時間執行local notifications 傳遞
實現長時間的背景工作
應用程式可以請求在後台運行以實現特殊的服務。這些應用程式並不連續的運行,但是會被系統架構在合適的時間喚醒,以實現這些服務
1、 追蹤使用者位置:略
2、在背景播放音頻:
添加UIBackgroundModes中audio值,註冊後台音頻應用。這個值使得應用程式可以在後台使用可聽的背景,如音樂播放或者音頻流應用。對於支援音頻和視頻功能的應用程式也可以添加該值以保證可以繼續持續的運行流。
當audio值設定後,當你的應用程式進入後台後,系統的多媒體架構會自動阻止它被掛斷,但是,如果應用程式停止播放音頻或者視頻,系統將掛斷應用程式。
當你的應用程式在後台時,你可以執行任意的系統音頻架構去初始化後台音頻。你的應用程式在後台時應該限制自身,使其執行與工作相關的代碼,不能執行任何與播放內容無關的任務
由於有多個應用程式支援音頻,前台的應用程式始終允許播放音頻,背景應用程式也被允許播放一些音頻內容,這取決於audio session object的設定。應用程式應該始終設定它們的audio session object,並小心的處理其他類型的音頻相關notifications和中斷。詳見audio session programming guide。
3、實現VOIP應用:
VOIP程式需要穩定的網路去串連和它相關的服務,這樣它才能接到來電和其他相關的資料。系統允許VOIP程式被掛斷並提供組件去監聽它們的sockets,而不是在任意時候都處於喚醒狀態。設定VOIP應用程式如下:
A、 添加UIBackgroundModes中的VOIP索引值
B、 為VOIP設定一個應用程式socket
C、 在移出後台之前,調用setKeepAliveTimeout:handler:方法去建立一個定期執行的handler,你的應用程式可以運行這個handler來保持服務的串連。
D、 設定你的audio session去處理這種切換
釋義:
A、大多數VOIP應用需要設定後台audio 應用去傳遞音頻,因此你應該設定audio 和voip兩個索引值。
B、為了使應用程式在後台時保持穩定的串連,你必須tag你的主通訊socket專門應用於VOIP,tagging這個socket來告訴系統,它必須在你的應用程式中斷時接管這個socket。這個切換本身對於你的應用程式時透明的,當新的資料到達socket的時候,系統會喚醒應用程式,並將socket的控制權返回給應用程式,這樣應用程式就可以處理新來的資料。
你只需要tag用於voip服務的socket,這個socket用來接收來電或者其他相關的資料來保持你的VOIP服務的串連。根據收到的資訊,這個socket要決定下一步的動作。比如一個來電,你會想彈出一個本地的通知來告知使用者;對於其他不是那麼關鍵的資料,你可能會想悄悄的處理這些資料並讓系統將應用程式重新中斷。
在IOS中,sockets是用流或者更進階的結構,設定一個VOIP的socket,你只需要在通常的設定中添加一個特殊的key來標明這個介面是用於串連VOIP服務的,下表列出了流的介面和設定:
設定流介面用於voip
介面
設定
NSInputStream 和NSOutputStream
對於 Cocoa streams, 使用 setProperty:forKey: 方法添加
NSStreamNetworkServiceType
屬性給
stream.
改屬性的值設為
NSStreamNetworkServiceTypeVoIP.
NSURLRequest
對於 URL loading system, 使用 setNetworkServiceType:
method of your NSMutableURLRequest object to set the network service
type of the request. The service type should be set to
NSURLNetworkServiceTypeVoIP.
CFReadStreamRef和CFWriteStreamRef
For Core Foundation streams, use the CFReadStreamSetProperty or
CFWriteStreamSetProperty function to add the kCFStreamNetwork-
ServiceType property to the stream. The value for this property should be
set to kCFStreamNetworkServiceTypeVoIP.
(注意:當設定socket的時候,你需要在你的主訊號通道中設定合適的service type key。當設定聲道時,不需要設定這個key)
由於,VOIP應用程式需要一直運行以確保收到來電,所以如果程式通過一個非零的exit code退出,系統將自動重啟這個應用程式(這種退出方式可以發生在記憶體壓力大時終止程式運行)。儘管如此,中斷應用程式會release所有的sockets,包括那個用於串連voip 服務的socket。因此,當程式運行時,它需要一直從頭建立socket。
C、 為了防止斷連,voip程式需要定期被喚醒去檢查它的服務。為了容易實現這個行為,IOS通過使用(UIApplication setKeepAliveTimeout:handler:)方法建立一個特殊的控制代碼。你可以在applicationDidEnterBackground方法中建立該控制代碼。一旦建立,系統至少會在逾時之前調用該控制代碼一次,來喚醒你的應用程式。
這個keep-alive handler在後台執行,必須儘快的返回參數,它有最多30秒的時間來執行所需的任務,如果這段時間內控制代碼沒有返回,那麼系統將終止應用程式。
當你建立了handler之後,確定應用程式所需的最大逾時。系統保證會在最大逾時之前調用handler,但是這個時間是不確定的,所以你的handler必須在你申明的逾時之前做好執行程式的準備。
D、設定audio session,詳見Audio Session Programming Guide.
在後台完成有限長度的任務
在被終止之前的任意時間,應用程式會調用beginBackgroundTaskWithExpirationHandler:方法讓系統給出額外的時間來完成一些需要在後台長時間執行的任務。(UIApplication的backgroundTimeRemaining屬性包含程式啟動並執行總時間)
可以使用task completion去保證那些比較重要但是需要長時間啟動並執行程式不會由於使用者切入後台而突然關閉。比如,你可以用這項功能來將使用者的資訊儲存到disk上或者從網路下載一個重要的檔案。有兩種方式來初始化這樣的任務:
1、將長時間啟動並執行重要任務用beginBackgroundTaskWithExpirationHandler:和endBackgroundTask:封裝。這樣就在程式突然切入背景時候保護了這些任務不被中斷。
2、當你的應用程式委託applicationDidEnterBackground:方法被調用時再啟動任務
中的兩個方法必須是一一對應的,endBackgroundTask:方法告訴系統任務已經完成,程式在此時可以被終止。由於應用程式只有有限的時間去完成背景工作,你必須在逾時或系統將要終止這個程式之前調用這個方法。為了避免被終止,你也可以在一個任務開始的時候提供一個expiration handler和endBackgroundTask:方法。(可以查看backgroundTimeRemaining屬性來確定還剩多少時間)。
一個程式可以同時提供多個任務,每當你啟動一個任務的時候,beginBackgroundTaskWithExpirationHandler:方法將返回一個獨一無二的handler去識別這個任務。你必須在endBackgroundTask:方法中傳遞相同的handler來終止該任務。
Listing 4-2 Starting a background task at quit time
- (void)applicationDidEnterBackground:(UIApplication *)application
{
UIApplication* app = [UIApplication sharedApplication];
bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
[app endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
// Start the long-running task and return immediately.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,
0), ^{
// Do the work associated with the task.
[app endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
});
}
上述例子中,bgTask變數是一個類的成員變數,儲存著指向該背景工作標示的指標。
在expriation handler中,可以添加關閉任務所需的代碼。儘管如此,加入的代碼不能執行太長的時間,當expriation handler被調用的時候,該程式已經非常接近被關閉,所以只有極短的時間來清除狀態資訊並終止任務。
安排Local Notification的傳遞
UILocalNotification類提供了一種方法來傳遞local notifications。和push notifications需要設定remote server不同,local notifications 在程式中安排並在當前的裝置上執行。滿足如下條件可以使用該能力:
1、一個基於時間的程式,可以在將來特定的時間讓程式post 一個alert,比如鬧鐘
2、一個在後台啟動並執行程式,post 一個local notification去引起使用者的注意
為了安排local notification 的傳遞,需要建立一個UILocalNotification的執行個體,並設定它,使用UIApplication類方法來安排它。Local notification對象包含了所要傳遞的類型(sound,alert,或者badge)和時間何時呈現) 。UIApplication類方法提供選項去確定是立即傳遞還是在指定的時間傳遞。
Listing 4-3 Scheduling an alarm notification
- (void)scheduleAlarmForDate:(NSDate*)theDate
{
UIApplication* app = [UIApplication sharedApplication];
NSArray* oldNotifications = [app scheduledLocalNotifications];
// Clear out the old notification before scheduling a new one.
if ([oldNotifications count] > 0)
[app cancelAllLocalNotifications];
// Create a new notification.
UILocalNotification* alarm = [[[UILocalNotification alloc] init] autorelease];
if (alarm)
{
alarm.fireDate = theDate;
alarm.timeZone = [NSTimeZone defaultTimeZone];
alarm.repeatInterval = 0;
alarm.soundName = @"alarmsound.caf";
alarm.alertBody = @"Time to wake up!";
[app scheduleLocalNotification:alarm];
}
}
(可以最多包含128個 local notifications active at any given time, any of which can be configured to repeat at a specified interval.)如果在調用該notification的時候,程式已經處於前台,那麼application:didReceiveLocalNotification:方法將取而代之。
下面轉載kmyhy老師的一篇文章
根據蘋果文檔中關於後台執行的描述,任何app都有10分鐘左右的背景工作執行時間。 10分鐘後,app會被iOS強行掛起。
但是,有5類app允許有“無限的”後台已耗用時間:
1. Audio。
2. Location/GPS。
3. VoIP。
4. Newsstand。
5. Exernal Accessory 。
你可以將任何app聲明為上述5種類型以獲得無限的後台已耗用時間,但當你提交app到App Store時,蘋果會審查你的app,一旦發現你“濫用”了後台API,你的app將被拒絕。
當然,對於企業開發而言,不存在“濫用”的問題——企業app可以通過OTA部署,不經過蘋果商店審查。
在企業部署中,你可以將一個app聲明為VoIP,但這個程式根本和VoIP無關,我們的目的只是為了讓iOS給我們無限後台執行的許可權。聲明過程是在app的Info.plist檔案中加入以下key:
<key>UIBackgroundModes</key>
<array>
<string>voip</string>
</array>
我測試了以下代碼:
- (void)backgroundHandler {
NSLog(@"### -->backgroundinghandler");
UIApplication* app = [UIApplicationsharedApplication];
bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
[app endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
// Start the long-running task
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,
0), ^{
while (1) {
NSLog(@"counter:%ld", counter++);
sleep(1);
}
});
}
- (void)applicationDidEnterBackground:(UIApplication *)application
{
BOOL backgroundAccepted = [[UIApplicationsharedApplication] setKeepAliveTimeout:600handler:^{
[selfbackgroundHandler]; }];
if (backgroundAccepted)
{
NSLog(@"backgrounding accepted");
}
[selfbackgroundHandler];
}