iOS 关于后台持续运行

作者: moxacist | 来源:发表于2018-01-25 15:54 被阅读741次

          在日常的工作开发中,有时会遇到需要在后台持续运行的需求。对于这个需求,安卓实现起来比较简单,而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

    相关文章

      网友评论

      • 无星灬:老大,github下下来TestBgTask.xcodeproj打不开
      • LeeRich:你好,我想问下,后台持续定位并且持续上报位置点,但是有很多情况会导致位置点定位不准确,误差很大,请问你遇到这个问题了吗
        moxacist:@LeeRich 这个差距太大了吧,我这边有时遇到的也只是500m左右,不过很少见,基站也不会差这么多,你可以换成苹果原生的定位试试,我之前试过,精度一样的,只是坐标系不同
        LeeRich:@MoonMD 我用的是高德sdk里面的持续定位,大概每5分钟必须上报一次位置点。坐标系这些应该是对的。有的时候会受到基站,信息差这些影响,所以有的点误差在3000-5000m左右都有
        moxacist:我这边没遇到过,我用的百度地图,误差很小,我们当时 记录规则是10s一次,超过一定距离才记录,否则认为没移动,坐标系对吗

      本文标题:iOS 关于后台持续运行

      本文链接:https://www.haomeiwen.com/subject/hblpaxtx.html