zoukankan      html  css  js  c++  java
  • iOS App后台保活

    http://www.cocoachina.com/articles/896173

    https://www.jianshu.com/p/d7215a9fc69f

    https://www.jianshu.com/p/d7215a9fc69f

    前段时间,笔者和GY哥一起吃饭聊天的时候,GY哥问了笔者一个问题,iOS App 可以后台保活吗?是如何做到后台保活的?当时笔者只想到了可以在后台播放静音的音乐,对于唤醒App,可以考虑使用推送的方式。GY哥提到播放静音文件会影响上线吗?这我就不大知道了…...由于没有相关实践,笔者后来在网上查了相关资料,总结了本文。

    笔者查询了相关资料后发现,iOS App可以实现后台保活。

    短时间保活的方式有beginBackgroundTaskWithName;

    App长时间保活的方式有:播放无声音乐、后台持续定位、后台下载资源、BGTaskScheduler等;

    唤醒App的方式有:推送、VoIP等;

    本文分为如下几部分:

    • App 运行状态、及状态变化

    • App 后台保活方式简介

    • 短时间App后台保活

    • Background Modes AVAudio,AirPlay,and Picture in Picture

    • Background Modes  Location updates

    • BGTaskScheduler (iOS13.0+)

    App 运行状态、及状态变化

    不低于iOS13.0的设备端App 运行状态

    image.png

    iOS13.0+的设备,支持多场景,共有上图中的Unattached、Foreground Inactive、Foreground Active、Forground Inactive、Background、Suspended 6种状态。

    Unattached:多个场景的情况,如果创建的场景不是当前显示的场景,那么场景处于Unattached状态;

    Foreground Inactive:应用启动后,显示启动图的过程中,处于Foreground Inactive状态;

    Forground Active:应用启动后,显示出来我们设置的rootViewController之后,场景处于Forground Active;

    Foreground Inactive:应用启动后,场景处于显示状态,数据加载完毕,且用户和App没有交互过程中,处于Forground Inactive状态;

    Background:用户点击Home键、或者是切换App后、锁屏后,应用进入Background状态;

    Suspended:进入Background后,应用的代码不执行后,应用进入Suspended状态;(代码是否在运行,可以在应用中写定时器,定时输出内容,从Xcode控制台,或Mac端控制台查看是否有输出内容来判断)

    低于iOS13.0的设备端App 运行状态

    image.png

    上图是低于iOS13.0的设备端App的运行状态,分别是Not Running、Foreground Inactive、Foreground Active、Forground Inactive、Background、Suspended 6种状态。

    Not Running:指用户没有启动App,或用户Terminate App 后,App处于的状态;其他的五种状态和不低于iOS13.0的设备端App的运行状态意义相同。

    App 进入后台状态变化

    笔者写了个定时器,定时输出“普通定时器进行中”,可以看到,应用进入后台后,基本上立刻,就没有内容输出了。笔者认为可以认为此时App 已经进入Suspended的状态。

    image.png

    下边笔者介绍下,尝试的App后台保活方式。

    iOS App 后台保活方式简介

    短时间App后台保活

    beginBackgroundTaskWithName 和 endBackgroundTask

    笔者尝试过使用相关API,测试过2款手机。

    对于系统版本低于iOS13(iOS 12.3)的设备(iPhone6 Plus)后台运行时间约3分钟(175秒);

    对于系统版本不低于iOS13(iOS 13.0)的设备(iPhone6 Plus)后台运行时间约31秒;

    播放无声音乐

    App 进入后台后,播放无声音乐,适用于音视频类App。

    笔者对逆向不了解,从iOS项目技术还债之路《一》后台下载趟坑中得知,腾讯视频、爱奇艺采用了播放无声音乐保活的方式。

    后台持续定位

    对于定位类App,持续定位App,可以实现App后台保活。定位类App需要后台保活,像系统的地图应用,在导航的时候切换App的时候,就需要后台保活。

    后台下载资源

    对于需要下载资源的App,需要后台下载资源,如我们在某App下载资源的时候,我们希望在切换App时候,或者App退出后台后,资源仍然继续下载,这样当我们打开App的时候,资源已经下载好了。

    BackgroundTasks

    BackgroundTasks.framework 是iOS13新增的framework,笔者认为此framework中的API可以在信息流类的App中发挥作用。

    短时间App后台保活

    系统版本低于iOS13.0的设备

    系统版本低于iOS13.0的设备,在应用进入后台的时候,开始后台任务([[UIApplication sharedApplication] beginBackgroundTaskWithName:)。在应用进入前台时或后台任务快过期的回调中,终止后台任务([[UIApplication sharedApplication] endBackgroundTask:)。

    示例代码如下:

    - (void)applicationDidEnterBackground:(UIApplication *)application {
        
        self.backgroundTaskIdentifier = [[UIApplication sharedApplication] beginBackgroundTaskWithName:kBgTaskName expirationHandler:^{
           if (self.backgroundTaskIdentifier != UIBackgroundTaskInvalid) {
               [[UIApplication sharedApplication] endBackgroundTask:self.backgroundTaskIdentifier];
               self.backgroundTaskIdentifier = UIBackgroundTaskInvalid;
           }
        }];
    }复制代码
    - (void)applicationWillEnterForeground:(UIApplication *)application {
        
        [[UIApplication sharedApplication] endBackgroundTask: self.backgroundTaskIdentifier];
    }复制代码

    添加相关代码后,笔者在iOS12.4的6 Plus上测试结果如下,应用在进入后台后,大概还运行了175秒。

    2019-12-29 19:06:55.647288+0800 QiAppRunInBackground[1481:409744] -[AppDelegate applicationDidEnterBackground:]:应用进入后台DidEnterBackground

    2019-12-29 19:06:56.256877+0800 QiAppRunInBackground[1481:409744] 定时器运行中

    ….

    2019-12-29 19:09:50.812460+0800 QiAppRunInBackground[1481:409744] 定时器运行中

    系统版本不低于iOS13.0的设备

    - (void)sceneDidEnterBackground:(UIScene *)scene  API_AVAILABLE(ios(13.0)){
       
        self.backgroundTaskIdentifier = [[UIApplication sharedApplication] beginBackgroundTaskWithName:kBgTaskName expirationHandler:^{
            if (self.backgroundTaskIdentifier != UIBackgroundTaskInvalid) {
                [[UIApplication sharedApplication] endBackgroundTask:self.backgroundTaskIdentifier];
                self.backgroundTaskIdentifier = UIBackgroundTaskInvalid;
            }
        }];
    }复制代码
    - (void)sceneWillEnterForeground:(UIScene *)scene  API_AVAILABLE(ios(13.0)){
        
        [[UIApplication sharedApplication] endBackgroundTask: self.backgroundTaskIdentifier];
    }复制代码

    添加相关代码后,笔者在iOS13.0的6s上测试结果如下,应用在进入后台后,大概还运行了31秒。

    image.png

    Xs·H 提到过,如果持续后台播放无声音频或是使用后台持续定位的方式实现iOS App后台保活,会浪费电量,浪费CPU,所以一般情况下,使用这种短时间延长App 后台保活的方式,应该够开发者做需要的操作了。

    Background Modes AVAudio,AirPlay,and Picture in Picture

    对于音视频类App,如果需要后台保活App,在App 进入后台后,可以考虑先使用短时间保活App的方式,如果后台保活App方式快结束后,还没处理事情,那么可以考虑使用后台播放无声音乐。相关示例代码如下。

    - (AVAudioPlayer *)player {
        
        if (!_player) {
            NSURL *fileURL = [[NSBundle mainBundle] URLForResource:@"SomethingJustLikeThis" withExtension:@"mp3"];
            AVAudioPlayer *audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:fileURL error:nil];
            audioPlayer.numberOfLoops = NSUIntegerMax;
            _player = audioPlayer;
        }
        return _player;
    }复制代码
    [self.player prepareToPlay];复制代码

    系统版本低于iOS13.0的设备

    - (void)applicationDidEnterBackground:(UIApplication *)application {
    
        NSLog(@"%s:应用进入后台DidEnterBackground", __FUNCTION__);
        self.backgroundTaskIdentifier = [[UIApplication sharedApplication] beginBackgroundTaskWithName:kBgTaskName expirationHandler:^{
    
           if ([QiAudioPlayer sharedInstance].needRunInBackground) {
               [[QiAudioPlayer sharedInstance].player play];
           }
           if (self.backgroundTaskIdentifier != UIBackgroundTaskInvalid) {
               [[UIApplication sharedApplication] endBackgroundTask:self.backgroundTaskIdentifier];
               self.backgroundTaskIdentifier = UIBackgroundTaskInvalid;
           }
        }];
    }复制代码
    - (void)applicationWillEnterForeground:(UIApplication *)application {
    
        NSLog(@"%s:应用将进入前台WillEnterForeground", __FUNCTION__);
        if ([QiAudioPlayer sharedInstance].needRunInBackground) {
            [[QiAudioPlayer sharedInstance].player pause];
        }
        [[UIApplication sharedApplication] endBackgroundTask: self.backgroundTaskIdentifier];
    }复制代码

    系统版本不低于iOS13.0的设备

    - (void)sceneDidEnterBackground:(UIScene *)scene  API_AVAILABLE(ios(13.0)){
    
        NSLog(@"%s:应用已进入后台DidEnterBackground", __FUNCTION__);
    
        self.backgroundTaskIdentifier = [[UIApplication sharedApplication] beginBackgroundTaskWithName:kBgTaskName expirationHandler:^{
            if (self.backgroundTaskIdentifier != UIBackgroundTaskInvalid) {
                if ([QiAudioPlayer sharedInstance].needRunInBackground) {
                    [[QiAudioPlayer sharedInstance].player play];
                }
                NSLog(@"终止后台任务");
                [[UIApplication sharedApplication] endBackgroundTask:self.backgroundTaskIdentifier];
                self.backgroundTaskIdentifier = UIBackgroundTaskInvalid;
            }
        }];
    }复制代码
    - (void)sceneWillEnterForeground:(UIScene *)scene  API_AVAILABLE(ios(13.0)){
    
        if ([QiAudioPlayer sharedInstance].needRunInBackground) {
            [[QiAudioPlayer sharedInstance].player pause];
        }
        [[UIApplication sharedApplication] endBackgroundTask: self.backgroundTaskIdentifier];
        NSLog(@"%s:应用将进入前台WillEnterForeground", __FUNCTION__);
    }复制代码

    Background Modes  Location updates

    开启后台定位持续更新配置,添加了位置隐私申请后,在应用使用持续定位的情况下,可以实现后台保活App。

    image.png

    image.png

    对于定位类App,如果需要后台保活App,在用户使用了定位功能后,App 进入后台后,App自动具备后台保活能力,部分示例代码如下。

        self.locationManager = [CLLocationManager new];
        self.locationManager.delegate = self;
        [self.locationManager requestAlwaysAuthorization];
        @try {
           self.locationManager.allowsBackgroundLocationUpdates = YES;
        } @catch (NSException *exception) {
            NSLog(@"异常:%@", exception);
        } @finally {
            
        }
        [self.locationManager startUpdatingLocation];复制代码

    如果遇到如下异常信息:

    2019-12-29 19:57:46.481218+0800 QiAppRunInBackground[1218:141397] 异常:Invalid parameter not satisfying: !stayUp || CLClientIsBackgroundable(internal->fClient) || _CFMZEnabled()

    • 检查:Signing&Capablities 的 backgounrd Modes 中 Location updates是否勾选;

    后台下载资源

    当需要实现下载资源类的App在进入后台后,持续下载资源的需求时。我们可能需要使用后台如下示例示例代码。

    创建指定标识的后台NSURLSessionConfiguration,配置好

        NSURL *url = [NSURL URLWithString:@"https://images.pexels.com/photos/3225517/pexels-photo-3225517.jpeg"];
        NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:@"com.qishare.ios.wyw.backgroundDownloadTask"];
    // 低于iOS13.0设备资源下载完后 可以得到通知 AppDelegate.m 文件中的 - (void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)(void))completionHandler
    // iOS13.0+的设备资源下载完后 直接在下载结束的代理方法中会有回调
        sessionConfig.sessionSendsLaunchEvents = YES;
    // 当传输大数据量数据的时候,建议将此属性设置为YES,这样系统可以安排对设备而言最佳的传输时间。例如,系统可能会延迟传输大文件,直到设备连接充电器并通过Wi-Fi连接到网络为止。 此属性的默认值为NO。
        sessionConfig.discretionary = YES;
        NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfig delegate:self delegateQueue:nil];
        NSURLSessionDownloadTask *downloadTask = [session downloadTaskWithURL:url];
        [downloadTask resume];复制代码

    BGTaskScheduler(iOS13.0+)

    如果我们的App是信息流类App,那么我们可能会使用到BGTaskScheduler.framework中的API,实现后台保活App,帮助用户较早地获取到较新信息。

    笔者尝试使用BGTaskScheduler 做了一个获取到App调度的时候。更新首页按钮颜色为随机色并且记录调度时间的Demo。

    Demo示意图

    项目配置

    为了App 支持 BGTaskScheduler,需要在项目中配置Background fetch,及Background Processing;

    需要在Info.plist文件中添加 key 为Permitted background task scheduler identifiers,Value为数组的内容。

    Value的数组填写,刷新的任务标识和清理的任务标识。

    注册后台任务

    在应用启动后,注册后台任务。

    - (void)registerBgTask {
        
        if (@available(iOS 13.0, *)) {
            BOOL registerFlag = [[BGTaskScheduler sharedScheduler] registerForTaskWithIdentifier:kRefreshTaskId usingQueue:nil launchHandler:^(__kindof BGTask * _Nonnull task) {
                [self handleAppRefresh:task];
            }];
            if (registerFlag) {
                NSLog(@"注册成功");
            } else {
                NSLog(@"注册失败");
            }
        } else {
            // Fallback on earlier versions
        }
        
        if (@available(iOS 13.0, *)) {
            [[BGTaskScheduler sharedScheduler] registerForTaskWithIdentifier:kCleanTaskId usingQueue:nil launchHandler:^(__kindof BGTask * _Nonnull task) {
                [self handleAppRefresh:task];
            }];
        } else {
            // Fallback on earlier versions
        }
    }复制代码

    调度App 刷新

    应用进入后台后,调度App 刷新。

    - (void)sceneDidEnterBackground:(UIScene *)scene  API_AVAILABLE(ios(13.0)){
    
        [self scheduleAppRefresh];
    }
    
    
    - (void)scheduleAppRefresh {
        
        if (@available(iOS 13.0, *)) {
            BGAppRefreshTaskRequest *request = [[BGAppRefreshTaskRequest alloc] initWithIdentifier:kRefreshTaskId];
            // 最早15分钟后启动后台任务请求
            request.earliestBeginDate = [NSDate dateWithTimeIntervalSinceNow:15.0 * 60];
            NSError *error = nil;
            [[BGTaskScheduler sharedScheduler] submitTaskRequest:request error:&error];
            if (error) {
                NSLog(@"错误信息:%@", error);
            }
            
        } else {
            // Fallback on earlier versions
        }
    }复制代码

    得到后台任务调度的时候,调用App刷新的方法,笔者在这个方法中做了发送更新首页按钮颜色的通知,并且记录了当前更新时间的记录。

    - (void)handleAppRefresh:(BGAppRefreshTask *)appRefreshTask  API_AVAILABLE(ios(13.0)){
        
        [self scheduleAppRefresh];
        
        NSLog(@"App刷新====================================================================");
        NSOperationQueue *queue = [NSOperationQueue new];
        queue.maxConcurrentOperationCount = 1;
        
        NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
            
            [[NSNotificationCenter defaultCenter] postNotificationName:AppViewControllerRefreshNotificationName object:nil];
            
            NSLog(@"操作");
            NSDate *date = [NSDate date];
            NSDateFormatter *dateFormatter = [NSDateFormatter new];
            [dateFormatter setDateFormat:@"yyyy-MM-dd HH:mm"];
            NSString *timeString = [dateFormatter stringFromDate:date];
            
            NSString *filePath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject] stringByAppendingPathComponent:@"QiLog.txt"];
            if (![[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
                NSData *data = [timeString dataUsingEncoding:NSUTF8StringEncoding];
                [[NSFileManager defaultManager] createFileAtPath:filePath contents:data attributes:nil];
            } else {
                NSData *data = [[NSData alloc] initWithContentsOfFile:filePath];
                NSString *originalContent = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
                NSString *content = [originalContent stringByAppendingString:[NSString stringWithFormat:@"
    时间:%@
    ", timeString]];
                data = [content dataUsingEncoding:NSUTF8StringEncoding];
                [data writeToFile:filePath atomically:YES];
            }
        }];
        
        appRefreshTask.expirationHandler = ^{
            [queue cancelAllOperations];
        };
        [queue addOperation:operation];
        
        __weak NSBlockOperation *weakOperation = operation;
        operation.completionBlock = ^{
            [appRefreshTask setTaskCompletedWithSuccess:!weakOperation.isCancelled];
        };
    }复制代码

    经过测试,发现App 在退到后台,没有手动Terminate App的情况下。苹果有调用过App调度任务的方法。现象上来看就是隔一段时间,我们再打开App 的时候可以发现,首页的按钮颜色改变了,相应的日志中追加了,调起相关方法的时间记录。

    手动触发后台任务调度

    Xcode运行我们的App

    -> App 退到后台

    -> 打开App 进入前台

    -> 点击下图中蓝框中的Pause program execution,输入如下内容

    image.png

    e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier: @"com.qishare.ios.wyw.background.refresh"]复制代码

    -> 再次点击Continue program execution,就可以模拟后台启动任务,调用我们的App。

    image.png

    查看日志记录小提示

    之前记得听沐灵洛提过怎么便于查看日志,正好我这里也用到了。便于我们可以直接在File App中查看写入到我们App的Documents中的文件,可以在Info.plist文件中添加key为LSSupportsOpeningDocumentsInPlace ,value为YES的键值对App 接入 iOS 11 的 Files App

    经过我们操作后,就可以打开File App -> 浏览 -> 我的iPhone -> 查看选择我们的App -> 查看我们的日志记录文件。

    示例Demo

    QiAppRunInBackground

    参考学习网址

    iOS后台保活按时间可分为短时保活长时间保活

    • 短时保活的方式
      通过beginBackgroundTaskWithName来实现。在iOS7-iOS13可以申请到大约3分钟的保活时间,在iOS 13以后只能申请30秒左右的时间。

      1. 先通过监听UIApplicationWillEnterForegroundNotification(应用进入前台通知)和UIApplicationDidEnterBackgroundNotification(应用进入后台通知)。
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appWillEnterForeground) name:UIApplicationWillEnterForegroundNotification object:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appDidEnterBackground) name:UIApplicationDidEnterBackgroundNotification object:nil];
    - (void)appWillEnterForeground {}
    
    - (void)appDidEnterBackground {}
    
    1. 使用Background Task在应用进入后台时开启保活,进入前台时关闭保活。
    @property (assign, nonatomic) UIBackgroundTaskIdentifier backgroundId;
    - (void)appWillEnterForeground {
       [self stopKeepAlive];
    }
    
    - (void)appDidEnterBackground {
        _backgroundId = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
            //申请的时间即将到时回调该方法
            NSLog(@"BackgroundTask time gone");
            [self stopKeepAlive];
        }];
    }
    
    - (void)stopKeepAlive{
      if (_backgroundId) {
            [[UIApplication sharedApplication] endBackgroundTask:_backgroundId];
            _backgroundId = UIBackgroundTaskInvalid;
        }
    }
    

    如果想申请多一点时间,可以使用NSTimer循环申请保活时间,但是建议不要无限申请保活时间,因为系统如果发现该app一直在后台运行,可能会直接杀掉app。

    //开启定时器 不断向系统请求后台任务执行的时间
    NSTimer *_timer = [NSTimer scheduledTimerWithTimeInterval:5 target:self selector:@selector(applyForMoreTime) userInfo:nil repeats:YES];
    [_timer fire];
    
    //在这里我判断了申请次数,加上第一次申请保活时间的次数一共6次。
    @property(nonatomic,assign) int applyTimes;
    -(void)applyForMoreTime {
        if ([UIApplication sharedApplication].backgroundTimeRemaining < 10) {
            _applyTimes += 1;
            NSLog(@"Try to apply for more time:%d",_applyTimes);
            [[UIApplication sharedApplication] endBackgroundTask:_backIden];
            _backIden = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
                [self stopKeepAlive];
            }];
            if(_applyTimes == 5){
                [_timer invalidate];
                _applyTimes = 0;
                [self stopKeepAlive];
            }
        }
    }
    
    • 长时间保活
      App长时间保活的方式有:播放无声音乐、后台持续定位、后台下载资源、BGTaskScheduler等,这些需要在苹果后台开通后台权限,并且在xcode中也开启相关权限。
    1. 播放无声音乐,适用于音乐类app。像腾讯视频、爱奇艺等用了播放无声音乐保活的方式。
      在app进入后台时开启无声音乐,进入前台后停止无声音乐。(更好的处理方式是先获取短时保活,短时快过时再播放无声音乐)示例如下:
      监听进入前后台:
    @property (nonatomic, strong) BackgroundPlayer* player;
    
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appWillEnterForeground) name:UIApplicationWillEnterForegroundNotification object:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appDidEnterBackground) name:UIApplicationDidEnterBackgroundNotification object:nil];
    - (void)appWillEnterForeground {
        if (self.player) {
            [self.player stopPlayBackgroundAlive];
        }
    }
    
    - (void)appDidEnterBackground {
        if (_player == nil) {
            _player = [[BackgroundPlayer alloc] init];
        }
        [self.player startPlayer];  
    }
    

    编写音乐播放类:

    #import <Foundation/Foundation.h>
    #import <AVFoundation/AVFoundation.h>
    
    @interface BackgroundPlayer : NSObject <AVAudioPlayerDelegate>
    {
        AVAudioPlayer* _player;
    }
    - (void)startPlayer;
    
    - (void)stopPlayer;
    @end
    
    #import "BackgroundPlayer.h"
    
    @implementation BackgroundPlayer
    
    - (void)startPlayer
    {
        if (_player && [_player isPlaying]) {
            return;
        }
        AVAudioSession *session = [AVAudioSession sharedInstance];
        [[AVAudioSession sharedInstance] setMode:AVAudioSessionModeDefault error:nil];
    
        NSString* route = [[[[[AVAudioSession sharedInstance] currentRoute] outputs] objectAtIndex:0] portType];
        
        if ([route isEqualToString:AVAudioSessionPortHeadphones] || [route isEqualToString:AVAudioSessionPortBluetoothA2DP] || [route isEqualToString:AVAudioSessionPortBluetoothLE] || [route isEqualToString:AVAudioSessionPortBluetoothHFP]) {
            if (@available(iOS 10.0, *)) {
                [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord
                                                 withOptions:(AVAudioSessionCategoryOptionMixWithOthers | AVAudioSessionCategoryOptionAllowBluetooth | AVAudioSessionCategoryOptionAllowBluetoothA2DP)
                                                       error:nil];
            } else {
                // Fallback on earlier versions
            }
        }else{
            [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord
                                             withOptions:(AVAudioSessionCategoryOptionMixWithOthers | AVAudioSessionCategoryOptionDefaultToSpeaker)
                                                   error:nil];
        }
        
        [session setActive:YES error:nil];
        
        NSURL *url = [[NSBundle bundleWithPath:WECAST_CLOUD_BUNDLE_PATH]URLForResource:@"你的音乐资源" withExtension:nil];
        _player = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:nil];
        [_player prepareToPlay];
        [_player setDelegate:self];
        _player.numberOfLoops = -1;
        BOOL ret = [_player play];
        if (!ret) {
            NSLog(@"play failed,please turn on audio background mode");
        }
    }
    
    - (void)stopPlayer
    {
        if (_player) {
            [_player stop];
            _player = nil;
            AVAudioSession *session = [AVAudioSession sharedInstance];
            [session setActive:NO error:nil];
            NSLog(@"stop in play background success");
        }
    }
    
    @end
    
    1. 后台持续定位

    2. 后台下载资源
      创建指定标识的后台NSURLSessionConfiguration,配置好。

        NSURL *url = [NSURL URLWithString:@"https://images.pexels.com/photos/3225517/pexels-photo-3225517.jpeg"];
        NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:@"com.qishare.ios.wyw.backgroundDownloadTask"];
    // 低于iOS13.0设备资源下载完后 可以得到通知 AppDelegate.m 文件中的 - (void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)(void))completionHandler
    // iOS13.0+的设备资源下载完后 直接在下载结束的代理方法中会有回调
        sessionConfig.sessionSendsLaunchEvents = YES;
    // 当传输大数据量数据的时候,建议将此属性设置为YES,这样系统可以安排对设备而言最佳的传输时间。例如,系统可能会延迟传输大文件,直到设备连接充电器并通过Wi-Fi连接到网络为止。 此属性的默认值为NO。
        sessionConfig.discretionary = YES;
        NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfig delegate:self delegateQueue:nil];
        NSURLSessionDownloadTask *downloadTask = [session downloadTaskWithURL:url];
        [downloadTask resume];


    作者:Cherry_06
    链接:https://www.jianshu.com/p/d7215a9fc69f
    来源:简书
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
  • 相关阅读:
    VC 常见问题百问
    python windows 环境变量
    Check server headers and verify HTTP Status Codes
    Where are the AES 256bit cipher suites? Please someone help
    outlook 如何预订会议和会议室
    安装Axis2的eclipse插件后,未出现界面
    windows 环境变量
    python 时间日期处理汇集
    openldap学习笔记(使用openldap2.3.32)
    set p4 environment in windows
  • 原文地址:https://www.cnblogs.com/itlover2013/p/14260372.html
Copyright © 2011-2022 走看看