iOS 關(guān)于后臺持續(xù)運(yùn)行

??????在日常的工作開發(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è)置情況下

設(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左右
image.png

重復(fù)啟動Bgtask

然后在以上基礎(chǔ)思考這個(gè)問題的時(shí)候想,能不能在剛好快結(jié)束的時(shí)候,通過dispatch_after或者在block里重新開啟一次bgtask 來獲取新一輪的延長時(shí)間,所以進(jìn)行了如下設(shè)置


死循環(huán)執(zhí)行
延遲一段時(shí)間后重新開一個(gè)

這兩種姿勢都還是只能撐180s,所有我在想是不是就算重新開了一個(gè)bgtask也不會增加系統(tǒng)剩余時(shí)間


設(shè)置系統(tǒng)標(biāo)志的后臺任務(wù) -- voip、location
location模式

我先用的后臺定位,這個(gè)需要在


image.png
  • 這里勾選一下后臺定位更新
  • 添加定位隱私描述 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)對蘋果的審核人員

Demo:https://github.com/MoonMD/BgTaskDemo.git

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容