后台保活的几个方向
1.短时间保活
beginBackgroundTaskWithName
和endBackgroundTask
测试机型【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】
拓展
- 参考学习网址:
App
接入iOS 11
的Files App
:https://www.jianshu.com/p/61b4e26ab413iOS
后台挂起的一些坑:https://www.cnblogs.com/saytome/p/7080056.htmliOS
如何实时查看App
运行日志:http://www.cocoachina.com/articles/19933iOS
版微信推送即时消息是如何实现的:https://www.zhihu.com/question/20654687iPhone
在一个小时内,接受推送的次数有限制吗:https://community.jiguang.cn/t/iphone/2511- 为什么
iOS
的微信没有常驻后台,也能收到新消息通知:https://mlog.club/article/45386 iOS
后台运行的相关方案总结:https://ctinusdev.github.io/2016/05/10/BackgroundTask/iOS
之原生地图的使用详解:https://blog.csdn.net/u011146511/article/details/51245469iOS
项目技术还债之路《一》后台下载趟坑:https://juejin.im/post/5cf7eb0351882576710e5c15