iOS 关于后台持续运行

      在日常的工作开发中,有时会遇到需要在后台持续运行的需求。对于这个需求,安卓实现起来比较简单,而iOS来说就比较复杂了。前段时间我们公司就有后台持续定位,并且上传上传地理坐标的需求(可能听起来有点流氓)。趁现在有空总结下。

先小结下:

  • 一般来说,没有进行过任何设置的app,默认退到后台极短的几秒后就变成挂起状态
  • 当设置了UIBackgroundTaskIdentifier后台任务标记时,程序后台保活会延迟到三分钟左右
  • 再设置后台持续定位,或者持续voip播放模式后,会长时间保活,这个时间从原理上可以无限(参考网易、酷狗等音乐播放器),我自己用的后台持续定位模式所持续的时间基本都在2小时以上

下面来说下主要操作和原理
      这里我测试后台持续时间是用APP角标计数的,因为发现如果本地调试退到后台和拔出线正常跑的时间结果不一样,在上面的第二种情况(UIBackgroundTaskIdentifier)情况下,如果本地连线调试,也可以很久,但是拔出线设置角标也就三分钟

先起个定时器用来改变角标(显示角标需要先注册)以记录时间,并在退到后台时启动
- (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);
}
正常未设置情况

代码和上面一样,角标在退出后台情况下只增加了一次


未设置情况下

设置bgtask

在进行代码实验前,先对UIBackgroundTaskIdentifier 进行了解下。
beginBackgroundTaskWithExpirationHandler这个方法是开启后台延迟挂起的操作方法,这个时间是有限的。通过UIApplication中的backgroundTimeRemaining值判断系统给你剩余的时间。

在前台运行时,backgroundTimeRemaining这个值是doule_max,就是说无限,在后台时这个值大约在180s左右,然后系统额外时间结束,开始进入挂起状态,注意这里不要在线本地调试

 - (void)applicationDidEnterBackground:(UIApplication *)application {
    [self stratBadgeNumberCount];
    [self startBgTask];
}

- (void)startBgTask{
    UIApplication *application = [UIApplication sharedApplication];
    __block    UIBackgroundTaskIdentifier bgTask;
    bgTask = [application beginBackgroundTaskWithExpirationHandler:^{
        //这里延迟的系统时间结束
        NSLog(@"%f",application.backgroundTimeRemaining);
    }];
}

果然值持续了180s左右
image.png

重复启动Bgtask

然后在以上基础思考这个问题的时候想,能不能在刚好快结束的时候,通过dispatch_after或者在block里重新开启一次bgtask 来获取新一轮的延长时间,所以进行了如下设置


死循环执行
延迟一段时间后重新开一个

这两种姿势都还是只能撑180s,所有我在想是不是就算重新开了一个bgtask也不会增加系统剩余时间


设置系统标志的后台任务 -- voip、location
location模式

我先用的后台定位,这个需要在


image.png
  • 这里勾选一下后台定位更新
  • 添加定位隐私描述 Privacy - Location Always and When In Use Usage Description 我这里是11的系统,就直接用这个了
  • 开启定时器调用系统定位方法进行定位,并设置后台更新模式为YES,iOS9系统才能用
  • 去吃午饭,回来看依然在跑


    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:^{
        //这里延迟的系统时间结束
        [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);
}



/** 苹果_用户位置更新后,会调用此函数 */
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray<CLLocation *> *)locations{
    [appleLocationManager stopUpdatingLocation];
    appleLocationManager.delegate = nil;
    NSLog(@"success");
}

/** 苹果_定位失败后,会调用此函数 */
- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error{
    [appleLocationManager stopUpdatingLocation];
    appleLocationManager.delegate = nil;
    NSLog(@"error");
}
@end

到这里功能就基本实现了,下面看使用voip进行调用的实现

后台播放静默语音模式
  • 和定位一样,勾选后台声音选项
  • 播放静默声音,无限循环

代码如下

#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];
        //一直循环播放
        _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:^{
        //这里延迟的系统时间结束
        [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

以上两种后台模式都可以支持无限运行,但是都有两个很尴尬的问题

  • 比较费电
  • 审核人员会问你你想干啥
    第一种后台定位的情况我们是打电话进行交流,说明了我们软件需要采集用户信息展示出来(和滴滴展示轨迹一样),后台语音那个,我们是录取一个视频给审核人员看的,虽然我觉得这个功能可能还是被拒,但是审核人员对这个好像不太严格,看了一下视频就过了

总结

      以上就是对后台持续运行的探讨,个人意见是,如果不需要后台持续做一些操作的话,不需要开启capabilities的后天模式,三分钟已经足够做很多事了。
      但是如果有需求的话,就需要设置后台模式,并且应对苹果的审核人员

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342