iOS后台保活

iOS后台保活

920 发布: 2020/7/22 12:30 本文总阅读量

后台保活的几个方向

1.短时间保活

beginBackgroundTaskWithNameendBackgroundTask
测试机型【iPhone6-12.4.7】后台运行时间约3分钟(176秒)左右;
测试机型【iPhone6s-13.5.1】后台运行时间34秒左右;

2.后台持续定位

这个适用于地图类app,我们是直播音视频,不适用,暂且不讨论;

3.后台下载资源

这个适用于下载比较大的文件【如:离线地图】,也不适用,暂且不讨论;

4.播放无声音乐

类比前几个方案,这个是最适合我们的;

上代码

RKKeepAlive.h
#import <Foundation/Foundation.h>

#import <AVFoundation/AVFoundation.h>

static NSString *const kBgTaskName = @"com.rk.root777.KeepAlive";

@interface RKKeepAlive : NSObject

@property(nonatomic,assign)BOOL needKeepAliveInBackground;
@property(nonatomic,strong)AVAudioPlayer *player;

+(instancetype)sharedKeepInstance;

-(void)startAppLifeCycleMonitor;
@end
RKKeepAlive.m
#import "RKKeepAlive.h"
#import "AppDelegate.h"

static RKKeepAlive *_keepInstance = nil;

@interface RKKeepAlive(){
    int vvvv;
    NSTimer *_timer;
}
@property (nonatomic, assign) UIBackgroundTaskIdentifier backgroundTaskIdentifier;
@end
@implementation RKKeepAlive

+(instancetype)sharedKeepInstance{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _keepInstance = [[super allocWithZone:NULL]init];
    });
    return _keepInstance;
}
+ (instancetype)allocWithZone:(struct _NSZone *)zone{
    return [self sharedKeepInstance];
}

- (void)initPlayer {
    [self.player prepareToPlay];
}
- (AVAudioPlayer *)player {
    if (!_player) {
        NSError *error = nil;
        NSURL *fileURL = [[NSBundle mainBundle] URLForResource:@"mute-mp3" withExtension:@"mp3"];
        AVAudioPlayer *audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:fileURL error:&error];
        audioPlayer.numberOfLoops = NSUIntegerMax;
        _player = audioPlayer;
        
         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];
        NSLog(@"%s 初始化==:%@",__FUNCTION__,error?[NSString stringWithFormat:@"失败:%@",error]:@"成功");
    }
    return _player;
}

-(void)startAppLifeCycleMonitor{
    self.needKeepAliveInBackground = YES;
    YBWeakSelf;
    AppDelegate *appDelegate =  (AppDelegate *)[UIApplication sharedApplication].delegate;
    appDelegate.lifeCycleEvent = ^(YBAppLifeCycle lifeCycleType) {
        [weakSelf appDelegateBlockEvent:lifeCycleType];
    };
}
-(void)appDelegateBlockEvent:(YBAppLifeCycle)lifeCycleType {
    switch (lifeCycleType) {
        case APPLifeCycle_EnterForeground:{
            //[self appActive];
            //前台
            NSLog(@"%s:应用将进入前台WillEnterForeground", __FUNCTION__);
            if (self.needKeepAliveInBackground) {
                [self.player pause];
            }
            [[UIApplication sharedApplication] endBackgroundTask:self.backgroundTaskIdentifier];
        }break;
        case APPLifeCycle_EnterBackground:{
            //[self backGround];
            //后台
            self.backgroundTaskIdentifier = [[UIApplication sharedApplication] beginBackgroundTaskWithName:kBgTaskName expirationHandler:^{
                if (self.needKeepAliveInBackground) {
                    [self.player play];
                }
                if (self.backgroundTaskIdentifier != UIBackgroundTaskInvalid) {
                    [[UIApplication sharedApplication] endBackgroundTask:self.backgroundTaskIdentifier];
                    self.backgroundTaskIdentifier = UIBackgroundTaskInvalid;
                }
            }];
            NSLog(@"%s:应用进入后台DidEnterBackground", __FUNCTION__);
        }break;
        case APPLifeCycle_WillTerminate:{
            //杀进程
            NSLog(@"%s:应用终止:WillTerminate", __FUNCTION__);
        }break;
            
        default:
            break;
    }
}
#pragma mark - 保活测试
-(void)backGround {
    [self setupTimer];
}
-(void)appActive {
    if (_timer) {
        [_timer invalidate];
        _timer = nil;
        vvvv = 0;
    }
}

#pragma mark - 定时器
- (void)setupTimer {
    vvvv = 1;
    
    _timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(timerEvent:) userInfo:nil repeats:YES];
    [[NSRunLoop currentRunLoop] addTimer:_timer forMode:NSRunLoopCommonModes];
    [_timer fire];
}

- (void)timerEvent:(id)sender {
    vvvv ++;
    NSLog(@"普通定时器运行中:%d",vvvv);
    
    if (vvvv%60 == 0) {
        [[NSUserDefaults standardUserDefaults] setObject:[NSString stringWithFormat:@"%d",vvvv] forKey:@"rk_keep_alive_time"];
    }
}

@end

使用

1.在AppDelegate.h中声明一下结构体和属性

typedef NS_ENUM(NSInteger,YBAppLifeCycle) {
    APPLifeCycle_Default,           //默认
    APPLifeCycle_EnterForeground,   //进入前台
    APPLifeCycle_EnterBackground,   //进入后台
    APPLifeCycle_WillTerminate,     //杀进程
};
typedef void(^YBAppLifeCycleBlock) (YBAppLifeCycle lifeCycleType);

@interface AppDelegate : YBBaseAppDelegate

@property(nonatomic,copy)YBAppLifeCycleBlock lifeCycleEvent;

2.在launchOptions中设置监听

    //生命周期监听
    [[RKKeepAlive sharedKeepInstance] startAppLifeCycleMonitor];

3.在以下几个系统方法中设置回调

#pragma mark - 杀进程
- (void)applicationWillTerminate:(UIApplication *)application{
    if (self.lifeCycleEvent) {
        self.lifeCycleEvent(APPLifeCycle_WillTerminate);
    }
}
#pragma mark - App进入后台
- (void)applicationDidEnterBackground:(UIApplication *)application {
    
    if (self.lifeCycleEvent) {
        self.lifeCycleEvent(APPLifeCycle_EnterBackground);
    }
}
#pragma mark - App将要从后台返回
- (void)applicationWillEnterForeground:(UIApplication *)application {
    
    if (self.lifeCycleEvent) {
        self.lifeCycleEvent(APPLifeCycle_EnterForeground);
    }
    
}

运行结果

总结

未经过大量统计

测试方式:播放后台音乐
测试时间: 7-22 21:00开始 7-23 9:20结束
测试时间[s]:iPhone6s【44160s】、iPhone6【43260s】【都是一分钟写一次记录】
型号:iPhone6s【13.5.1】、iPhone6【12.4.7】
电池峰值:iPhone6s【76%】、iPhone6【100%-换过电池】
电量:iPhone6s【100%消耗到52%-消耗48%】、iPhone6【80%消耗到25%-消耗55%】

测试方式:杀掉所有应用进程
测试时间:7-23 18:00 7-24 8.20
型号:iPhone6s【13.5.1】、iPhone6【12.4.7】
电池峰值:iPhone6s【76%】、iPhone6【100%-换过电池】
电量:iPhone6s【100%消耗到56%-消耗44%】(不可思议电池有问题?)、iPhone6【100%-消耗0】

拓展