iOS后臺任務(wù)beginBackgroundTaskWithExpirationHandler

1、標準寫法

UIBackgroundTaskIdentifier backgroundUpdateTask;

long aa;

NSTimer *_timer;

- (void) didEnterBackground:(NSNotification *)notif{

? ? aa = 0;

? ? [self startTask];

}

- (void) startTask {

? ? _timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(go:) userInfo:nil repeats:YES];

? ? backgroundUpdateTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{

? ? ? ? DDLogInfo(@"bgTask expiration=============");

? ? ? ? [_timer invalidate];

? ? ? ? [[UIApplication sharedApplication] endBackgroundTask:backgroundUpdateTask];

? ? ? ? backgroundUpdateTask = UIBackgroundTaskInvalid;

? ? }];

}

-(void)go:(NSTimer *)tim {

? ? DDLogInfo(@"%@==%ld,%g ",[NSDate date],aa,[UIApplication sharedApplication].backgroundTimeRemaining);

? ? aa++;

? ? if (aa%10 == 0 || [UIApplication sharedApplication].backgroundTimeRemaining == 0) {

? ? ? ? [LocalNotificationManager postLocalNotificationAlertBody:s];

? ? }

}

文檔上說有10分鐘的執(zhí)行時間,但從打印的backgroundTimeRemaining時間來看,只有180秒。

注意:測試此功能不能用Xcode直接debug運行,因為在調(diào)試器鏈接到app的進行的情況下,app是不會在后臺被掛起的,也就是說即使backgroundTimeRemaining =0了,timer里的代碼依然能夠繼續(xù)執(zhí)行。

所以要測試運行態(tài)的情況,要么用文件日志(總是要導(dǎo)出比較麻煩),要么用本地通知來查看。

2、是否能遞歸調(diào)用此方法來持續(xù)獲得執(zhí)行時間

在beginBackgroundTaskWithExpirationHandler里最后再遞歸調(diào)用[self startTask];

經(jīng)嘗試此方法無效,180秒超時后再次申請,會立刻回調(diào)超時的block,并且backgroundTimeRemaining時間一直都是0。

并且由于一直不停的在遞歸創(chuàng)建和終止后臺任務(wù),當(dāng)Expiration真正到來的時候,一個還有一個創(chuàng)建的任務(wù)沒有關(guān)閉。從而導(dǎo)致違背begin和end成對調(diào)用的原則,app被系統(tǒng)強制kill。所以此方法不但不能延長執(zhí)行時間,還會導(dǎo)致app在180秒后臺執(zhí)行時間到達后,被系統(tǒng)kill的情況。

3、beginBackgroundTaskWithExpirationHandler多次被調(diào)用的情況

didEnterBackground每次調(diào)用都會觸發(fā)beginBackgroundTaskWithExpirationHandler來創(chuàng)建新的后臺任務(wù),并用backgroundUpdateTask保存任務(wù)id,但如果第一次的任務(wù)還沒有endBackgroundTask之前,應(yīng)用回到前臺,然后再次進入后臺,就會重新創(chuàng)建一個新的后臺任務(wù),并且backgroundUpdateTask之前保存的id會被覆蓋,這就違背了beginBackgroundTaskWithExpirationHandler與endBackgroundTask成對調(diào)用的原因。因為前一個后臺任務(wù)超時的block回調(diào)的時候,其實是end了后一個taskId對應(yīng)的后臺任務(wù),并且把taskId賦值為UIBackgroundTaskInvalid。而后一個后臺任務(wù)超時的block回調(diào)的時候,taskId已經(jīng)變成了null,對其進行end調(diào)用已經(jīng)無效了,所以相當(dāng)于沒有成對調(diào)用begin和end,導(dǎo)致的結(jié)果就是:后一個后臺任務(wù)超時的時候,app被系統(tǒng)強制kill。

所以每一次創(chuàng)建的后臺任務(wù)都要有一個獨立的變量來維護其taskId,如果只有一個后臺任務(wù),但是有重入的可能,那么應(yīng)該在willEnterForeground回調(diào)中,把前一個后臺任務(wù)進行endBackgroundTask操作,這樣就不存在taskId被覆蓋的問題了?;蛘呤敲看蝑idEnterBackground的時候,檢查taskId == UIBackgroundTaskInvalid,若不滿足該條件,說明taskId已經(jīng)引用了一個正在進行的后臺任務(wù),還沒有完成,由于這個后臺任務(wù)重進前臺又切換回后臺的情況下,backgroundTimeRemaining會被重置為180秒,所以在這種需求下,關(guān)閉前一個任務(wù)再重新建議一個相同的后臺任務(wù)沒有必要,所以應(yīng)該直接

if(backgroundUpdateTask != UIBackgroundTaskInvalid){

? ? return;

}

4、后臺任務(wù)expiration后,app被系統(tǒng)kill的問題

按照文檔里的說法,只要begin與end在真正expiration之前成對調(diào)用,就不會導(dǎo)致系統(tǒng)強制kill app,而是app從后臺執(zhí)行狀態(tài)切換到suspend狀態(tài),但實際測試中,每次expiration之后,app都會被kill掉,根據(jù)是app從launch頁面重新進入。但我在willTerminate通知里的回調(diào)中加了一個local notification,并沒有觸發(fā)這個本地通知。(從app switcher強制退出應(yīng)用的時候會觸發(fā)本地通知,說明本地通知有效)。只能認為是app從后臺狀態(tài)切換到suspend狀態(tài)后,立刻被系統(tǒng)kill掉了,但不知道為什么會這樣。

5、參考另一個文章中的實現(xiàn),可以在任務(wù)結(jié)束后不被kill

參考http://www.cnblogs.com/lyanet/archive/2013/03/26/2983079.html

測試他這個寫法是可以在endTask以后,app變成suspend而不是被直接kill,但我沒找到跟前面寫法有什么本質(zhì)上的區(qū)別。

有三個不同點,依次排除一下。

① 在endTask里面把timer進行了invalidate處理。(測試無關(guān),注釋掉這部分代碼依然可以)

② taskId使用的是屬性而不是全局變量。(測試無關(guān),替換成全局變量依然可以)

③ 使用了application delegate里面的回調(diào),而不是notification center的通知。(把代碼從AppDelegate移動到Controller里面用通知來回調(diào)),竟然也好用。

把controller里的代碼回退到初始狀態(tài)再檢查,還是會被系統(tǒng)kill掉,完全找不到兩者之前有什么不同造成的。

最后,又恢復(fù)了。。感覺什么都沒改,怎么好的完全不知道。

找到原因了!?。?!

懷疑原因是某些其他地方開啟的beginBackgroundTask沒有被對應(yīng)的end掉,找到在引入環(huán)信的時候,要求在ApplicationDelegate里做如下處理:

- (void)applicationDidEnterBackground:(UIApplication *)application {

? ? [[EMClient sharedClient] applicationDidEnterBackground:application];

}

而在ApplicationDelegate里面begin和end正常是因為,用我寫的applicationDidEnterBackground替換到了上面這段。

并且在正常和非正常關(guān)閉的現(xiàn)象做對比,當(dāng)正常調(diào)用endTask的時候,Timer在收到Expiration的時候是會立刻被停止調(diào)用的。而異常的情況下Timer會繼續(xù)調(diào)用直到被系統(tǒng)kill。所以懷疑是環(huán)信引入的代碼沒有做對應(yīng)的begin和end操作。為了驗證這個分析,通過swizzling UIApplication的beginBackgroundTask方法進行測試。

- (UIBackgroundTaskIdentifier )swizzling1 {

? ? UIBackgroundTaskIdentifier taskId = [self swizzling1];

? ? DDLogDebug(@"enter beginBackgroundTaskWithExpirationHandler:%ld",taskId);

? ? return taskId;

}

- (void)swizzling2:(UIBackgroundTaskIdentifier) identifier {

? ? DDLogDebug(@"enter endBackgroundTask:%ld",identifier);

? ? [self swizzling2:identifier];

}

從日志結(jié)果看,begin了3次,id分別為1,4,6(6是我創(chuàng)建的)。end了4次,id分別是6,4,0,0。也就是說環(huán)信內(nèi)部在end的時候不但搞錯了對taskId的引用,很可能是用的同一個變量,創(chuàng)建id=4的時候覆蓋了id=1的,關(guān)閉id=4的時候成功了,并且將id設(shè)置為UIBackgroundTaskInvalid == 0。而對應(yīng)id=1的任務(wù)完成以后,關(guān)閉時執(zhí)行了endTask:0,沒有起到真正關(guān)閉的作用。于是再次等到真正expiration時再次關(guān)閉,依然是endTask:0,最終的結(jié)果就是還是沒有關(guān)閉。到達時限以后begin和end沒有成對調(diào)用,導(dǎo)致app被系統(tǒng)kill掉。

進一步研究,發(fā)現(xiàn)是環(huán)信的初始化中,在hyphenateApplication:didFinishLaunchingWithOptions:中已經(jīng)監(jiān)聽了didEnterBackground和willEnterForeground的事件,并做了后臺任務(wù)的處理,而application的對應(yīng)代理里面再寫一遍就會沖突,看來是文檔不同步造成的問題。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • IOS開發(fā)之----詳解在IOS后臺執(zhí)行 文一 我從蘋果文檔中得知,一般的應(yīng)用在進入后臺的時候可以獲取一定時間來...
    dongfang閱讀 1,474評論 0 7
  • 自從古老的iOS4以來,當(dāng)用戶點擊home建的時候,你可以使你的APP們在內(nèi)存中處于suspended(掛起)狀態(tài)...
    木易林1閱讀 3,363評論 1 4
  • 蘋果官網(wǎng)地址 Background Execution (后臺執(zhí)行)當(dāng)用于沒有-啟動應(yīng)用,系統(tǒng)移到后臺狀態(tài)。對于很...
    helinyu閱讀 8,059評論 0 9
  • 文檔app在后臺時會被暫停,暫停的apps會提高電池的使用壽命,并且會讓系統(tǒng)將重要的系統(tǒng)資源投入到引起用戶注意的前...
    zziazm閱讀 4,956評論 0 5
  • 文一 我從蘋果文檔中得知,一般的應(yīng)用在進入后臺的時候可以獲取一定時間來運行相關(guān)任務(wù),也就是說可以在后臺運行一小段時...
    Kloar閱讀 1,626評論 0 1

友情鏈接更多精彩內(nèi)容