前言
实现后台保活方案前,我们需要了解iOS应用程序的 生命周期
。因为iOS系统的资源是有限的的,应用程序在前台和后台的状态是不一样的。在后台,程序会受到系统的限制,以提高电池使用和用户体验。
应用程序的生命周期
- 应用程序状态
Not Running
:未运行 - 程序未启动
Inactive
:未激活 - 程序在前台运行,不过没有接收到事件。在没有事件处理情况下程序通常停留在这个状态
Active
:激活 - 程序在前台运行且接收到事件。这也是前台的一个正常的模式
Background
:后台 - 程序在后台而且能执行代码。大多数程序进入这个状态后会在这个状态上停留一会儿。时间到了之后会进入 挂起(Suspended)状态
。有的程序经过特殊的请求可以长期处于Background状态
Suspended
:挂起 - 程序在后台不能执行代码。系统会自动把程序变成这个状态而且不会发出通知。当挂起时,程序还是停留在内存中,当系统内存低时,系统就把挂起的程序清除掉,为前提程序提供更多的内存。
下图是程序状态变化图:
- 应用程序状态的代理回调
1
| - (BOOL)application:(UIApplication *)application willFinishLaunchingWithOptions:(NSDictionary *)launchOptions
|
1
| - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
|
- 程序将要进入
非活动状态
执行,再此期间,应用程序不接受消息或事件,比如:来电话了
1
| - (void)applicationWillResignActive:(UIApplication *)application
|
1
| - (void)applicationDidBecomeActive:(UIApplication *)application
|
- 当程序被推送到后台的时候调用。所以要设置后台继续运行,则在这个函数里面设置即可
1
| - (void)applicationDidEnterBackground:(UIApplication *)application
|
1
| - (void)applicationWillEnterForeground:(UIApplication *)application
|
- 当程序将要退出是被调用,通常是用来保存数据和一些退出前的清理工作。这个需要要设置UIApplicationExitsOnSuspend的键值。
1
| - (void)applicationWillTerminate:(UIApplication *)application
|
- 应用程序启动的执行顺序
1 2 3
| willFinishLaunchingWithOptions didFinishLaunchingWithOptions applicationDidBecomeActive
|
1 2
| applicationWillResignActive applicationDidEnterBackground
|
1 2
| applicationWillEnterForeground applicationDidBecomeActive
|
- 应用程序的生命周期
main函数是程序启动的入口,在iOS app中,main函数的功能被最小化,它的主要工作都交给了UIKit framework:
1 2 3 4 5 6 7 8 9
| #import <UIKit/UIKit.h> int main(int argc, char *argv[]) { @autoreleasepool { return UIApplicationMain(argc, argv, nil, NSStringFromClass([MyAppDelegate class])); } }
|
UIApplicationMain
函数有四个参数,你不需要改变这些参数值,不过我们也需要理解这些参数和程序是如何开始的
argc
和 argv
参数包含了系统带过来的 启动时间
。第三个参数确定了主要应用程序类的名称,这个参数指定为nil,这样 UIKit
就会使用默认的程序类 UIApplication
。第四个参数是程序自定义的 代理类名
,这个类 负责系统和代码之间的交互
。它一般在Xcode新建项目时会自动生成。
另外 UIApplicationMain
函数加载了程序主界面的文件。虽然这个函数加载了界面文件,但是没有放到应用程序的windows上,你需要在Delegate的application:willFinishLaunchingWithOptions
方法中加载它。
一个应用程序可以有一个主的storyboard文件或者有一个主的nib文件,但不能同时有两个存在。
如果程序在启动时没有自动加载主要的故事版或nib文件,你可以 application:willFinishLaunchingWithOptions
方法里准备windows的展示。
后台保活方案
iOS 有两种后台运行保活方案:
- 无声音乐保活
- 第一步:打开应用的
Target
页面 Signing & Cabailities
,添加 Capability(Background Modes)
勾选 Audio,AirPlay,and Picture in Picture
选项
- 第二步:我们需要监听 UIApplicationWillEnterForegroundNotification(应用进入前台通知)和 UIApplicationDidEnterBackgroundNotification(应用进入后台通知)
1 2 3 4 5
| #pragma mark - 监听 前后台 - (void)addNotification{ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appWillEnterForeground) name:UIApplicationWillEnterForegroundNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appDidEnterBackground) name:UIApplicationDidEnterBackgroundNotification object:nil]; }
|
BackgroundTool.h
1 2 3 4 5 6
| #import "BackgroundTool.h" #import <AVFoundation/AVFoundation.h>
@interface BackgroundTool ()<AVAudioPlayerDelegate> @property (nonatomic,strong)AVAudioPlayer * audioPlayer; // 音频播放器 @end
|
Background.m
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| #pragma mark - 音乐 播放 停止 // 开始播放音乐 - (void)startPlayer{ AVAudioSession *session = [AVAudioSession sharedInstance]; if (@available(iOS 10.0, *)) { [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord withOptions:(AVAudioSessionCategoryOptionMixWithOthers | AVAudioSessionCategoryOptionAllowBluetooth | AVAudioSessionCategoryOptionAllowBluetoothA2DP) error:nil]; }else{ [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord withOptions:(AVAudioSessionCategoryOptionMixWithOthers | AVAudioSessionCategoryOptionDefaultToSpeaker) error:nil]; } [session setActive:YES error:nil]; // 播放资源 NSURL * url = [[NSBundle mainBundle] URLForResource:@"backgroundVolue.mp3" withExtension:nil]; self.audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:nil]; self.audioPlayer.volume = 0.01; [self.audioPlayer prepareToPlay]; [self.audioPlayer setDelegate:self]; self.audioPlayer.numberOfLoops = -1; BOOL ret = [self.audioPlayer play]; if (!ret) { DLogInfo(@"play failed,please turn on audio background mode"); } DLogInfo(@"star play success"); }
// 停止播放音乐 - (void)stopPlayer{ [self.audioPlayer stop]; self.audioPlayer = nil; AVAudioSession * session = [AVAudioSession sharedInstance]; [session setActive:NO error:nil]; DLogInfo(@"stop in play background success"); }
|
- Background Task
- 第一步:我们需要监听 UIApplicationWillEnterForegroundNotification(应用进入前台通知)和 UIApplicationDidEnterBackgroundNotification(应用进入后台通知)
1 2 3 4 5
| #pragma mark - 监听 前后台 - (void)addNotification{ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appWillEnterForeground) name:UIApplicationWillEnterForegroundNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appDidEnterBackground) name:UIApplicationDidEnterBackgroundNotification object:nil]; }
|
- 第二步:使用 Background Task 申请保活时间,在应用进入后台时开启保活,在应用进入前台时关闭保活
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| @property (nonatomic,assign)UIBackgroundTaskIdentifier backgroundTaskIdentifier; // 后台任务 @property (nonatomic,strong)NSTimer * timer; // 计时器 @property (nonatomic,assign)int applyTimes; // 申请时间
#pragma mark - 系统任务 // 开始系统任务 - (void)startSystemTask{ // 申请 self.backgroundTaskIdentifier = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{ DLogInfo(@"BackgroundTask time gone"); [self stopSystemTask]; }]; // 开启计时器 [self startTimer]; }
// 申请更多保活时间 - (void)applyForMoreTime{ if ([UIApplication sharedApplication].backgroundTimeRemaining < 10) { self.applyTimes += 1; DLogInfo(@"Try to apply for more time:%d",self.applyTimes); [[UIApplication sharedApplication] endBackgroundTask:self.backgroundTaskIdentifier]; self.backgroundTaskIdentifier = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{ [self stopSystemTask]; }]; } }
// 开始计时器 - (void)startTimer{ // 开始计时器 申请更多保活时间 self.timer = [NSTimer scheduledTimerWithTimeInterval:5.0 target:self selector:@selector(applyForMoreTime) userInfo:nil repeats:YES]; [self.timer fire]; // 在这里我判断了申请次数,加上第一次申请保活时间的次数 }
// 停止计时器 - (void)stopTimer{ [self.timer invalidate]; self.timer = nil; self.applyTimes = 0; }
// 结束系统任务 - (void)stopSystemTask{ if (self.backgroundTaskIdentifier) { [[UIApplication sharedApplication] endBackgroundTask:self.backgroundTaskIdentifier]; self.backgroundTaskIdentifier = UIBackgroundTaskInvalid; } }
|