1. iOS后臺運行
iOS后臺運行分為三種
后臺任務(wù)
App在進入后臺后還有任務(wù)沒執(zhí)行完盗棵,還需要運行一小段時間缘挽,那么可以用Background Task相關(guān)API向系統(tǒng)申請運行權(quán)限,運行完了再通知系統(tǒng)可以掛起App了后臺模式
需要后臺長時間運行任務(wù)的App都需要顯式向系統(tǒng)申請權(quán)限员串,如Background Fetch勇哗,允許App不定時被喚醒來更新一些數(shù)據(jù)。后臺下載
專指由配置了backgroundSessionConfiguration的NSURLSession管理的下載過程寸齐。由系統(tǒng)進程接管App數(shù)據(jù)的下載欲诺,因此即便App被系統(tǒng)掛起抄谐,甚至殺死或崩潰了,也能繼續(xù)下載瞧栗。下載完成后App會被喚醒斯稳,處理一些狀態(tài)更新和回調(diào)。
1.1. iOS后臺模式
- Audio, AirPlay迹恐,and Picture in Picture
- Location updates
- Voice over IP VoIP
- External accessory communication
- Uses Bluetooth LE accessories
- Acts as a Bluetooth LE accessory
- Background fetch
- Remote notifications
- iOS13 新增 Background processing
1.2. iOS App 后臺闭醵瑁活方式簡介
1.2.1. 短時間APP后臺保活(有時間限制30s)
該種方式屬于后臺任務(wù)殴边,是調(diào)用相關(guān)的api來實現(xiàn)
// 開啟后臺任務(wù)
- (UIBackgroundTaskIdentifier)beginBackgroundTaskWithExpirationHandler:(void(^ __nullable)(void))handler
- (UIBackgroundTaskIdentifier)beginBackgroundTaskWithName:(nullable NSString *)taskName expirationHandler:(void(^ __nullable)(void))handler
// 結(jié)束后臺任務(wù)
- (void)endBackgroundTask:(UIBackgroundTaskIdentifier)identifier
實測憎茂,在有音樂播放或錄音時,后臺任務(wù)申請的時間會無限大
1.2.2. 當(dāng)app需要支持在后臺下載文件時
可以通過設(shè)置urlsession的background模式來讓下載任務(wù)傳遞給系統(tǒng)锤岸,這樣當(dāng)系統(tǒng)需要終止APP時會自動接收未下載完成的任務(wù)竖幔,并在下載完成后調(diào)用相應(yīng)的api進行處理
1.2.3. Audio, AirPlay,and Picture in Picture 模式
應(yīng)用在后臺時可以播放聲音信息是偷。
可以利用此模式播放無聲音樂拳氢,App 進入后臺后,播放無聲音樂蛋铆,配合beginBackgroundTaskWithName對系統(tǒng)申請后臺使用時間馋评,可以使APP在后臺長時間保活刺啦。
1.2.4. Location updates 模式
應(yīng)用提供位置信息 應(yīng)用場景:在后臺時需要不斷通知用戶位置更新信息留特。
通過后臺持續(xù)定位App,可以實現(xiàn)App后臺甭耆常活
如果持續(xù)后臺播放無聲音頻或是使用后臺持續(xù)定位的方式實現(xiàn)iOS App后臺蓖汕啵活,會浪費電量糊渊,浪費CPU
實測右核,用戶定位權(quán)限為kCLAuthorizationStatusAuthorizedWhenInUse,此時使用定位泵烊蓿活蒙兰,并不一定能永久保活芒篷,在定位權(quán)限為kCLAuthorizationStatusAuthorizedAlways時搜变,使用定位保活针炉,可以永久蹦铀活
1.2.5 VoIP 模式
VoIP是能真正做到在App掛起和被殺死情況下實時拉起應(yīng)用的方法。當(dāng)然它也有一定的局限性篡帕,應(yīng)用必須要是VoIP應(yīng)用殖侵,即應(yīng)用中有類似視頻呼叫或者語音呼叫等功能贸呢。
1.2.5. Background fetch 模式
應(yīng)用場景:需不斷地頻繁的基于一定規(guī)律從網(wǎng)絡(luò)上獲取新的數(shù)據(jù),大多數(shù)APP的后臺刷新都是使用此模式來完成拢军。
1.2.6. Remote notifications 模式
iOS的靜默推送:收到推送(沒有文字沒有聲音)楞陷,不用點開通知,不用打開APP茉唉,就能執(zhí)行
-application:didReceiveRemoteNotification:fetchCompletionHandler:固蛾,用戶完全感知不到
靜默推送的缺點是:
1、如果應(yīng)用已經(jīng)被Kill度陆。是無法自動拉起應(yīng)用的艾凯。所以它只能在應(yīng)用后臺掛起的情況下使用。
2懂傀、靜默推送和無法保證應(yīng)用被實時喚醒趾诗。官網(wǎng)說法如下:
靜默推送不是讓您的應(yīng)用程序在快速刷新操作之后保持醒來的方式,也不是用于高優(yōu)先級更新的方式蹬蚁。>APN將后臺更新通知視為低優(yōu)先級恃泪,如果總數(shù)過多,APN可能會將其傳輸完全限制在一定程度犀斋。實際的限>制是動態(tài)的贝乎,可以根據(jù)條件進行更改,但不要每小時發(fā)送一次以上的通知闪水。
1.2.7. External accessory communication 模式
有規(guī)律的從外部藍牙設(shè)備獲取信息, 可以在后臺不斷的與外設(shè)進行溝通糕非,開啟后可讓應(yīng)用不斷的與外設(shè)進行溝通蒙具。
1.2.8. Uses Bluetooth LE accessories/Acts as a Bluetooth LE accessory 模式
這兩種模式區(qū)別是一個是將設(shè)備作為外圍設(shè)備球榆,一個是將設(shè)備作為中心設(shè)備。需要在后臺不斷訪問其他藍牙設(shè)備獲取數(shù)據(jù)或不斷更新藍牙狀態(tài)禁筏。
1.2.9 Background processing
這是iOS13新增的一個模式持钉,基于BackgroundTasks,
有點在于不會檢測cpu的占用率篱昔,也會啟動應(yīng)用的后臺任務(wù)每强。
2.后臺保活
后臺任務(wù)+定位敝莨簦活+無聲音樂空执,實現(xiàn)永遠保活
先開啟后臺任務(wù)穗椅,后臺任務(wù)大概30s辨绊,再在后臺任務(wù)過期時,如果定位權(quán)限是kCLAuthorizationStatusAuthorizedAlways匹表,每隔10s定位一次门坷,如果定位權(quán)限不是kCLAuthorizationStatusAuthorizedAlways宣鄙,就每隔10s播放下無聲音樂
核心代碼
- (void)startBackRuning {
NSLog(@"%@ startBackRuning",NSStringFromClass([self class]));
self.backgroundTaskIdentifier = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
[[UIApplication sharedApplication] endBackgroundTask:self.backgroundTaskIdentifier];
self.backgroundTaskIdentifier = UIBackgroundTaskInvalid;
[self startDoTask];
}];
}
- (void)stopBackRuning {
NSLog(@"%@ stopBackRuning",NSStringFromClass([self class]));
if (self.backgroundTaskIdentifier) {
[[UIApplication sharedApplication] endBackgroundTask:self.backgroundTaskIdentifier];
self.backgroundTaskIdentifier = UIBackgroundTaskInvalid;
}
[self stopDoTask];
}
- (void)startDoTask {
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(startDoTask) object:nil];
[self doTask];
[self performSelector:@selector(startDoTask) withObject:nil afterDelay:10];
}
- (void)doTask {
if ([CLLocationManager authorizationStatus] == kCLAuthorizationStatusAuthorizedAlways) {
// 用戶允許持續(xù)定位,使用定位蹦觯活
[self locationTask];
}else {
[self playTask];
}
}
- (void)stopDoTask {
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(startDoTask) object:nil];
}
- (void)locationTask {
[self.locationManager requestLocation];
NSLog(@"%@ locationTask",NSStringFromClass([self class]));
}
- (void)playTask {
[self setAudioPlaySession];
[self playSound];
NSLog(@"%@ playTask",NSStringFromClass([self class]));
}
- (void)setAudioPlaySession {
AVAudioSession *audioSession = [AVAudioSession sharedInstance];
if([NSThread mainThread]){
[audioSession setCategory:AVAudioSessionCategoryPlayback withOptions:AVAudioSessionCategoryOptionMixWithOthers error:nil];
[audioSession setActive:YES error:nil];
}else{
dispatch_async(dispatch_get_main_queue(), ^{
[audioSession setCategory:AVAudioSessionCategoryPlayback withOptions:AVAudioSessionCategoryOptionMixWithOthers error:nil];
[audioSession setActive:YES error:nil];
});
}
}
- (void)playSound
{
if (!self.audioPlayer) {
// 播放文件
NSString *filePath = [[NSBundle mainBundle] pathForResource:@"RunInBackground" ofType:@"mp3"];
NSURL *fileURL = [[NSURL alloc] initFileURLWithPath:filePath];
if (!fileURL) {
NSLog(@"playEmptyAudio 找不到播放文件");
}
// 0.0~1.0,默認(rèn)為1.0
NSError *error = nil;
self.audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:fileURL error:&error];
self.audioPlayer.volume = 0.0;
// 循環(huán)播放 倍澄睿活在后臺導(dǎo)航時 容易不生效
// self.audioPlayer.numberOfLoops = -1;
// [self.audioPlayer prepareToPlay];
}
[self.audioPlayer play];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(.2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self.audioPlayer pause];
self.audioPlayer = nil;
});
}