iOS的消息推送简析

原文:iOS的消息推送简析

iOS的消息推送简析

消息推送的目的是为了在应用切至后台或手机锁屏情况下,提醒用户当前时间APP内部发生了某事,吸引用户打开APP应用。

消息推送有三种提示类型:

  1. UIUserNotificationTypeBadge:应用的BadgeValue小红圈
  2. UIUserNotificationTypeSound:声音或震动提示
  3. UIUserNotificationTypeAlert:Alert展示Message内容
基于不轻易打扰用户使用的原则,APP消息推送需要征得用户同意,提前进行注册。

是否允许开启推送.png

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
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// 程序启动注册推送
if ([[UIDevice currentDevice].systemVersion floatValue] >= 8.0)
{
UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeBadge |
UIUserNotificationTypeAlert |
UIUserNotificationTypeSound
categories:nil];
[[UIApplication sharedApplication] registerUserNotificationSettings:settings];
}
else
{
[application registerForRemoteNotificationTypes:UIRemoteNotificationTypeAlert |
UIRemoteNotificationTypeBadge |
UIUserNotificationTypeSound];
}
......
return YES;
}
// 在 iOS8 系统中,还需要添加这个方法。通过新的 API 注册推送服务,如果用户不允许推送,程序启动时会调用此方法,获取用户的设置
- (void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings
{
[application registerForRemoteNotifications];
}
// 注册失败调用
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error
{
NSLog(@"远程通知注册失败:%@",error);
}

如果用户选择允许,苹果会根据bundleID和手机UDID生成deviceToken,然后调用 application:didRegisterForRemoteNotificationsWithDeviceToken: 方法获取到devicetoken

1
2
3
4
5
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
{
NSLog(@"test:%@",deviceToken); // 获取设备的Token,获取成功则代表注册成功
// 程序应该将devicetoken发给APP的服务器,服务器将其存储,以便于发送推送消息
}

消息推送分为两种:

  • 本地消息推送(LocalNotification)
  • 离线消息推送(PushNotification或者RemoteNotification)

本地消息推送

  1. 一般用于工具类软件,比如:事务提醒等软件,到某个时间点发出通知提示。
  2. 实时通信软件,通知客户端的形式,比如实时通信软件,收到聊天信息后,通知手机响铃或震动,或修改TabBar的Item的BadgeValue等。

离线消息推送

APP切至后台,Kill掉APP的情况下,提示用户。

收到通知后的回调

收到通知后,单击本地通知后,有两种情况:

  1. 程序从后台切至前台打开(程序未被kill掉)
    收到本地通知就会调用didReceiveLocalNotification代理方法,当应用在前台时,收到本地推送通知也会调用,不过一般不做处理

本地推送在手机的呈现方式.png

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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
- (void) application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification
{
switch (application.applicationState)
{
case UIApplicationStateActive: // 活动在前台,未锁屏情况
{
// 收到本地推送的时候,程序在前台
// 前台的界面的变化代码,一般是TabBar的Item的BadgeValue改变
[self playSoundAndVibration]; // 振铃
return;
}
break;
case UIApplicationStateInactive: // 进入消息通知栏时点击某条消息,退到后台
{
// 收到本地推送的时候,程序在被切至后台
// 具体业务执行代码,跳转或其他
[self playSoundAndVibration]; // 振铃
}
break;
case UIApplicationStateBackground: //程序进入后台
{
NSLog(@"%@",message);
[self playSoundAndVibration]; // 振铃
[self showNotificationWithMessage:message];
}
break;
default:
break;
}
}
- (void)playSoundAndVibration
{
NSTimeInterval timeInterval = [[NSDate date] timeIntervalSinceDate:self.lastPlaySoundDate];
if (timeInterval < kDefaultPlaySoundInterval)
{
// 如果距离上次响铃和震动时间太短, 则跳过响铃
NSLog(@"skip ringing & vibration %@, %@", [NSDate date], self.lastPlaySoundDate);
return;
}
self.lastPlaySoundDate = [NSDate date]; // 保存最后一次响铃时间
[[EMCDDeviceManager sharedInstance] playNewMessageSound]; // 收到消息时,播放音频
[[EMCDDeviceManager sharedInstance] playVibration]; // 收到消息时,震动
}
- (void)showNotificationWithMessage:(EMMessage *)message
{
UILocalNotification *notification = [[UILocalNotification alloc] init]; // 发送本地推送
notification.fireDate = [NSDate date]; //触发通知的时间
notification.alertBody = NSLocalizedString(@"receiveMessage", @"you have a new message");
notification.hasAction = NO;
notification.timeZone = [NSTimeZone defaultTimeZone];
NSTimeInterval timeInterval = [[NSDate date] timeIntervalSinceDate:self.lastPlaySoundDate];
if (timeInterval < kDefaultPlaySoundInterval)
{
NSLog(@"skip ringing & vibration %@, %@", [NSDate date], self.lastPlaySoundDate);
}
else
{
notification.soundName = UILocalNotificationDefaultSoundName;
self.lastPlaySoundDate = [NSDate date];
}
NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
[userInfo setObject:[NSNumber numberWithInt:message.messageType] forKey:kMessageType];
[userInfo setObject:message.conversationChatter forKey:kConversationChatter];
notification.userInfo = userInfo;
[[UIApplication sharedApplication] scheduleLocalNotification:notification]; // 发送通知
[UIApplication sharedApplication].applicationIconBadgeNumber += 1;
}

  1. 程序Kill掉,重新运行

打开程序,都会执行执行application: didFinishLaunchingWithOptions: 回调方法,不同的是,正常启动时launchOptions参数为null,如果其他方式启动launchOptions会带有特定参数。

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
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
if (launchOptions[UIApplicationLaunchOptionsLocalNotificationKey])
{
NSDictionary*userInfo = [launchOptions objectForKey:@"UIApplicationLaunchOptionsRemoteNotificationKey"];
// NSDictionary*userInfo = launchOptions[UIApplicationLaunchOptionsLocalNotificationKey];
// kill掉的程序,单击本地通知打开,会进入这个地方,并将相应的launchOptions传递给程序
// 以便于我们做出对应的跳转等
[self didReceiveRemoteNotification:userInfo];
}
......
return YES;
}
#pragma mark - 杀死进程,离线推送获取的信息
- (void)didReceiveRemoteNotification:(NSDictionary *)userInfo
{
// 方便离线推送消息的解析
NSError *parseError = nil;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:userInfo
options:NSJSONWritingPrettyPrinted error:&parseError];
NSString *str = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:NSLocalizedString(@"apns.content", @"Apns content")
message:str
delegate:nil
cancelButtonTitle:NSLocalizedString(@"ok", @"OK")
otherButtonTitles:nil];
[alert show];
}

AppDelegate中关于推送代理方法的执行情况:

首次安装APP应用:
--> didRegisterForRemoteNotificationsWithDeviceToken被调用;
--> 系统提示用户是否允许开启推送;
--> didRegisterUserNotificationSettings被调用;
非首次启动时:
--> 推送处于拒绝状态:didRegisterUserNotificationSettings被调用;
--> 推送处于允许状态
--> didRegisterForRemoteNotificationsWithDeviceToken 被调用
--> didRegisterUserNotificationSettings 被调用
运行过程中用户修改推送设置:
--> 拒绝 到 允许:didRegisterForRemoteNotificationsWithDeviceToken被调用
--> 允许 到 拒绝:什么也不做

一点疑问???

在Kill掉APP情况,单击本地推送消息,iOS7.0之后不在didFinishLaunchingWithOptions中做处理,只在下面函数做处理,但我实际开发中,没有这样奇怪。

1
2
3
4
5
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
{
// userInfo
}