12.2.2. 方案
使用UIApplication的beginBackgroundTaskWithExpirationHandler: 執行個體方法。在你完成任務後,調用UIApplication的endBackgroundTask:方法。
12.2.3. 討論
當一個iOS應用被送到後台,它的主線程會被暫停。你用NSThread的detachNewThreadSelector:toTar get:withObject:類方法建立的線程也被掛起了。如果你想在後台完成一個長期任務,就必須調用UIApplication的beginBackgroundTaskWithExpirationHandler:執行個體方法,來向iOS借點時間。UIApplication的backgroundTimeRemaining屬性包含了程式完成他的任務可以使用的秒數。如果在這個期限內,長期任務沒有被完成,iOS將終止程式。每個對beginBackgroundTaskWithExpirationHandler:方法的調用,必須要相應的調用endBackgroundTask:方法(UIApplication的另一個執行個體方法)。也就是說,如果你向iOS要更多時間來完成一個任務,你必須告訴iOS你什麼時候能完成那個任務,那時,你的程式將iOS 5 Programming Cookbook www.devdiv.com 翻譯整理
DevDiv 翻譯:kyelup cloudhsu 耐心摩卡 wangli2003j3 xiebaochun dymx101 jimmylianf BeyondVincent 20 DevDiv 校對:laigb kyelup DevDiv 編輯:BeyondVincent 版本 1.0 | 2012 年 07 月 30 日
和其所有被暫停線程被放入後台。
當你的程式在前台時,UIApplication的backgroundTimeRemaining屬性等於DBL_MAX常量,這是double類型可表示的最大值(和這個值相當的integer通常等於-1)。在iOS被要求在程式被完全掛起之前給於更多的執行時間,這個屬性指明了在完成任務前程式擁有多少秒。
在程式中你可以多次調用beginBackgroundTaskWithExpirationHandler:方法。要記住的重點是,當iOS為你的程式返回一個token或者任務標識(task identifier)時,你都必須調用endBackgroundTask:方法,在啟動並執行任務結束時,用來標誌任務結束。如果你不這麼做的話,iOS會終止你的程式。
在後台時,程式不應該執行完全的功能,也不應該處理大量資料。事實上,他們只應該完成一個長期任務。
比如,一個程式正在調用一個web service API,並且還沒有從伺服器上的那個API接收到響應。在此期間,如果程式被送入後台,它可以請求更多的時間,直到它從伺服器收到響應。一旦響應被接收,程式必須儲存其狀態,並調用UIApplication的endBackgroundTask:執行個體方法將任務標記為完成。
讓我們看一個例子。我將從在應用程式委託中定義一個UIBackgroundTaskIdentifier類型的屬性開始。同時,讓我們定義一個NSTimer,當程式被送到後台時,我們將用它每隔1秒向控制台視窗輸出一條訊息:
#import <UIKit/UIKit.h>
@interface Completing_a_Long_Running_Task_in_the_BackgroundAppDelegate : UIResponder <UIApplicationDelegate>
@property (nonatomic, strong) UIWindow *window;
@property (nonatomic, unsafe_unretained) UIBackgroundTaskIdentifier backgroundTaskIdentifier;
@property (nonatomic, strong) NSTimer *myTimer;
@end
接下來我們繼續同步屬性:
#import "Completing_a_Long_Running_Task_in_the_BackgroundAppDelegate.h" @implementation Completing_a_Long_Running_Task_in_the_BackgroundAppDelegate
@synthesize window = _window;
@synthesize backgroundTaskIdentifier; @synthesize myTimer;
現在,讓我們建立定時器,並在程式被送到後台時啟動它:
- (BOOL) isMultitaskingSupported{
BOOL result = NO;
if ([[UIDevice currentDevice]
respondsToSelector:@selector(isMultitaskingSupported)]){ result = [[UIDevice currentDevice] isMultitaskingSupported];
}
return result;
}
- (void) timerMethod:(NSTimer *)paramSender{
NSTimeInterval backgroundTimeRemaining =
[[UIApplication sharedApplication] backgroundTimeRemaining];
if (backgroundTimeRemaining == DBL_MAX){ NSLog(@"Background Time Remaining = Undetermined");
} else { iOS 5 Programming Cookbook www.devdiv.com 翻譯整理
DevDiv 翻譯:kyelup cloudhsu 耐心摩卡 wangli2003j3 xiebaochun dymx101 jimmylianf BeyondVincent 21 DevDiv 校對:laigb kyelup DevDiv 編輯:BeyondVincent 版本 1.0 | 2012 年 07 月 30 日
NSLog(@"Background Time Remaining = %.02f Seconds",
backgroundTimeRemaining);
} }
- (void)applicationDidEnterBackground:(UIApplication *)application{
if ([self isMultitaskingSupported] == NO){
return; }
self.myTimer =
[NSTimer scheduledTimerWithTimeInterval:1.0f
target:self
selector:@selector(timerMethod:) userInfo:nil
repeats:YES];
self.backgroundTaskIdentifier =
[application beginBackgroundTaskWithExpirationHandler:^(void) { [self endBackgroundTask];
}]; }
你可以看到,在背景工作的完成處理者(completion handler)中,我們調用了應用程式委託的endBackgroundTask方法。這是一個我們編寫的方法,如下:
- (void) endBackgroundTask{
dispatch_queue_t mainQueue = dispatch_get_main_queue();
__weak Completing_a_Long_Running_Task_in_the_BackgroundAppDelegate *weakSelf = self;
dispatch_async(mainQueue, ^(void) {
Completing_a_Long_Running_Task_in_the_BackgroundAppDelegate *strongSelf = weakSelf;
if (strongSelf != nil){
[strongSelf.myTimer invalidate];
[[UIApplication sharedApplication] endBackgroundTask:self.backgroundTaskIdentifier];
strongSelf.backgroundTaskIdentifier = UIBackgroundTaskInvalid; }
}); }
在長期任務結束後,我們需要做一些事情進行清理:
1. 結束所有的線程和定時器,不管他們是基礎定時器還是GCD中建立的。
2.調用UIApplication的endBackgroundTask:方法來結束背景工作。
3.將任務標識設定為UIBackgroundTaskInvalid,標誌我們的任務結束。
最後,當我們的應用回到前台,如果我們的背景工作還在執行中,我們需要確保我們在幹掉它:
- (void)applicationWillEnterForeground:(UIApplication *)application{
if (self.backgroundTaskIdentifier != UIBackgroundTaskInvalid){
[self endBackgroundTask]; }
}
在我們的例子中,不論何時程式被送到後台,我們都會要求更多時間以完成一個長期任務(例如,在這裡是我們計時器的代碼)。在我們的時間裡,我們不斷的讀取UIApplication執行個體中backgroundTimeRemaining屬性的值,將它列印到控制台。在UIApplication的beginBackgroundTask WithExpirationHandler: 執行個體方法中,在程式的額外時間內完成一個長期任務之前,我們提供的代碼將被執行(一版大概在任務到期前5到10秒)。在此,我們只要調用UIApplication的endBackgroundTask:執行個體方法來結束任務。