多任务(2)

来源:互联网 发布:砂浆稠度试验数据 编辑:程序博客网 时间:2024/06/15 15:54

3.在后台接收本地通知


3.1. 问题

你希望即使在程序没有运行时,可以向用户展示一个警告。


你希望在没有使用推送消息的情况下,可在程序中创建本地警告。


3.2. 方案


实例化一个 UILocalNotification 对象,然后使用 UIApplication 的scheduleLocalNotification:实例方法安排它:

//badge /bædʒ/ n. 徽章;标记

- (BOOL)localNotificationWithMessage:(NSString *)paramMessage

                   actionButtontitle:(NSString *)paramActionButtonTitle

                         launchImage:(NSString *)paramLaunchImage

                    applicationBadge:(NSInteger)paramApplicationBadge

                      secondsFromNow:(NSTimeInterval)paramSecondsFromNow

                            userInfo:(NSDictionary *)paramUserInfo{

    if ([paramMessage length] == 0) {

        return NO;

    }

    UILocalNotification *notification = [[UILocalNotificationalloc]init];

    notification.alertBody = paramMessage;

    notification.alertAction = paramActionButtonTitle;

    if ([paramActionButtonTitle length] > 0) {

        /* Make sure we have the action button for the user to press

         to open our application 

         确保我们有能让用户点击进入App的按钮*/

        notification.hasAction = YES;

    }else{

        notification.hasAction = NO;

    }

    /* Here you have a chance to change the launch image of your application when the notification's action is viewed by the user 

     改变加载图片

     */

    notification.alertLaunchImage = paramLaunchImage;

    /* Change the badge number of the application once the notification is presented to the user. Even if the user dismisses the notification,the badge number of the application will change

     */

    notification.applicationIconBadgeNumber = paramApplicationBadge;

    /* This dictionary will get passed to your application

     later if and when the user decides to view this notification */

    notification.userInfo = paramUserInfo;

    /* We need to get the system time zone so that the alert view

     will adjust its fire date if the user's time zone changes */

    NSTimeZone *timeZone = [NSTimeZonesystemTimeZone];

    notification.timeZone = timeZone;

    /* Schedule the delivery of this notification x seconds from now */

    NSDate *today = [NSDatedate];

    NSDate *fireDate = [today dateByAddingTimeInterval:paramSecondsFromNow];

    NSCalendar *calendar = [NSCalendarautoupdatingCurrentCalendar];

    NSUInteger dateComponents =NSCalendarUnitYear|NSCalendarUnitMonth|NSCalendarUnitDay|NSCalendarUnitHour|NSCalendarUnitMinute|NSCalendarUnitSecond;

    NSDateComponents *components = [calendar components:dateComponents fromDate:fireDate];

    /* Here you have a chance to change these components. That's why we retrieved the components of the date in the first place. */

    fireDate = [calendar dateFromComponents:components];

    /* Finally set the schedule date for this notification */

    notification.fireDate = fireDate;

    [[UIApplicationsharedApplication]cancelAllLocalNotifications];

    [[UIApplicationsharedApplication]scheduleLocalNotification:notification];

    return YES;

}


3.3. 讨论
本地通知是一个警告视图(UIAlertView 类型的对象),在你的程序运行于后台时或者 根本没运行的情况下展现给用户。你可以使用 UIApplication 的scheduleLocalNotification: 实例方法安排本地通知的发送,cancelAllLocalNotifications 实例方法取消所有进行中的本地通知的发送。



你也可以要求 iOS 将来即使在程序没有运行时向用户发送本地通知。这些通知可以是重复的--比如,每周的某个时间。但是,在你指定通知的触发日期时必须特别小心。

举例来说,假设现在是伦敦时间 13:00,现在的时区是 GMT+0,你的程序正运行在用户 的设备上。你想在 14:00 向用户发送通知,即使那时程序已退出。现在你的用户在伦敦盖特 维克机场飞往斯德哥尔摩的飞机上,斯德哥尔摩的时区是 GMT+1.如果飞行需要 30 分钟, 用户到达斯德哥尔摩的时间将是 GMT+0 时区 13:00。但是,当它到达时,iOS 设备会侦测到 时区的变化,然后将用户设备的时间调到了 14:30。你的通知本应该在 14:00(GMT+0) 时发生,所以当时区变化时,iOS 设备检测到通知应该被显示了(晚了 30 分钟,实际上是新时区),然后显示该通知。

问题是你的通知本来应该在 14:00 GTM+0 或者 15:00 GTM+1 显示,而不是 14:30 GTM+1。为了处理这类情况(由于现代的旅行习惯,它发生几率比你认为的更频繁),当 你为通知指定了触发日期和时间,你还应该为这个时间指定时区。


前面的代码并不包括警告视图,你需要编写它来向用户显示些东西。我们继续向程序添加代码,看看在不同情景下面 iPhone 模拟器会发生什么。现在我们要到 application:didReceiveLocalNotification:方法中,看看是否有本地通知唤醒了我们的应用。如果没有的话,我们安排一个:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

    id scheduledLocalNotification = [launchOptionsvalueForKey:UIApplicationLaunchOptionsLocalNotificationKey];

    if (scheduledLocalNotification != nil) {

        /* We received a local notification while our application wasn't running. You can now typecase the ScheduledLocalNotification variable to UILocalNotification and use it in your application

         */

        NSString *message =@"Local Notification Woke Us Up";

        [[[UIAlertViewalloc]initWithTitle:@"Notification"message:message delegate:nilcancelButtonTitle:@"OK"otherButtonTitles:nil,nil]show];

    }else{

        NSString *message =@"A new instant message is available. Would you like to read this message?";

        /* If a local notification didn't start our application,

         then we start a new local notification */

        [selflocalNotificationWithMessage:message actionButtontitle:@"Yes"launchImage:nilapplicationBadge:1secondsFromNow:10.0fuserInfo:nil];

        message = @"A new Local Notification is set up to be displayed 10 seconds from now";

        [[[UIAlertViewalloc]initWithTitle:@"Set Up"message:message delegate:nilcancelButtonTitle:@"OK"otherButtonTitles:nil,nil]show];

    }

    return YES;

}

/接受本地推送

//最后强调一点,我们会在程序中处理 UIApplicationDelegate application:didReceiveLocalNotification:方法,来确保用户打开应用时,因为收到本地通知而向她显示了一条消息:

- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification{

    NSString *message =@"The Local Notification is delivered.";

    [[[UIAlertViewalloc] initWithTitle:@"Local Notification"message:message

                               delegate:nil

                      cancelButtonTitle:@"OK"

                      otherButtonTitles:nil,nil] show];

}




第一步:创建本地推送

// 创建一个本地推送  UILocalNotification *notification = [[[UILocalNotification alloc] init] autorelease];  //设置10秒之后  NSDate *pushDate = [NSDate dateWithTimeIntervalSinceNow:10];  if (notification != nil) {      // 设置推送时间      notification.fireDate = pushDate;      // 设置时区      notification.timeZone = [NSTimeZone defaultTimeZone];      // 设置重复间隔      notification.repeatInterval = kCFCalendarUnitDay;      // 推送声音      notification.soundName = UILocalNotificationDefaultSoundName;      // 推送内容      notification.alertBody = @"推送内容";      //显示在icon上的红色圈中的数子      notification.applicationIconBadgeNumber = 1;      //设置userinfo 方便在之后需要撤销的时候使用      NSDictionary *info = [NSDictionary dictionaryWithObject:@"name"forKey:@"key"];      notification.userInfo = info;      //添加推送到UIApplication             UIApplication *app = [UIApplication sharedApplication];      [app scheduleLocalNotification:notification];   }  
第二步:接收本地推送

- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification*)notification{      UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"iWeibo" message:notification.alertBody delegate:nil cancelButtonTitle:@"确定" otherButtonTitles:nil];      [alert show];      // 图标上的数字减1      application.applicationIconBadgeNumber -= 1;  }  
第三步:解除本地推送

// 获得 UIApplication  UIApplication *app = [UIApplication sharedApplication];  //获取本地推送数组  NSArray *localArray = [app scheduledLocalNotifications];  //声明本地通知对象  UILocalNotification *localNotification;  if (localArray) {      for (UILocalNotification *noti in localArray) {          NSDictionary *dict = noti.userInfo;          if (dict) {              NSString *inKey = [dict objectForKey:@"key"];              if ([inKey isEqualToString:@"对应的key值"]) {                  if (localNotification){                      [localNotification release];                      localNotification = nil;                  }                  localNotification = [noti retain];                  break;              }          }      }      //判断是否找到已经存在的相同key的推送      if (!localNotification) {          //不存在初始化          localNotification = [[UILocalNotification alloc] init];      }      if (localNotification) {          //不推送 取消推送          [app cancelLocalNotification:localNotification];          [localNotification release];          return;      }  }


4.在后台播放声音

4.1. 问题
你在写一个播放声音文件的程序(比如一个音乐播放器),你希望即使程序在后台运行

4.2. 方案
在你程序的主.plist(main .plist)创建一个新的 array 键。设置键的名字为 UIBackgroundModes(instead of    Required background modes)将 audio (App plays audio or streams audio/video using AirPlay)赋值给这个新的键。下面是一个.plist 文件内容的例子,加入了前面提到的键和值:时,也能播放声音文件。


现在你可以使用 AV Fondation 来播放声音文件了,即使程序在后台,你的声音文件还是会被播放。

4.3. 讨论
在 iOS 中,即使程序在后台运行,他还是可以请求继续播放声音文件。AV Foundation 的 AVAudioPlayer 是个易于使用的播放器,我们将在本节中使用它。我们的任务是启动一个音频播放器并播放一首简单的歌曲,当歌曲在播放时,按下 Home 按钮将程序转入后台。如果我们在程序的.plist 文件中包含了 UIBackgroundModes 键,iOS 将继续播放音频播放器正在播放的音乐,即使程序在后台。当程序在后台时,程序应该只播放音乐以及音乐播放器运行时必要的数据。我们不应该运行其他任何的任务,比如显示新的屏幕等等。


#import <UIKit/UIKit.h>

#import <AVFoundation/AVFoundation.h>

@interface ViewController :UIViewController<AVAudioPlayerDelegate>

@property (nonatomic,strong) AVAudioPlayer *audioPlayer;

@end



当我们的程序被打开时,我们会分配和初始化我们的音频播放器,读取一个名为 MySong.mp4 的文件的内容到 NSData 的实例中,并在我么的音频播放器的初始化过程中使 用这个数据:


- (void)viewDidLoad {

    [superviewDidLoad];

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^{

        NSError *audioSessionError = nil;

        AVAudioSession *audioSession = [AVAudioSessionsharedInstance];

        if ([audioSession setCategory:AVAudioSessionCategoryPlaybackerror:&audioSessionError]) {

            NSLog(@"Successfully set the audio session.");

        }else{

            NSLog(@"Could not set the audio session");

        }

        NSString *filePath = [[NSBundlemainBundle]pathForResource:@"MySong"ofType:@"mp3"];

        NSData *fileData = [NSDatadataWithContentsOfFile:filePath];

        NSError *error = nil;

        self.audioPlayer = [[AVAudioPlayeralloc]initWithData:fileDataerror:&error];

        if (self.audioPlayer !=nil) {

            self.audioPlayer.delegate =self;

            if ([self.audioPlayerprepareToPlay] && [self.audioPlayerplay]) {

                NSLog(@"Successfully started playing...");

            }else{

                NSLog(@"Failed to play.");

            }

        }

    });

}



5.在后台处理位置变化

5.1. 问题
你在写一个程序,它的主要功能是使用 Core Location 来处理位置变化。你希望即使程序在后台时,也能获得 iOS 设备位置变化的通知。


5.2. 方案
在你的主程序.plist 文件(main application .plist file)中,向 UIBackgroundModes 键中添加 location (App registers for location updates)值,如下:


5.3. 讨论
当你的程序在前台运行时,你可以从一个 CLLocationManager 实例获得委托消息,在 iOS 检测到设备移动到新位置时告诉你这一变化。但是,如果你的程序被送到后台并且不再 处于活动状态,这些位置委托消息一般是不会被发给你的程序的。实际上,他们会在你的程 序回到前台后,批量的被发送。
如果在后台时,你仍然想能够获得用户设备位置改变的消息,你就必须在你的主程序.plist文件(main application .plist file)中,向UIBackgroundModes键中添加location值, 就像本小节的“方案”中指出的那样。这样一旦程序转到后台,你的程序将不断的获得设备位置变化的消息。让我们在一个仅有应用程序委托的简单程序中测试一下。
在这个应用中,我打算在 app 委托中保存一个布尔值,我将它取名为 executingInBackground。当程序退到后台,我将他设置为 YES,回到前台时,我会将它设置 为 NO。当我们得到 Core Location 的位置更新消息,我会检查这个标志,如果此标志为 YES,我们就不做任何密集计算或者 UI 更新,因为,我们的程序在后台,作为一个负责的程序员,我们不应该在程序运行在后台时,做开销很大的处理。但是如果我们的应用在前台,我们就拥有设备所有的处理能力来做我们想做的一般处理。程序在前台或是后台时,我们也会尝试得到最佳的位置变化精度,我们将确定在位置更新时请求更低的精度,来减轻位置感应器的压力。让我们继续定义我们的 app 委托吧:

#import <CoreLocation/CoreLocation.h>

CLLocationManagerDelegate

@property (nonatomic,strong) CLLocationManager *myLocationManager;

@property (nonatomic,unsafe_unretained,getter=isExecutingInBackground)BOOL executingInBackground;



.m

- (void)createLocationManager{

    //创建和启动位置管理器

    self.myLocationManager = [[CLLocationManageralloc]init];

    //desired 渴望的  accuracyn /'ækjʊrəsɪ/ 精确(),准确()

    self.myLocationManager.desiredAccuracy =kCLLocationAccuracyBest;

    self.myLocationManager.delegate =self;

    [self.myLocationManagerstartUpdatingLocation];

    

}

你可以看到,我们将位置管理器的要求精度设为了最高级别。但是,当我们退到后台 时,希望降低这个精度来让 iOS 稍稍休息一下:

- (void)applicationDidEnterBackground:(UIApplication *)application{

    self.executingInBackground =YES;

    self.myLocationManager.desiredAccuracy =kCLLocationAccuracyHundredMeters;

}

当我们的应用从后台激活时,我们可以将精度改到最高级别:

- (void)applicationWillEnterForeground:(UIApplication *)application{

    self.executingInBackground =NO;

    self.myLocationManager.desiredAccuracy =kCLLocationAccuracyBest;

}

而且,当程序运行在后台时,我们希望避免在从位置管理器得到新位置时进行密集处理,所以我们需要对位置管理器的 locationManager:didUpdateToLocation:fromLocation: 委托方法做如下处理:

- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations{

    if ([selfisExecutingInBackground]) {

        /* We are in the background. Do not do any heavy processing */

    }else{

        /* We are in the foreground. Do any processing that you wish */

    }

}

简单的法则是,当我们在后台时,我们应该使用尽可能少量的内存和处理器能力来满足 程序的需要。所以,通过在后台时降低位置管理器的精度,我们就减少了对 iOS 必须向我们 程序发送新位置消息的处理。



6.保存和加载多任务的iOS程序的状态

6.1. 问题
你希望在程序被送到后台时保存你 iOS 应用的状态,并在程序回到前台时恢复到和之前相同的状态。

6.2. 方案

使用发送到你的应用程序委托的 UIApplicationDelegate 协议消息,和 iOS 发送消息的组合,来保存你的多任务程序的状态。

6.3. 讨论


a.



一个空的 iOS 程序(仅有一个窗口没有其他任何代码的程序)在支持多任务的 iOS 设备上第一次运行时,下列的 UIApplicationDelegate 消息将被发送给你的应用程序委托,以下面的顺序:

1. application:didFinishLaunchingWithOptions: 

2. applicationDidBecomeActive:

果用户按下了 iOS 设备上的 Home 按钮,你的应用程序委托将收到按顺序下列消息:

1. applicationWillResignActive:
2. applicationDidEnterBackground:



旦程序在后台,用户可以按下 Home 按钮两次,从后台程序列表中选择我们的程序。 程序一旦再次回到前台,我们将以下面的顺序在应用程序委托中收到这些消息:

1. applicationWillEnterForeground: 

2. applicationDidBecomeActive:


当我们的程序被送到后台或回到前台时,除了这些消息,我们还会从 iOS 收到各种通知消息。

为了保存和加载你的应用的状态,你需要细心考虑需要在转入后台时暂停并在回到前台时恢复的任务。让我给你个例子。如下节所提到的,网络间接可以被系统本身轻易的恢复,所以我们大概不必再从网络下载文件的情况下做些特别的事情。但是,举个例子,如果你在写一个游戏,你最好在你的程序被送到后台时监听 iOS 发送的通知,然后相应的行动。在那种情况下,你可以简单的将游戏引擎置为暂停状态。如果有必要,你也可以将声音引擎暂停。

b.


在程序被送往后台之后,你有 10 秒钟来保存任何为保存的数据,以及为任何时候用户 将程序带回前台做好准备。如果需要的话,你可以选择要求更多的执行时间(关于这点更多的信息参见 2 小节)。


让我们用一个例子展示如何保存程序的状态。假设我们在为 iOS 编写一个游戏。当我们的游戏被送到后台,我们想:
1.将游戏引擎置为暂停状态。
2.将用户的分数保存到磁盘。 

3.保存当前关卡的数据到磁盘。这包括用户在关卡中的位置,关卡的物理参数,照相机位置,等等。

当用户重新打开程序,将程序带回前台,我们想: 

1.从磁盘加载用户的分数。 

2.从磁盘加载上次用户在玩的关卡。

3.恢复游戏引擎


c.

现在假设我们的应用程序委托就是游戏引擎。然我们在它的头文件中定义一些方法:


.h

#import <UIKit/UIKit.h>


@interface AppDelegate :UIResponder <UIApplicationDelegate>


@property (strong,nonatomic) UIWindow *window;

/* Saving the state of our app */

- (void)saveUserScore;

- (void)saveLevelToDisk;

- (void)pauseGameEngine;

/* Loading the state of our app */

- (void) loadUserScore;

- (void) loadLevelFromDisk;

- (void) resumeGameEngine;


@end


.m

在我们的应用程序委托中我们将对这些方法进行站位实现(place stub implementations)。

- (void) saveUserScore{

    /* Save the user score here */

}

- (void) saveLevelToDisk{

    /* Save the current level and the user's location on map to disk */

}

- (void) pauseGameEngine{

    /* Pause the game engine here */

}

- (void) loadUserScore{

    /* Load the user's location back to memory */

}

- (void) loadLevelFromDisk{

    /* Load the user's previous location on the map */ }

- (void) resumeGameEngine{

    /* Resume the game engine here */

}


现在我们需要确定我们的程序能够处理中断,比如在 iPhone 上接到来电。在那种情况 下,我们的应用不会被送到后台,而是会变为非激活状态。比如,当用户结束通话,iOS 会 将程序重新设置为激活状态。所以当我们的程序变为非激活时,我们要确保正在暂停游戏引 擎,当程序重新激活时,我们可以恢复游戏引擎。我们不需要在程序变为非激活时向磁盘保存任何东西(至少在本例中),因为 iOS 将在程序重新激活时将它置为之前的状态:

- (void)applicationWillResignActive:(UIApplication *)application {

    [selfpauseGameEngine];

}

- (void)applicationDidBecomeActive:(UIApplication *)application {

    [selfresumeGameEngine];

}

现在,当我们的应用被送到后台,我们将保存游戏的状态,在程序回到前台时,我们将重新加载程序状态:

- (void)applicationDidEnterBackground:(UIApplication *)application {

    [selfsaveUserScore];

    [selfsaveLevelToDisk];

    [selfpauseGameEngine];

}


- (void)applicationWillEnterForeground:(UIApplication *)application {

    [selfloadUserScore];

    [selfloadLevelFromDisk];

    [selfresumeGameEngine];

}


不是所有程序都是游戏。但是,在 iOS 的多任务环境中,你可以使用这项技术来保存和加载程序的状态。











0 0
原创粉丝点击