[iOS]關(guān)于iOS后臺長時間掛起的方法


補(bǔ)充一下:首先表述一下我個人的觀點,對于后臺開啟audio開關(guān)的問題,不是蘋果允許也颤,盡可能避免使用一些取巧的方式,最好能按照官方規(guī)定郁轻。如果很有必要翅娶,首先你得說服蘋果的審核人員。個人血淋淋的教訓(xùn)好唯,有一個項目就是需要在后臺長時間播放語音竭沫,不是音樂類或?qū)Ш筋惸欠N,在每次收到推送骑篙,在后臺播放推送內(nèi)容TTS合成的語音蜕提,內(nèi)容基本上不會相同,所以放棄使用固定語音文件方式靶端。出了一兩個版本都神不知鬼不覺的過審贯溅,以為這樣會高枕無憂,沒想到到最后還是被發(fā)現(xiàn)躲查,然后蘋果打電話,很顯然译柏,我沒有說過他镣煮,可能我的理由沒準(zhǔn)備好吧。又試了幾次其他方式鄙麦,雖然也有過了的情況典唇,但考慮到應(yīng)用的正規(guī)性和后續(xù)維護(hù)成本,只好換成了其他計劃胯府。好吧介衔,就這樣,下面講一些后臺長時間運(yùn)行的方法骂因,僅供參考炎咖。


蘋果限制App在后臺運(yùn)行,是為了減少系統(tǒng)對當(dāng)前硬件的開銷,可以更高效的使用App乘盼,駐足后臺升熊,肯定會占用系統(tǒng)資源。為了讓設(shè)備盡量省電绸栅,減少不必要的開銷级野,保持系統(tǒng)流暢,因而對后臺機(jī)制采用墓碑式的“假后臺”粹胯。除了系統(tǒng)官方極少數(shù)程序可以真后臺蓖柔,一般開發(fā)者開發(fā)出來的應(yīng)用程序后臺受到以下限制:

  • 1.用戶按Home之后,App轉(zhuǎn)入后臺進(jìn)行運(yùn)行风纠,此時擁有180s后臺時間(iOS7)或者600s(iOS6)運(yùn)行時間可以處理后臺操作

  • 2.當(dāng)180S或者600S時間過去后况鸣,從 iOS 4 開始,我們可以告知系統(tǒng)有未完成任務(wù)议忽,需要申請繼續(xù)完成懒闷,系統(tǒng)批準(zhǔn)申請之后,可以繼續(xù)運(yùn)行栈幸,但總時間還是不會超過10分鐘愤估,下面是申請后臺的方法。

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

    __block UIBackgroundTaskIdentifier background_task;
    //注冊一個后臺任務(wù)速址,告訴系統(tǒng)我們需要向系統(tǒng)借一些事件
    _bgTaskId = [application beginBackgroundTaskWithExpirationHandler:^ {
        //不管有沒有完成玩焰,結(jié)束background_task任務(wù)
        [application endBackgroundTask: background_task];
        background_task = UIBackgroundTaskInvalid;
    }];
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"Running in the background\n");
        while(true){
             sleep(1000);
              //NSLog(@"Background time Remaining: %f",[[UIApplication sharedApplication] backgroundTimeRemaining]);
             if ([UIApplication sharedApplication].applicationState == UIApplicationStateActive) {
                 NSLog(@"---------  App處于前臺  ---------");
                 //我們自己完成任務(wù)后,結(jié)束background_task任務(wù)
                 [application endBackgroundTask: background_task];
                 background_task = UIBackgroundTaskInvalid;
                 return;
            }
        }

    });
}

  • 3.當(dāng)10分鐘時間到之后芍锚,無論怎么向系統(tǒng)申請繼續(xù)后臺昔园,系統(tǒng)都會強(qiáng)制掛起你的App,掛起所有后臺操作并炮、線程默刚,直到用戶再次點擊App之后才會繼續(xù)運(yùn)行。

當(dāng)然iOS為了特殊應(yīng)用也保留了一些可以實現(xiàn)“真后臺”的方法逃魄,摘取比較常用的如:

  • 1.VoIP . 后臺語音服務(wù)荤西,類似Skype通話應(yīng)用需要調(diào)用,可進(jìn)行后臺的語音通話
  • 2.定位服務(wù) . 下面會講到
  • 3.后臺下載 . 如Newsstand伍俘,iOS5后多了一種類型,報刊雜志后臺自動下載更新邪锌,其能夠自動實時更新,iOS 7 則可以下載各種玩意和定時抓取
  • 4.在后臺一直播放無聲音樂 . 這是后臺的音頻癌瘾,這個很早之前便有觅丰,也是iOS設(shè)備中用得最多的后臺應(yīng)用,調(diào)用這個接口可以實現(xiàn)后臺的音樂播放妨退,審核也不容易被發(fā)現(xiàn)妇萄。但是容易受到電話或者其他程序影響, 僅憑自己取舍蜕企。

其中VoIP需要綁定一個Socket鏈接并申明給系統(tǒng),系統(tǒng)將會在后臺接管這個連接嚣伐,
一旦遠(yuǎn)端數(shù)據(jù)過來糖赔,你的App將會被喚醒10s(或者更少)的時間來處理數(shù)據(jù),超過時間或者處理完畢轩端,程序繼續(xù)休眠放典。VOIP對服務(wù)端改動太大,選擇需符合項目需求基茵,下面介紹兩種方式供參考奋构。

<h3>一、后臺處理多媒體時間</h3>


  • 首先需要在Capabilities -Background Modes打開選擇Audio或在plist中配置Required background modes -App plays audio or streams audio/video using AirPlay
  • 開啟后臺處理多媒體session
  • 設(shè)置后臺任務(wù)
  • [ 重要] 有很多例子采用播放無聲音樂的方式拱层,但愿沒有被發(fā)現(xiàn)弥臼。 要給水果粑粑說明你需要后臺播放的原因,比如說你有后臺播放的實體等根灯,如若被拒径缅,電話討論時,你要能夠說服他
-(void)applicationWillResignActive:(UIApplication* )application{
    
    //開啟后臺處理多媒體事件
    [[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
    AVAudioSession *session=[AVAudioSession sharedInstance];
    [session setActive:YES error:nil];
    //后臺播放
    [session setCategory:AVAudioSessionCategoryPlayback error:nil];
    //這樣做烙肺,可以在按home鍵進(jìn)入后臺后 纳猪,播放一段時間,600s吧桃笙。但是不能持續(xù)播放網(wǎng)絡(luò)歌曲氏堤,若需要持續(xù)播放網(wǎng)絡(luò)歌曲,還需要申請后臺任務(wù)id搏明,具體做法是:
    _bgTaskId=[AppDelegate backgroundPlayerID:_bgTaskId];
    //其中的_bgTaskId是后臺任務(wù)UIBackgroundTaskIdentifier _bgTaskId;
}

//實現(xiàn)一下backgroundPlayerID:這個方法:
+(UIBackgroundTaskIdentifier)backgroundPlayerID:(UIBackgroundTaskIdentifier)backTaskId{
    
    //設(shè)置并激活音頻會話類別
    AVAudioSession *session=[AVAudioSession sharedInstance];
    [session setCategory:AVAudioSessionCategoryPlayback error:nil];
    [session setActive:YES error:nil];
    
    //允許應(yīng)用程序接收遠(yuǎn)程控制
    [[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
    //設(shè)置后臺任務(wù)ID
    UIBackgroundTaskIdentifier newTaskId=UIBackgroundTaskInvalid;
    newTaskId=[[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:nil];
    if(newTaskId!=UIBackgroundTaskInvalid&&backTaskId!=UIBackgroundTaskInvalid)
    {
        [[UIApplication sharedApplication] endBackgroundTask:backTaskId];
    }
    return newTaskId;
}


<h3>二鼠锈、后臺定位服務(wù)</h3>

  • 首先在info.plist (Privacy - Location Always Usage Description)中加上描述申明其必要性,如:GPS在后臺持續(xù)運(yùn)行星著,會降低電池的壽命
  • [重要] 備注里跟蘋果解釋清楚用后臺定位的地方和原因购笆,最好說明App里有有關(guān)需求,如地圖軌跡定位等

要啟動定位服務(wù):

  • 1.需要引入頭文件:#import <CoreLocation/CoreLocation.h>
  • 2.在AppDelegate.m中定義CLLocationManager * locationManager;作為全局變量方便控制
  • 3.在程序啟動初期對定位服務(wù)進(jìn)行初始化:
locationManager  = [[CLLocationManager alloc] init];
locationManager.delegate = self;
  • 4.在程序轉(zhuǎn)入后臺的時候虚循,啟動定位服務(wù)
    [locationManager startUpdatingLocation];(第一次運(yùn)行這個方法的時候由桌,如果之前用戶沒有使用過App,則會彈出是否允許位置服務(wù)邮丰,關(guān)于用戶是否允許,后面代碼中有判斷)
    這樣在定位服務(wù)可用的時候铭乾,程序會不斷刷新后臺時間剪廉,實際測試,發(fā)現(xiàn)后臺180s時間不斷被刷新炕檩,達(dá)到長久后臺的目的斗蒋。

但是這樣使用也有一些問題捌斧,在部分機(jī)器上面,定位服務(wù)即使打開也可能不能刷新后臺時間泉沾,需要完全結(jié)束程序再運(yùn)行捞蚂。穩(wěn)定性不知道是因為代碼原因還是系統(tǒng)某些機(jī)制原因。
代碼:
判斷用戶是否打開了定位服務(wù)跷究,是否禁用了該程序的定位權(quán)限:

if(![CLLocationManager
 locationServicesEnabled] || ([CLLocationManager authorizationStatus] == kCLAuthorizationStatusDenied))//判斷定位服務(wù)是否打開
    {
        [InterfaceFuncation
 ShowAlertWithMessage:@"錯誤" AlertMessage:@"定位服務(wù)未打開\n保持在線需要后臺定位服務(wù)\n請到
 設(shè)置-隱私 中打開定位服務(wù)" ButtonTitle:@"我錯了"];
        return;
    }

AppDelegate.m源碼:

@property (assign,
nonatomic)
 UIBackgroundTaskIdentifier bgTask;
 
@property (strong,
nonatomic)
 dispatch_block_t expirationHandler;
@property (assign,
nonatomic)
BOOL jobExpired;
@property (assign,
nonatomic)
BOOL background;
- (BOOL)application:(UIApplication
 *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
 
 UIApplication*
 app = [UIApplication sharedApplication];
 
 __weak
NSUAAAIOSAppDelegate*
 selfRef = self;
 
 self.expirationHandler
 = ^{  //創(chuàng)建后臺自喚醒姓迅,當(dāng)180s時間結(jié)束的時候系統(tǒng)會調(diào)用這里面的方法
 [app
 endBackgroundTask:selfRef.bgTask];
 selfRef.bgTask
 = UIBackgroundTaskInvalid;
 selfRef.bgTask
 = [app beginBackgroundTaskWithExpirationHandler:selfRef.expirationHandler];
 NSLog(@"Expired");
 selfRef.jobExpired
 = YES;
 while(selfRef.jobExpired)
 {
 //
 spin while we wait for the task to actually end.
 NSLog(@"等待180s循環(huán)進(jìn)程的結(jié)束");
 [NSThread sleepForTimeInterval:1];
 }
 //
 Restart the background task so we can run forever.
 [selfRef
 startBackgroundTask];
 };
 
 //
 Assume that we're in background at first since we get no notification from device that we're in background when
 //
 app launches immediately into background (i.e. when powering on the device or when the app is killed and restarted)
 [self monitorBatteryStateInBackground];
 locationManager
 = [[CLLocationManager alloc] init];
 locationManager.delegate
 = self;
 //[locationManager
 startUpdatingLocation];
 return YES;
}
 
- (void)monitorBatteryStateInBackground
{
 self.background
 = YES;
 [self startBackgroundTask];
}
 
- (void)applicationDidBecomeActive:(UIApplication
 *)application
{
 //
 Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
 NSLog(@"App
 is active");
 [UIApplication
 sharedApplication].applicationIconBadgeNumber=0;//取消應(yīng)用程序通知腳標(biāo)
 [locationManager
 stopUpdatingLocation];
 self.background
 = NO;
}
 
- (void)applicationDidEnterBackground:(UIApplication
 *)application
{
 //
 Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
 //
 If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
 //if([self
 bgTask])
 if(isLogined)//當(dāng)?shù)顷憼顟B(tài)才啟動后臺操作
 {
 self.bgTask
 = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:self.expirationHandler];
 NSLog(@"Entered
 background");
 [self monitorBatteryStateInBackground];
 }
}
 
-  (void)locationManager:(CLLocationManager
 *)manager didFailWithError:(NSError *)error//當(dāng)定位服務(wù)不可用出錯時,系統(tǒng)會自動調(diào)用該函數(shù)
{
 NSLog(@"定位服務(wù)出錯");
 if([error
 code]==kCLErrorDenied)//通過error的code來判斷錯誤類型
 {
 //Access
 denied by user
 NSLog(@"定位服務(wù)未打開");
 [InterfaceFuncation
 ShowAlertWithMessage:@"錯誤" AlertMessage:@"未開啟定位服務(wù)\n客戶端保持后臺功能需要調(diào)用系統(tǒng)的位置服務(wù)\n請到設(shè)置中打開位置服務(wù)" ButtonTitle:@"好"];
 }
}
 
-  (void)locationManager:(CLLocationManager
 *)manager didUpdateLocations:(NSArray *)locations//當(dāng)用戶位置改變時俊马,系統(tǒng)會自動調(diào)用丁存,這里必須寫一點兒代碼,否則后臺時間刷新不管用
{
 NSLog(@"位置改變柴我,必須做點兒事情才能刷新后臺時間");
 CLLocation
 *loc = [locations lastObject];
 //NSTimeInterval
 backgroundTimeRemaining = [[UIApplication sharedApplication] backgroundTimeRemaining];
 //NSLog(@"Background
 Time Remaining = %.02f Seconds",backgroundTimeRemaining);
 //
 Lat/Lon
 float latitudeMe
 = loc.coordinate.latitude;
 float longitudeMe
 = loc.coordinate.longitude;
}
 
- (void)startBackgroundTask
{
 NSLog(@"Restarting
 task");
 if(isLogined)//當(dāng)?shù)顷憼顟B(tài)才進(jìn)入后臺循環(huán)
 {
 //
 Start the long-running task.
    NSLog(@"登錄狀態(tài)后臺進(jìn)程開啟");
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,
 0), ^{
 //
 When the job expires it still keeps running since we never exited it. Thus have the expiration handler
 //
 set a flag that the job expired and use that to exit the while loop and end the task.
    NSInteger count=0;
    BOOL NoticeNoBackground=false;//只通知一次標(biāo)志位
    BOOL FlushBackgroundTime=false;//只通知一次標(biāo)志位
    locationManager.distanceFilter
 = kCLDistanceFilterNone;//任何運(yùn)動均接受解寝,任何運(yùn)動將會觸發(fā)定位更新
    locationManager.desiredAccuracy
 = kCLLocationAccuracyHundredMeters;//定位精度
    while(self.background
 && !self.jobExpired)
    {
       NSLog(@"進(jìn)入后臺進(jìn)程循環(huán)");
       [NSThread sleepForTimeInterval:1];
       count++;
       if(count>60)//每60s進(jìn)行一次開啟定位,刷新后臺時間
       {
          count=0;
          [locationManager
 startUpdatingLocation];
          NSLog(@"開始位置服務(wù)");
          [NSThread sleepForTimeInterval:1];
          [locationManager
 stopUpdatingLocation];
          NSLog(@"停止位置服務(wù)");
          FlushBackgroundTime=false;
       }
       if(!isLogined)//未登錄或者掉線狀態(tài)下關(guān)閉后臺
       {
          NSLog(@"保持在線進(jìn)程失效艘儒,退出后臺進(jìn)程");
          [InterfaceFuncation
 ShowLocalNotification:@"保持在線失效聋伦,登錄已被注銷,請重新登錄"];
          [[UIApplication
 sharedApplication] endBackgroundTask:self.bgTask];
          return;//退出循環(huán)
       }
       NSTimeInterval backgroundTimeRemaining
 = [[UIApplication sharedApplication] backgroundTimeRemaining];
       NSLog(@"Background
 Time Remaining = %.02f Seconds",backgroundTimeRemaining);
       if(backgroundTimeRemaining<30&&NoticeNoBackground==false)
       {
          [InterfaceFuncation
 ShowLocalNotification:@"向系統(tǒng)申請長時間保持后臺失敗界睁,請結(jié)束客戶端重新登錄"];
          NoticeNoBackground=true;
    }
    //測試后臺時間刷新
       if(backgroundTimeRemaining>200&&FlushBackgroundTime==false)
       {
          [[NSNotificationCenter defaultCenter]
 postNotificationName:@"MessageUpdate" object:@"刷新后臺時間成功\n"];
          FlushBackgroundTime=true;
          //[InterfaceFuncation
 ShowLocalNotification:@"刷新后臺時間成功"];
       }
    }
    self.jobExpired
 = NO;
    });
 }
}

最后觉增,忠誠的祝愿大家都能過審~


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市晕窑,隨后出現(xiàn)的幾起案子抑片,更是在濱河造成了極大的恐慌,老刑警劉巖杨赤,帶你破解...
    沈念sama閱讀 212,884評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件敞斋,死亡現(xiàn)場離奇詭異,居然都是意外死亡疾牲,警方通過查閱死者的電腦和手機(jī)植捎,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,755評論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來阳柔,“玉大人焰枢,你說我怎么就攤上這事∩嗉粒” “怎么了济锄?”我有些...
    開封第一講書人閱讀 158,369評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長霍转。 經(jīng)常有香客問我荐绝,道長,這世上最難降的妖魔是什么避消? 我笑而不...
    開封第一講書人閱讀 56,799評論 1 285
  • 正文 為了忘掉前任低滩,我火速辦了婚禮召夹,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘恕沫。我一直安慰自己监憎,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,910評論 6 386
  • 文/花漫 我一把揭開白布婶溯。 她就那樣靜靜地躺著鲸阔,像睡著了一般。 火紅的嫁衣襯著肌膚如雪爬虱。 梳的紋絲不亂的頭發(fā)上隶债,一...
    開封第一講書人閱讀 50,096評論 1 291
  • 那天,我揣著相機(jī)與錄音跑筝,去河邊找鬼死讹。 笑死,一個胖子當(dāng)著我的面吹牛曲梗,可吹牛的內(nèi)容都是我干的赞警。 我是一名探鬼主播,決...
    沈念sama閱讀 39,159評論 3 411
  • 文/蒼蘭香墨 我猛地睜開眼虏两,長吁一口氣:“原來是場噩夢啊……” “哼愧旦!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起定罢,我...
    開封第一講書人閱讀 37,917評論 0 268
  • 序言:老撾萬榮一對情侶失蹤笤虫,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后祖凫,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體琼蚯,經(jīng)...
    沈念sama閱讀 44,360評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,673評論 2 327
  • 正文 我和宋清朗相戀三年惠况,在試婚紗的時候發(fā)現(xiàn)自己被綠了遭庶。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,814評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡稠屠,死狀恐怖峦睡,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情权埠,我是刑警寧澤榨了,帶...
    沈念sama閱讀 34,509評論 4 334
  • 正文 年R本政府宣布,位于F島的核電站攘蔽,受9級特大地震影響阻逮,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜秩彤,卻給世界環(huán)境...
    茶點故事閱讀 40,156評論 3 317
  • 文/蒙蒙 一叔扼、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧漫雷,春花似錦瓜富、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至蓄坏,卻和暖如春价捧,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背涡戳。 一陣腳步聲響...
    開封第一講書人閱讀 32,123評論 1 267
  • 我被黑心中介騙來泰國打工结蟋, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人渔彰。 一個月前我還...
    沈念sama閱讀 46,641評論 2 362
  • 正文 我出身青樓嵌屎,卻偏偏與公主長得像,于是被迫代替她去往敵國和親恍涂。 傳聞我的和親對象是個殘疾皇子宝惰,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,728評論 2 351

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