Keep Alive in Background

開始

在項(xiàng)目中遇上了一個(gè)需要常駐后臺(tái)并且輪詢Http的需求(不上App Store),所以整理下后臺(tái)常駐的方式.

iOS是偽多任務(wù)系統(tǒng),當(dāng)按下home鍵,app就會(huì)處于掛起狀態(tài),不執(zhí)行任何操作.不過(guò)很多情況下,這不是我們希望的,iOS提供了兩類后臺(tái)工作的方式:

  • 有限長(zhǎng)時(shí)間
  • 不限時(shí)間

通過(guò)這兩類方式,均可以實(shí)現(xiàn)常駐后臺(tái)的需求.

有限長(zhǎng)時(shí)間

有限長(zhǎng)時(shí)間,那么是多長(zhǎng)的時(shí)間呢:答案是180s(iOS9)
通過(guò)簡(jiǎn)單的代碼可以查看到

NSLog(@"%.1f",[UIApplication sharedApplication].backgroundTimeRemaining);
//=> 179.9s

也就是說(shuō),可以在向系統(tǒng)申請(qǐng)大約3分鐘的時(shí)間執(zhí)行自己的任務(wù).大約的意思就是不精確.事實(shí)上通過(guò)timer進(jìn)行計(jì)時(shí),在還剩下4s左右的時(shí)候,任務(wù)就已經(jīng)停止,開始執(zhí)行超時(shí)的收尾工作,app隨后被掛起.

做法很簡(jiǎn)單,不用做任何的設(shè)置.在applicationDidEnterBackground:中直接書寫代碼即可:

- (void)applicationDidEnterBackground:(UIApplication *)application {
    _counter = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(counter1) userInfo:nil repeats:YES];
    [[NSRunLoop currentRunLoop] addTimer:_counter forMode:NSDefaultRunLoopMode];
    [_counter fire];
}

- (void)counter1 {
    NSLog(@"%.1f",[UIApplication sharedApplication].backgroundTimeRemaining);
}

然后你會(huì)發(fā)現(xiàn),timer只會(huì)執(zhí)行一次...原因是,并沒(méi)有向系統(tǒng)申請(qǐng).加上申請(qǐng)權(quán)限即可:

- (void)applicationDidEnterBackground:(UIApplication *)application {
    __block UIBackgroundTaskIdentifier taskIdentifier = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
        [[UIApplication sharedApplication] endBackgroundTask:taskIdentifier];
        taskIdentifier = UIBackgroundTaskInvalid;
    }];
    
    _counter = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(counter1) userInfo:nil repeats:YES];
    [[NSRunLoop currentRunLoop] addTimer:_counter forMode:NSDefaultRunLoopMode];
    [_counter fire];
}

這樣,就會(huì)獲得大約180s的執(zhí)行時(shí)間.在時(shí)間完畢后,系統(tǒng)會(huì)執(zhí)行過(guò)期handler,進(jìn)行一些收尾工作,也就是beginBackgroundTaskWithExpirationHandler方法的參數(shù).

能否通過(guò)這種方式獲取更多的時(shí)間呢?能!

有兩種方式:

  1. 在過(guò)期handler里面再次begin,形成一個(gè)循環(huán),這樣的確能保證app不會(huì)被掛起.但在測(cè)試中,有線程混亂的現(xiàn)象(sleep(1)這個(gè)方法無(wú)法正常執(zhí)行).沒(méi)有深究,并沒(méi)有采用.
  2. 小技巧,往下看!

不限時(shí)間

提供了3種方式:

  1. GPS
  2. Audio
  3. VOIP

當(dāng)然,如果是提交App Store的話,采用某種方式就必須有相關(guān)的業(yè)務(wù)需求,不然會(huì)被拒.不過(guò)不上的話,那就沒(méi)關(guān)系~

使用GPS,和普通的位置服務(wù)一模一樣,沒(méi)有任何區(qū)別.只是在申請(qǐng)權(quán)限為永久而非應(yīng)用內(nèi).當(dāng)位置發(fā)生改變,iOS會(huì)喚醒a(bǔ)pp,進(jìn)入代理方法didUpdateLocations.

不過(guò)這似乎不符合需求,位置不變的情況下,app仍然處于掛起狀態(tài).

VOIP是最好的方式.不過(guò)需要server端的支持.
需要做3步操作:

  1. 打開VOIP服務(wù):在plist里面直接添加也行,在capabilities中的background modes中勾選更為簡(jiǎn)潔.
  2. 注冊(cè)VOIP(code from SRWebSocket).
   CFReadStreamRef readStream = NULL;
   CFWriteStreamRef writeStream = NULL;
   
   CFStreamCreatePairWithSocketToHost(NULL, (__bridge CFStringRef)host, port, &readStream, &writeStream);
   
   _outputStream = CFBridgingRelease(writeStream);
   _inputStream = CFBridgingRelease(readStream);
   
   [_inputStream setProperty:networkServiceType forKey:NSStreamNetworkServiceTypeVoIP];
   [_outputStream setProperty:networkServiceType forKey:NSStreamNetworkServiceTypeVoIP];

順便提下,square的SocketRocket本身支持VOIP,只需給urlRequest的networkServiceType 屬性設(shè)置為NSURLNetworkServiceTypeVoIP.如果是使用web socket的話,這個(gè)庫(kù)是一個(gè)很好的選擇.

3.在applicationDidEnterBackground中調(diào)用setKeepAliveTimeout:handler: 方法.該方法可以用來(lái)執(zhí)行ping/pong等操作.

使用socket的方式對(duì)于輪詢http的需求來(lái)講是最好的方案,通過(guò)配置VOIP來(lái)保持后臺(tái)常駐也是很好的方案.可惜需要server端的支持.為了趕需求只能后續(xù)采用:(.

最后來(lái)使用Audio吧

推薦使用AVQueuePlayer,它自帶了一個(gè)Timer類似的方法:addPeriodicTimeObserverForInterval.

- (void)setupPlayer {
    [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord withOptions:AVAudioSessionCategoryOptionMixWithOthers error:nil];
    NSURL *url = [[NSBundle mainBundle] URLForResource:@"song" withExtension:@"mp3"];
    AVPlayerItem *item = [[AVPlayerItem alloc] initWithURL:url];
    _player = [[AVQueuePlayer alloc] initWithPlayerItem:item];
    _player.volume = 0;
    __weak typeof(self) weakSelf = self;
    [_player addPeriodicTimeObserverForInterval:CMTimeMake(1, 1) queue:dispatch_get_main_queue() usingBlock:^(CMTime time) {
        ++count;
        [weakSelf infinityPlaying];
        if ([UIApplication sharedApplication].applicationState == UIApplicationStateBackground) {
            //do what you want to do
        }
    }];
    
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(dealWithInterrpution:) name:AVAudioSessionInterruptionNotification object:nil];
}

都是一些基礎(chǔ)的使用方式:

  • volume設(shè)為0,表示無(wú)聲;
  • count是一個(gè)static的值,起計(jì)數(shù)作用;
  • addPeriodicTimeObserverForInterval方法設(shè)置一個(gè)1秒左右的周期性執(zhí)行的block;
  • infinityPlaying這個(gè)方法會(huì)讓一首歌曲無(wú)限循環(huán).

如何處理呢?方式很簡(jiǎn)單.當(dāng)計(jì)數(shù)(count)達(dá)到一定的值的時(shí)候,player可以seekTime到0,從頭開始播放即可:

//static NSInteger MCInfinityCount = 50;

- (void)infinityPlaying {
    if (count == MCInfinityCount) {
        [_player seekToTime:kCMTimeZero];
        count = 0;
    }
}

這個(gè)值取多少呢?可以調(diào)整,取得小則seek的次數(shù)較多;取得大則意味著這首歌曲本身較大,占用的內(nèi)存多;最終根據(jù)實(shí)際情況取舍.

最后通過(guò)AVAudioSessionInterruptionNotification通知來(lái)處理打斷事件:notification的參數(shù)會(huì)表示打斷事件的begin和end.

注意,當(dāng)AVAudioSession的option如果不是AVAudioSessionCategoryOptionMixWithOthers的時(shí)候,處理打斷事件end時(shí),調(diào)用[_player play]無(wú)效,不會(huì)恢復(fù)播放,自然周期性執(zhí)行的block也不會(huì)恢復(fù).

但是這樣很費(fèi)電啊

這樣就等于一直在聽歌...

有沒(méi)有更好的方式呢?

也就是上面說(shuō)的一個(gè)小技巧.

通過(guò)beginBackgroundTaskWithExpirationHandler來(lái)注冊(cè)一個(gè)后臺(tái)有限長(zhǎng)時(shí)間任務(wù).

通過(guò)audio服務(wù)來(lái)刷新task的剩余時(shí)間(backgroundTimeRemaining),這樣組合則能同樣達(dá)到不限時(shí)間的效果.

首先準(zhǔn)備一個(gè)超短音頻,大約零點(diǎn)幾秒,我這里的音頻文件大小為7k.

然后同有限長(zhǎng)時(shí)間后臺(tái)任務(wù)一樣,沒(méi)有任何區(qū)別:

- (void)applicationDidEnterBackground:(UIApplication *)application {
    __block UIBackgroundTaskIdentifier taskIdentifier = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
        [[UIApplication sharedApplication] endBackgroundTask:taskIdentifier];
        taskIdentifier = UIBackgroundTaskInvalid;
    }];
    
    _timer = [NSTimer scheduledTimerWithTimeInterval:5 target:self selector:@selector(playeAudio) userInfo:nil repeats:YES];
    [[NSRunLoop currentRunLoop] addTimer:_timer forMode:NSDefaultRunLoopMode];
    [_timer fire];
    
    //do what you want to do
}

- (void)playAudio {
    NSLog(@"time remain:%.1f",[UIApplication sharedApplication].backgroundTimeRemaining);
    [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord withOptions:AVAudioSessionCategoryOptionMixWithOthers error:nil];
    NSURL *url = [[NSBundle mainBundle] URLForResource:@"mute" withExtension:@"wav"];
    [_player stop];
    _player = nil;
    _player = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:nil];
    [_player play];
}

因?yàn)槭欠浅6痰囊纛l文件,所以從生成一個(gè)player到加載音頻文件,到播放完畢,會(huì)在瞬間完成.對(duì)于資源的消耗非常少.也不存在費(fèi)電的問(wèn)題.

而當(dāng)audio播放的時(shí)候,后臺(tái)任務(wù)時(shí)間(backgroundTimeRemaining)是"無(wú)限"的.當(dāng)auido播放完畢的時(shí)候,后臺(tái)任務(wù)時(shí)間會(huì)持續(xù)5秒左右仍然"無(wú)限".隨后進(jìn)入倒計(jì)時(shí)狀態(tài).

MCChat[3167:1406329] time remain:179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368.0
MCChat[3167:1406329] time remain:179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368.0

在倒計(jì)時(shí)中,可以做任何事情.當(dāng)然也可以再次開啟一個(gè)audio服務(wù).再次開啟后,后臺(tái)任務(wù)時(shí)間會(huì)被刷新.

那么周期性的開啟重復(fù)操作,既能夠達(dá)到常駐后臺(tái)的目的,又能夠基本不費(fèi)電量.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市忙上,隨后出現(xiàn)的幾起案子术荤,更是在濱河造成了極大的恐慌丛晦,老刑警劉巖骨杂,帶你破解...
    沈念sama閱讀 218,525評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件迅脐,死亡現(xiàn)場(chǎng)離奇詭異娃肿,居然都是意外死亡罐栈,警方通過(guò)查閱死者的電腦和手機(jī)黍衙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,203評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)荠诬,“玉大人琅翻,你說(shuō)我怎么就攤上這事位仁。” “怎么了方椎?”我有些...
    開封第一講書人閱讀 164,862評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵聂抢,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我棠众,道長(zhǎng)琳疏,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,728評(píng)論 1 294
  • 正文 為了忘掉前任闸拿,我火速辦了婚禮空盼,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘新荤。我一直安慰自己揽趾,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,743評(píng)論 6 392
  • 文/花漫 我一把揭開白布苛骨。 她就那樣靜靜地躺著篱瞎,像睡著了一般。 火紅的嫁衣襯著肌膚如雪痒芝。 梳的紋絲不亂的頭發(fā)上俐筋,一...
    開封第一講書人閱讀 51,590評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音严衬,去河邊找鬼校哎。 笑死,一個(gè)胖子當(dāng)著我的面吹牛瞳步,可吹牛的內(nèi)容都是我干的闷哆。 我是一名探鬼主播,決...
    沈念sama閱讀 40,330評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼单起,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼抱怔!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起嘀倒,我...
    開封第一講書人閱讀 39,244評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤屈留,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后测蘑,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體灌危,經(jīng)...
    沈念sama閱讀 45,693評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,885評(píng)論 3 336
  • 正文 我和宋清朗相戀三年碳胳,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了勇蝙。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,001評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡挨约,死狀恐怖味混,靈堂內(nèi)的尸體忽然破棺而出产雹,到底是詐尸還是另有隱情,我是刑警寧澤翁锡,帶...
    沈念sama閱讀 35,723評(píng)論 5 346
  • 正文 年R本政府宣布蔓挖,位于F島的核電站,受9級(jí)特大地震影響馆衔,放射性物質(zhì)發(fā)生泄漏瘟判。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,343評(píng)論 3 330
  • 文/蒙蒙 一角溃、第九天 我趴在偏房一處隱蔽的房頂上張望荒适。 院中可真熱鬧,春花似錦开镣、人聲如沸刀诬。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,919評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)陕壹。三九已至,卻和暖如春树埠,著一層夾襖步出監(jiān)牢的瞬間糠馆,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,042評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工怎憋, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留又碌,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,191評(píng)論 3 370
  • 正文 我出身青樓绊袋,卻偏偏與公主長(zhǎng)得像毕匀,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子癌别,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,955評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • 文檔app在后臺(tái)時(shí)會(huì)被暫停皂岔,暫停的apps會(huì)提高電池的使用壽命,并且會(huì)讓系統(tǒng)將重要的系統(tǒng)資源投入到引起用戶注意的前...
    zziazm閱讀 4,610評(píng)論 0 5
  • IOS開發(fā)之----詳解在IOS后臺(tái)執(zhí)行 文一 我從蘋果文檔中得知展姐,一般的應(yīng)用在進(jìn)入后臺(tái)的時(shí)候可以獲取一定時(shí)間來(lái)...
    dongfang閱讀 1,383評(píng)論 0 7
  • 原文地址:http://dxjia.cn/2016/05/26/ios-background-executions...
    蘋果API搬運(yùn)工閱讀 863評(píng)論 0 4
  • 文一 我從蘋果文檔中得知躁垛,一般的應(yīng)用在進(jìn)入后臺(tái)的時(shí)候可以獲取一定時(shí)間來(lái)運(yùn)行相關(guān)任務(wù),也就是說(shuō)可以在后臺(tái)運(yùn)行一小段時(shí)...
    Kloar閱讀 1,482評(píng)論 0 1
  • 在iOS后臺(tái)執(zhí)行是本文要介紹的內(nèi)容圾笨,大多數(shù)應(yīng)用程序進(jìn)入后臺(tái)狀態(tài)不久后轉(zhuǎn)入暫停狀態(tài)教馆。在這種狀態(tài)下,應(yīng)用程序不執(zhí)行任何...
    我的馬里奧兄弟閱讀 828評(píng)論 0 0