执行后台任务

执行后台任务

iOS在2008年面世的时候,只能够有一个第三方任务处于活动状态(位于前台)。这以为着执行的任务必须在在应用位于前台时候完成,否则任务将处于暂停并且在应用下次启动的时候继续执行。iOS4.0以后向第三方应用提供了后台功能。鉴于iOS设备的系统资源有限,且节省电量至关重要,对后台处理有一些限制,那就是不能干扰前台应用,也不能消耗太多的电量。通过妥善的利用后台功能,应用能够做到很多事情。我们这次来介绍后台任务处理以及如何使用这些方式。

iOS支持两种后台任务处理方式

  • 第一种方式就是在后台完成耗时的任务。这种方式适合用于完成大量数据下载或者更新等任务,他们需要的时间超过了用户与应用交互的时间
  • 第二种方式是执行iOS允许的后台任务,比如播放音乐、与蓝牙交互、监视GPS数据以及获悉位置的重大变化、维持永久性网络连接让VoIP型应用能够正常运行。

注意。“后台任务”在iOS中表达了两种方式,在应用不在前台的时候执行任务、在主线程外异步执行任务

实例应用

在这个应用里面只有两个按钮,分别演示两种方式,分别是应用进入后台的时候完成耗时的任务和在应用位于后台的时候持续播放音频。

检查设备是否支持后台任务

如果能够运行iOS5以上的设备都能够支持后台任务,在Apple 文档中被称为多任务。如果应用需要支持iOS4.0就需要注意了,因为有些设备不能够支持多任务。编写任务的时候需要检查设备是否支持多任务。检查代码:

    UIDevice *device = [UIDevice currentDevice];
    if (![device isMultitaskingSupported]) {
        NSLog(@"你的设备不支持多任务");
        return;
    }
    self.backkgroundTaskButton.enabled = NO;

    NSString *buttonTitle = @"任务正在运行";

    [self.backkgroundTaskButton setTitle:buttonTitle forState:UIControlStateNormal];

    dispatch_queue_t background = dispatch_get_global_queue(0, 0);

    dispatch_async(background, ^{
        [self performBackgroundTask];
    });

要检查设备是否支持多任务,可以调用UIDevice的类方法currentDeveice,以获取有关当前设备的信息,然后调用方法isMultitaskingSupported确定当前设备是否支持多任务,如果支持的话就更新界面并异步调用方法performBackgroundTask以启动后台任务。

在后完成任务

要在后台执行耗时的任务,应该告诉应用这项任务能够在后台运行。另外还应该考虑任务需要多少内存以及需要多长时间才能完成时间。如果任务完成需要时间超过10~15分钟,很可能任务还未完成时候应用就终止了,任务应该包含处理提前终止的逻辑,并能够在应用重新启动后继续执行。操作系统给应用指定一段时间,供它完成后台任务,但如果操作系统发现资源紧缺,也可能提早终止应用。要查看应用的后台任务执行情况,运行任务,然后进入主屏幕让应用推出,直接查看控制台,其中如果不断有应用生成的信息出现就表明应用还在后台执行。

后台任务的通用执行过程如下:

  • 向应用请求后台任务标示符,并制定一个用做终止处理程序的块
  • 仅当任务用完了后台时间,或者系统发现资源使用率过高的时候而终止应用,终止处理程序才会被调用。
  • 执行后台任务逻辑。这包括从请求后台任务标示符到结束后台任务之间的代码
  • 让应用程序结束后台任务,并让后台任务标示符失效。

后台任务标示符

要启动可以在后台继续运行的任务,需要从应用那里获取一个后台任务标示符。后台任务标示符让应用知道哪些任务还在运行,哪些任务已经完成。要告诉应用,任务已经完成,不再需要后台处理,需要用到后台标示符。在performBackgroundTask中,先获取后台任务标示符,再启动要在后台执行的任务

-(void)performBackgroundTask{
    __block UIBackgroundTaskIdentifier bTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
        //coding
    }];
}

获取后台任务标示符时候,应该指定终止处理程序块。这里声明后台任务标示符变量的时候,使用了限定符__block,因为需要在终止处理程序块中修改这个变量。

终止处理程序

操作系统发现应用用完了给他指定的时间或者资源,因此需要关闭的时候,将调用处理程序提供的时间不多(最多只有几秒钟),因此它执行的操作应该尽可能少。

    __block UIBackgroundTaskIdentifier bTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
        NSLog(@"后台任务将被终止");
        NSLog(@"task id = %lu",bTask);

        [[UIApplication sharedApplication]endBackgroundTask:bTask];
        bTask = UIBackgroundTaskInvalid;

    }];

然而,终止处理应用程序至少执行如下操作,对共享应用实例调用方法endBackgroundTask:让应用知道后台任务已经结束;将bTask设置为UIBackgroundTaskInvalid,让后台任务标示符无效,以防止应用不小心再次使用它。

执行后台任务

获取后台标示符后,就可以开始在后台任务执行实际的任务了。在方法performBackgroundTask中,设置了一些变量,以便可以知道从什么地方开始计数,当前的迭代次数,以及在什么地方停止计数,适应类方法standardUserDefaults创建一个NSUserDefaults引用,用于获取最后的计数以及在每次迭代时候存储当前计数

    NSInteger counter = 0;
    NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
    NSInteger startCounter = [userDefaults integerForKey:kLastCounterKey];

在实例应用中,因为后台任务比较小,所以让他在循环中让线程休眠1秒,以此来模拟大量的耗时迭代。它将当前的计数存储到NSUserDefaults中,这样可以在后台任务终止时候记录已经执行到什么地方。可以修改这种逻辑,对任何重复性后台任务进行跟踪。


    NSLog(@"后台任务开始,任务ID是%lu",bTask);
    NSInteger twentyMins = 20;
    for (NSInteger counter = startCounter; counter <= twentyMins; counter++) {
        [NSThread sleepForTimeInterval:1];
        [userDefaults setInteger:counter forKey:kLastCounterKey];
        [userDefaults synchronize];

        NSTimeInterval remaingTime = [[UIApplication sharedApplication]backgroundTimeRemaining];
        NSLog(@"后台任务进度:%lu,后台任务时间剩余:%f",counter,remaingTime);
    }

每次迭代结束后,都从应用哪里获取后台任务余下的时间。可以根据这个来决定时候接着执行其他的迭代。注意:通常在时间剩下的几秒后终止后台任务,让他有时间执行扫尾工作,因此决定是否接着执行余下的迭代时候,必须考虑这一点。

后台任务完成后,更新存储在NSUserDefaults的计数,这样就能够正确的重新执行后台任务;同时更新UI,让用户能够重新执行后台任务。

    NSLog(@"后台任务完成");
    [userDefaults setInteger:0 forKey:kLastCounterKey];
    [userDefaults synchronize];

    dispatch_sync(dispatch_get_main_queue(), ^{
        [self.backkgroundTaskButton setEnabled:YES];
        [self.backkgroundTaskButton setTitle:@"Start Background Task" forState:UIControlStateNormal];
    });

后台任务完成之后,我们还需要做两项重要的工作:让应用刚结束后台任务;让后台任务标示符无效。从获取后台任务标示符到结束后台人物之间的所有代码都将在后台执行。

        [[UIApplication sharedApplication]endBackgroundTask:bTask];
        bTask = UIBackgroundTaskInvalid;

实现后台任务

iOS支持一组非常具体的后台任务活动,这些活动课不断进行下去,不像时候后台标示符那样受到限制。这些活动可以不断地进行下去,就是说可以在时间上不收限制,但是他们不应该使用过多的系统资源,以免应用被操作系统强行终止。

后台活动类型

iOS支持以下的后台活动

  • 在后台播放音频
  • 跟踪设备位置
  • 支持IP语音应用
  • 下载“报刊杂志”应用的新内容
  • 与外置蓝牙设备通信
  • 在后台取回内容
  • 使用推送通知启动后台下载

要支持这些后台任务,应用必须在文件Info.plist中声明这一点。为此,在Xcode中选择应用的目标,然后单击标签Capabilities。将BackgroundModes设置为On,再选择要支持的后台模式对应的复选框。Xcode将在文件Info.plist中添加响应的条目。也可以再会直接编辑文件Info.plist。为此,在Xcode中选择应用的目标,再单击标签Info。找到条目Required Background Modes;如果没有这个条目,将鼠标指向既有的条目,单击嘉豪以新增一个条目,再选择Required Background Modes。这将新增一个数组条目,其中包含一个空的NSString项。选择要支持的后台模式。

创建好Required Background Modes条目后,就可以在应用中添加与后台活动相关的逻辑了,这些逻辑将在应用进入后台后执行。

在后台播放音乐

要在后台播放音乐,首先需要调整应用的视频会话(audio session)设置。默认情况下,应用使用的音频类别为AVAudioSessionCategorySoloAmbient。这确保应用启动之后,将关闭其他音频,而屏幕锁定或者设备静音开启后,应用的音频变成静音。这种音频会话类型不适合用于在后台播放音乐,因为在屏幕锁定或者其他应用进入前台之后,音频将进入静音状态,在ViewDidLoad中,将音频会话类型需改成为AVAvdioSessionCategoryPlayBack,这确保了进入后台或者静音开关已经开启,音频也将继续播放。

    AVAudioSession *session = [AVAudioSession sharedInstance];

    NSError *activeError = nil;

    if (![session setActive:YES error:&activeError]) {
        NSLog(@"音频设备不能打开");
    }
    NSError *categoryError = nil;
    if (![session setCategory:AVAudioSessionCategoryPlayback error:&categoryError]) {
        NSLog(@"不能够设置后台播放类型");
    }

要在后台播放音频,下一步是初始化一个音频播放器。这是在方法viewDidLoad方法中完成的,这样用户指定要播放音频的时候,音频就已经准备就绪了。

    NSError *playerInitError = nil;

    NSString *audioPath = [[NSBundle mainBundle]pathForResource:@"16_audio" ofType:@"mp3"];

    NSURL *audioURL = [NSURL fileURLWithPath:audioPath];

    self.audioPlayer = [[AVAudioPlayer alloc]initWithContentsOfURL:audioURL error:&playerInitError];

用户轻按按钮Play Background Music时候,讲调用方法PlayBackgroundMusicTouched:。这个方法检查当前是否在播放音频,如果在播放就停止播放,并且修改按钮的文本。

if (![self.audioPlayer isPlaying]) {
        [self.audioPlayer stop];
        [self.backgroundMusicButton setTitle:@"播放后台音乐" forState:UIControlStateNormal];
    }else{

    }

如果没有播放,就开始播放音频,并修改按钮的文本。

    if (![self.audioPlayer isPlaying]) {
        [self.audioPlayer stop];
        [self.backgroundMusicButton setTitle:@"播放后台音乐" forState:UIControlStateNormal];
    }else{
        [self.audioPlayer play];
        [self.backgroundMusicButton setTitle:@"停止后台音译额" forState:UIControlStateNormal];
    }

播放音频的时候,用户需要请安设备的锁屏按钮或者主屏幕按钮,让应用进入后台,但是音频将继续播放。在音频播放的期间,如果能够在设备进入锁屏状态时候,在锁屏上显示当前的播放信息,那就太好了。对此,我们创建一个字典,其中包含有关当前播放的媒体项信息。

        UIImage *lockImage = [UIImage imageNamed:@"book_cover"];

        MPMediaItemArtwork *artwork = [[MPMediaItemArtwork alloc]initWithImage:lockImage];
        NSDictionary *mediaDic =
  @{
    MPMediaItemPropertyTitle:@"背景音乐",
    MPMediaItemPropertyMediaType:@(MPMediaTypeAnyAudio),
    MPMediaItemPropertyPlaybackDuration:@(self.audioPlayer.duration),
    MPNowPlayingInfoPropertyPlaybackRate:@1.0,
    MPNowPlayingInfoPropertyElapsedPlaybackTime:@(self.audioPlayer.currentTime),
    MPMediaItemPropertyAlbumArtist:@"eason",
    MPMediaItemPropertyArtist:@"eason",
    MPMediaItemPropertyArtwork:artwork
    };

可选的选项有很多。注意这里指定了标题和图像,他们讲显示在锁定的屏幕上。还指定了时长和当前播放位置,这些值有媒体播放器根据设备状态和当前上下文确定。创建媒体项信息字典后,启动音频播放器,再将当前播放的媒体项信息提供给MPNowPlayingInfoCenter。接下来,将第一响应者设置为self,因为要让媒体播放器的信息中心正确运行,第一响应者必须是播放音频的视图或者视图控制器。然后让应用开始响应“遥控”时间,这让用户能够使用锁定屏幕上的控件(通过委托方法)控制音频播放。


        [[MPNowPlayingInfoCenter defaultCenter]setNowPlayingInfo:mediaDic];
        [self becomeFirstResponder];
        [[UIApplication sharedApplication]beginReceivingRemoteControlEvents];

现在,在后台播放音频时候,锁定屏幕上将显示有关音频的信息。

另外,在控制中心也将显示有关音频的信息。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值