補(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;
});
}
}
最后觉增,忠誠的祝愿大家都能過審~