??????在日常的工作開發(fā)中,有時(shí)會遇到需要在后臺持續(xù)運(yùn)行的需求。對于這個(gè)需求,安卓實(shí)現(xiàn)起來比較簡單,而iOS來說就比較復(fù)雜了。前段時(shí)間我們公司就有后臺持續(xù)定位,并且上傳上傳地理坐標(biāo)的需求(可能聽起來有點(diǎn)流氓)。趁現(xiàn)在有空總結(jié)下。
先小結(jié)下:
- 一般來說,沒有進(jìn)行過任何設(shè)置的app,默認(rèn)退到后臺極短的幾秒后就變成掛起狀態(tài)
- 當(dāng)設(shè)置了UIBackgroundTaskIdentifier后臺任務(wù)標(biāo)記時(shí),程序后臺?;顣舆t到三分鐘左右
- 再設(shè)置后臺持續(xù)定位,或者持續(xù)voip播放模式后,會長時(shí)間?;?,這個(gè)時(shí)間從原理上可以無限(參考網(wǎng)易、酷狗等音樂播放器),我自己用的后臺持續(xù)定位模式所持續(xù)的時(shí)間基本都在2小時(shí)以上
下面來說下主要操作和原理
??????這里我測試后臺持續(xù)時(shí)間是用APP角標(biāo)計(jì)數(shù)的,因?yàn)榘l(fā)現(xiàn)如果本地調(diào)試退到后臺和拔出線正常跑的時(shí)間結(jié)果不一樣,在上面的第二種情況(UIBackgroundTaskIdentifier)情況下,如果本地連線調(diào)試,也可以很久,但是拔出線設(shè)置角標(biāo)也就三分鐘
先起個(gè)定時(shí)器用來改變角標(biāo)(顯示角標(biāo)需要先注冊)以記錄時(shí)間,并在退到后臺時(shí)啟動
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
//注冊推送
UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
[center requestAuthorizationWithOptions:(UNAuthorizationOptionBadge | UNAuthorizationOptionSound | UNAuthorizationOptionAlert) completionHandler:^(BOOL granted, NSError * _Nullable error) {
if (!error) {
NSLog(@"request authorization succeeded!");
}
}];
return YES;
}
- (void)applicationDidEnterBackground:(UIApplication *)application {
[self stratBadgeNumberCount];
}
- (void)stratBadgeNumberCount{
[UIApplication sharedApplication].applicationIconBadgeNumber = 0;
_badgeTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
dispatch_source_set_timer(_badgeTimer, DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC, 1 * NSEC_PER_SEC);
dispatch_source_set_event_handler(_badgeTimer, ^{
[UIApplication sharedApplication].applicationIconBadgeNumber ++;
});
dispatch_resume(_badgeTimer);
}
正常未設(shè)置情況
代碼和上面一樣,角標(biāo)在退出后臺情況下只增加了一次

設(shè)置bgtask
在進(jìn)行代碼實(shí)驗(yàn)前,先對UIBackgroundTaskIdentifier 進(jìn)行了解下。
beginBackgroundTaskWithExpirationHandler這個(gè)方法是開啟后臺延遲掛起的操作方法,這個(gè)時(shí)間是有限的。通過UIApplication中的backgroundTimeRemaining值判斷系統(tǒng)給你剩余的時(shí)間。
在前臺運(yùn)行時(shí),backgroundTimeRemaining這個(gè)值是doule_max,就是說無限,在后臺時(shí)這個(gè)值大約在180s左右,然后系統(tǒng)額外時(shí)間結(jié)束,開始進(jìn)入掛起狀態(tài),注意這里不要在線本地調(diào)試
- (void)applicationDidEnterBackground:(UIApplication *)application {
[self stratBadgeNumberCount];
[self startBgTask];
}
- (void)startBgTask{
UIApplication *application = [UIApplication sharedApplication];
__block UIBackgroundTaskIdentifier bgTask;
bgTask = [application beginBackgroundTaskWithExpirationHandler:^{
//這里延遲的系統(tǒng)時(shí)間結(jié)束
NSLog(@"%f",application.backgroundTimeRemaining);
}];
}
果然值持續(xù)了180s左右
重復(fù)啟動Bgtask
然后在以上基礎(chǔ)思考這個(gè)問題的時(shí)候想,能不能在剛好快結(jié)束的時(shí)候,通過dispatch_after或者在block里重新開啟一次bgtask 來獲取新一輪的延長時(shí)間,所以進(jìn)行了如下設(shè)置


這兩種姿勢都還是只能撐180s,所有我在想是不是就算重新開了一個(gè)bgtask也不會增加系統(tǒng)剩余時(shí)間
設(shè)置系統(tǒng)標(biāo)志的后臺任務(wù) -- voip、location
location模式
我先用的后臺定位,這個(gè)需要在

- 這里勾選一下后臺定位更新
- 添加定位隱私描述 Privacy - Location Always and When In Use Usage Description 我這里是11的系統(tǒng),就直接用這個(gè)了
- 開啟定時(shí)器調(diào)用系統(tǒng)定位方法進(jìn)行定位,并設(shè)置后臺更新模式為YES,iOS9系統(tǒng)才能用
-
去吃午飯,回來看依然在跑
image.png
code如下
#import "AppDelegate.h"
#import <UserNotifications/UserNotifications.h>
#import <CoreLocation/CoreLocation.h>
@interface AppDelegate ()<CLLocationManagerDelegate>
@property (nonatomic) dispatch_source_t badgeTimer;
@end
@implementation AppDelegate{
CLLocationManager *appleLocationManager;
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
//注冊推送
UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
[center requestAuthorizationWithOptions:(UNAuthorizationOptionBadge | UNAuthorizationOptionSound | UNAuthorizationOptionAlert) completionHandler:^(BOOL granted, NSError * _Nullable error) {
if (!error) {
NSLog(@"request authorization succeeded!");
}
}];
return YES;
}
- (void)applicationDidEnterBackground:(UIApplication *)application {
[self stratBadgeNumberCount];
[self startBgTask];
}
- (void)startBgTask{
UIApplication *application = [UIApplication sharedApplication];
__block UIBackgroundTaskIdentifier bgTask;
bgTask = [application beginBackgroundTaskWithExpirationHandler:^{
//這里延遲的系統(tǒng)時(shí)間結(jié)束
[application endBackgroundTask:bgTask];
NSLog(@"%f",application.backgroundTimeRemaining);
}];
}
- (void)stratBadgeNumberCount{
[UIApplication sharedApplication].applicationIconBadgeNumber = 0;
//UIApplication *application = [UIApplication sharedApplication];
_badgeTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
dispatch_source_set_timer(_badgeTimer, DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC, 1 * NSEC_PER_SEC);
dispatch_source_set_event_handler(_badgeTimer, ^{
[UIApplication sharedApplication].applicationIconBadgeNumber++;
appleLocationManager = [[CLLocationManager alloc] init];
appleLocationManager.allowsBackgroundLocationUpdates = YES;
appleLocationManager.desiredAccuracy = kCLLocationAccuracyBest;
appleLocationManager.delegate = self;
[appleLocationManager requestAlwaysAuthorization];
[appleLocationManager startUpdatingLocation];
});
dispatch_resume(_badgeTimer);
}
/** 蘋果_用戶位置更新后,會調(diào)用此函數(shù) */
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray<CLLocation *> *)locations{
[appleLocationManager stopUpdatingLocation];
appleLocationManager.delegate = nil;
NSLog(@"success");
}
/** 蘋果_定位失敗后,會調(diào)用此函數(shù) */
- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error{
[appleLocationManager stopUpdatingLocation];
appleLocationManager.delegate = nil;
NSLog(@"error");
}
@end
到這里功能就基本實(shí)現(xiàn)了,下面看使用voip進(jìn)行調(diào)用的實(shí)現(xiàn)
后臺播放靜默語音模式
- 和定位一樣,勾選后臺聲音選項(xiàng)
- 播放靜默聲音,無限循環(huán)
代碼如下
#import "AppDelegate.h"
#import <UserNotifications/UserNotifications.h>
#import <CoreLocation/CoreLocation.h>
#import <AVFoundation/AVFoundation.h>
//#import "TestUserVoice.h"
@interface AppDelegate ()<CLLocationManagerDelegate>
@property (nonatomic) dispatch_source_t badgeTimer;
@property (nonatomic, strong) AVAudioPlayer *player;
@end
@implementation AppDelegate{
CLLocationManager *appleLocationManager;
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
//注冊推送
UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
[center requestAuthorizationWithOptions:(UNAuthorizationOptionBadge | UNAuthorizationOptionSound | UNAuthorizationOptionAlert) completionHandler:^(BOOL granted, NSError * _Nullable error) {
if (!error) {
NSLog(@"request authorization succeeded!");
}
}];
[self player];
return YES;
}
- (void)applicationDidEnterBackground:(UIApplication *)application {
[self stratBadgeNumberCount];
[self startBgTask];
/** 播放聲音 */
[self.player play];
}
- (AVAudioPlayer *)player{
if (!_player){
NSURL *url=[[NSBundle mainBundle]URLForResource:@"work5.mp3" withExtension:nil];
_player = [[AVAudioPlayer alloc]initWithContentsOfURL:url error:nil];
[_player prepareToPlay];
//一直循環(huán)播放
_player.numberOfLoops = -1;
AVAudioSession *session = [AVAudioSession sharedInstance];
[session setCategory:AVAudioSessionCategoryPlayback error:nil];
[session setActive:YES error:nil];
}
return _player;
}
- (void)startBgTask{
UIApplication *application = [UIApplication sharedApplication];
__block UIBackgroundTaskIdentifier bgTask;
bgTask = [application beginBackgroundTaskWithExpirationHandler:^{
//這里延遲的系統(tǒng)時(shí)間結(jié)束
[application endBackgroundTask:bgTask];
NSLog(@"%f",application.backgroundTimeRemaining);
}];
}
- (void)stratBadgeNumberCount{
[UIApplication sharedApplication].applicationIconBadgeNumber = 0;
_badgeTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
dispatch_source_set_timer(_badgeTimer, DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC, 1 * NSEC_PER_SEC);
dispatch_source_set_event_handler(_badgeTimer, ^{
[UIApplication sharedApplication].applicationIconBadgeNumber++;
// appleLocationManager = [[CLLocationManager alloc] init];
// appleLocationManager.allowsBackgroundLocationUpdates = YES;
// appleLocationManager.desiredAccuracy = kCLLocationAccuracyBest;
// appleLocationManager.delegate = self;
// [appleLocationManager requestAlwaysAuthorization];
// [appleLocationManager startUpdatingLocation];
});
dispatch_resume(_badgeTimer);
}
@end
以上兩種后臺模式都可以支持無限運(yùn)行,但是都有兩個(gè)很尷尬的問題
- 比較費(fèi)電
- 審核人員會問你你想干啥
第一種后臺定位的情況我們是打電話進(jìn)行交流,說明了我們軟件需要采集用戶信息展示出來(和滴滴展示軌跡一樣),后臺語音那個(gè),我們是錄取一個(gè)視頻給審核人員看的,雖然我覺得這個(gè)功能可能還是被拒,但是審核人員對這個(gè)好像不太嚴(yán)格,看了一下視頻就過了
總結(jié)
??????以上就是對后臺持續(xù)運(yùn)行的探討,個(gè)人意見是,如果不需要后臺持續(xù)做一些操作的話,不需要開啟capabilities的后天模式,三分鐘已經(jīng)足夠做很多事了。
??????但是如果有需求的話,就需要設(shè)置后臺模式,并且應(yīng)對蘋果的審核人員
