OC学习53:后台保活方案

张建 lol

前言

实现后台保活方案前,我们需要了解iOS应用程序的 生命周期。因为iOS系统的资源是有限的的,应用程序在前台和后台的状态是不一样的。在后台,程序会受到系统的限制,以提高电池使用和用户体验。

应用程序的生命周期

  1. 应用程序状态
  • Not Running:未运行 - 程序未启动

  • Inactive:未激活 - 程序在前台运行,不过没有接收到事件。在没有事件处理情况下程序通常停留在这个状态

  • Active:激活 - 程序在前台运行且接收到事件。这也是前台的一个正常的模式

  • Background:后台 - 程序在后台而且能执行代码。大多数程序进入这个状态后会在这个状态上停留一会儿。时间到了之后会进入 挂起(Suspended)状态。有的程序经过特殊的请求可以长期处于Background状态

  • Suspended:挂起 - 程序在后台不能执行代码。系统会自动把程序变成这个状态而且不会发出通知。当挂起时,程序还是停留在内存中,当系统内存低时,系统就把挂起的程序清除掉,为前提程序提供更多的内存。

下图是程序状态变化图:

  1. 应用程序状态的代理回调
  • 程序进程启动但还没进入状态保存
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. 应用程序启动的执行顺序
  • 启动程序
1
2
3
willFinishLaunchingWithOptions
didFinishLaunchingWithOptions
applicationDidBecomeActive
  • 按下home键
1
2
applicationWillResignActive
applicationDidEnterBackground
  • 再打开程序
1
2
applicationWillEnterForeground
applicationDidBecomeActive
  1. 应用程序的生命周期
  • 应用程序进入前台

  • 应用程序进入后台

  • 关于 main 函数

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 函数有四个参数,你不需要改变这些参数值,不过我们也需要理解这些参数和程序是如何开始的

argcargv 参数包含了系统带过来的 启动时间。第三个参数确定了主要应用程序类的名称,这个参数指定为nil,这样 UIKit 就会使用默认的程序类 UIApplication。第四个参数是程序自定义的 代理类名,这个类 负责系统和代码之间的交互。它一般在Xcode新建项目时会自动生成。

另外 UIApplicationMain 函数加载了程序主界面的文件。虽然这个函数加载了界面文件,但是没有放到应用程序的windows上,你需要在Delegate的application:willFinishLaunchingWithOptions 方法中加载它。

一个应用程序可以有一个主的storyboard文件或者有一个主的nib文件,但不能同时有两个存在。

如果程序在启动时没有自动加载主要的故事版或nib文件,你可以 application:willFinishLaunchingWithOptions 方法里准备windows的展示。

后台保活方案

iOS 有两种后台运行保活方案:

  • 第一种:无声音乐保活(即在后台开启音频播放,只不过不需要播放出音量且不能影响其他音乐播放软件)

  • 第二种:Background Task

    • iOS 13.0以后只能申请短短的30s

    • 在iOS7.0~iOS13.0以前可以申请3分钟,可以经过处理申请更多的保活时间

  1. 无声音乐保活
  • 第一步:打开应用的 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");
}

  1. 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;
}
}

  • Post title:OC学习53:后台保活方案
  • Post author:张建
  • Create time:2023-05-28 21:07:44
  • Post link:https://redefine.ohevan.com/2023/05/28/OC/OC学习53:后台保活方案/
  • Copyright Notice:All articles in this blog are licensed under BY-NC-SA unless stating additionally.
On this page
OC学习53:后台保活方案