iOS APNs: 处理数据

简介

博文
iOS 细说推送: 远程推送
iOS 细说推送: 本地推送

分别介绍了 iOS 的两种典型推送, 当然, iOS 还有很多类型的推送, 如静默推送, VOIP 推送(iOS8)等, 后面会慢慢介绍给大家.

今天跟大家聊聊关于如何处理推送的数据(payload).

该系列博客:

推送的代理回调时机

还记得 AppDelegate 里面关于推送的几个代理方法吧.
~ 估计你也忘记了, 没关系, 我们再一起整理一下.

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo {
MZLOG(@"markApp push. RemotePush userInfo: %@", userInfo);
// 可以根据推送内容决定下一步的行为
}

该方法在接收到 RemotePush 的时候, 调用时机:

  1. APP 在前台运行的, RemotePush 被推送过来了.
  2. APP 在后台运行, 无论是否被挂起只要没有被杀死, 点击推送内容.

以上两种情况均可以进入 didReceiveRemoteNotification 方法.

- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification {
MZLOG(@"App push. LocalPush notification: %@", notification);
// 可以根据推送内容决定下一步的行为
}

该方法接收到 LocalPush 的时候, 调用时机:

  1. APP 在前台运行的, LocalPush 被推送过来了.
  2. APP 在后台运行, 无论是否被挂起只要没有被杀死, 点击推送内容.

以上两种情况均可以进入 didReceiveLocalNotification 方法.

这两个回调方法, 分别接收 RemotePush 和 LocalPush 的推送消息.

有些人会问了, 假如这个时候我的应用在后台, 被系统杀死了或者被用户双击 Home 键杀死了, 此时远程推送过来了, 或者状态栏里面有本地推送的消息, 我点击推送消息, 这两个方法会被调用吗?

类似这种效果, 上面是 RemotePush, 下面是 LocalPush.

1

我肯定的告诉大家, 应用被杀死的情况下, 点击推送内容, didReceiveRemoteNotificationdidReceiveLocalNotification 都不会被调用.

这个时候, AppDelegate 中的另一个方法上场了.

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

看一下 didFinishLaunchingWithOptions 的具体实现, 示例代码如下:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
/// 将应用图标的 badge 清零
{
[[UIApplication sharedApplication] setApplicationIconBadgeNumber:1];
[[UIApplication sharedApplication] setApplicationIconBadgeNumber:0];
[[UIApplication sharedApplication] cancelAllLocalNotifications];
}
if (nil != launchOptions) {
/// 处理 LocalPush
{
// 这里可以得到 UILocalNotification 对象
id localPushNotify = [launchOptions objectForKey:UIApplicationLaunchOptionsLocalNotificationKey];
MZLOG(@"AppDelegate localPush: %@", localPushNotify);
if (nil != localPushNotify) {
if ([localPushNotify isKindOfClass:[UILocalNotification class]]) {
// 获取 userinfo 数据
NSDictionary *userInfo = [(UILocalNotification *)localPushNotify userInfo];
MZLOG(@"AppDelegate localPush of UILocalNotification: %@", userInfo);
// 根据 userInfo 的内容处理如页面跳转等
}
}
}
/// 处理 RemotePush
{
NSDictionary *remotePushNotify = [launchOptions objectForKey:UIApplicationLaunchOptionsRemoteNotificationKey];
MZLOG(@"AppDelegate remotePush: %@", remotePushNotify);
if (nil != remotePushNotify) {
NSDictionary *remoteAps = [remotePushNotify objectForKey:@"aps"];
MZLOG(@"AppDelegate remotePush. The aps' info, alert: %@, badge: %@, sound: %@",
[remoteAps objectForKey:@"alert"],
[remoteAps objectForKey:@"badge"], [remoteAps objectForKey:@"sound"]);
// 根据推送的内容处理如页面跳转等
}
}
}
MZLOG(@"AppDelegate. launchOptions: %@", launchOptions);
return YES;
}

对应的打印输出内容, 如下

本地推送的内容:

AppDelegate localPush of UILocalNotification: {
"user_info_key" = "user_info_value_json_str";
}

远程推送的内容:

aps = {
alert = "Testing.. (2)";
badge = 1;
sound = default;
};
The aps' info, alert: Testing.. (19), badge: 1, sound: default

自定义推送内容

对于推送的数据格式, 是苹果规定的格式, 我们可以在其基础上添加我们需要的数据.

在介绍下面内容之前, 先了解一下什么是 payload.

payload 是推送通知的一部分,每一条推送通知都包含一个 Payload.
它包含了系统提醒用户通知到达的方式,还可以添加自定义的数据, 即通知主要传递的数据为 payload.

Payload 本身为 JSON 格式的字符串,它内部必须要包含一个键为 aps 的字典.也就是说 payload 是整个字符串.

关于 payload 的限制

在早期的推送中, payload 不能超过 256bytes, 中间还经历过推送的 payload 最大为 2KB.
现在苹果支持最大为5KB(VOIP 推送), 官方文档有说, 如下:

For regular remote notifications, the maximum size is 4KB (4096 bytes)
For Voice over Internet Protocol (VoIP) notifications, the maximum size is 5KB (5120 bytes)
NOTE
If you are using the legacy APNs binary interface to send notifications instead of an HTTP/2 request, the maximum payload size is 2KB (2048 bytes)

上面是 官方文档 的原文, 意思是, 现在 APNs 支持 payload 为4KB, 如果是 VoIP 推送的话, 支持5KB. 但是, 如果你使用传统的 APNs 而不是使用 HTTP/2 的话, 最大支持2KB. 也就是说, 推送的 payload 大小和 iOS 系统无关, 而是和协议有关.

可以使用下面代码, 查看 payload 长度

payload.getBytes().length

我们正常的推送内容是这样的, 正常格式:

{"aps":{"alert":"Testing.. (21)","badge":1,"sound":"default"}}

如果想对推送内容做自定义, 可以自己添加额外的数据, 如

{"aps":{"alert":"Testing.. (21)","badge":1,"sound":"default"}, "ext":{"key":"value"}}

其中, ext 就是额外添加的数据模型.

在 APP 收到这样的数据模型时, 可以对应不同的行为.

大家可以根据自己的业务需求, 自行定义数据格式.

自定义的数据格式, 注意内容的长度, 在不同的 iOS 系统上面, 对于推送的内容是有长度限制的.

再唠叨几句

关于 iOS7 以后添加的方法

- (void)application:(UIApplication *)application
didReceiveRemoteNotification:(NSDictionary *)userInfo
fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler

这个方法也是在 AppDelegate 中实现的.

- (void)application:(UIApplication *)application
didReceiveRemoteNotification:(NSDictionary *)userInfo

这个方法在 iOS3 就已经有了, 可谓是历史悠久.我姑且称之为该方法为 FatherMethod, 上面那个新加入的称之为 SonMethod.

注意事项:

[1]. 实现了 FatherMethod, 即使你实现了 SonMethod, SonMethod 也不会被调用.
[2]. SonMethod 比 FatherMethod 多了一个参数 completionHandler, 这个在后面会说到.主要用于 Background Mode 中的 Background Fetch.
[3]. 推送被调用的时机, 除了 FatherMethod 的时机外, 还多了一种时机, 就是在 APP 被杀死后, 点击推送内容打开 APP, 此时 APP 再次回到前台的时候, 该方法也会被调用(didFinishLaunchingWithOptions也会调用), 所以在处理远程推送内容的时候, 要注意这一点.

针对注意事项[3], 目前我能给出的有两种解决方案:

第一, 判断是否是第一次启动 APP, 如果是第一次启动 APP, 统一在didFinishLaunchingWithOptions 中处理, 不在 SonMethod 中处理.

第二, 远程推送统一在 SonMethod 中处理, didFinishLaunchingWithOptions 中只处理在 APP 被杀死的情况下, 用户点击推送本地的内容.

我个人采用的是第二中方案.

感谢

在推送工具 NWPusher 和 Xcode 的 Window/Devices/Console 帮助下, 才得以完善博文.

NWPusher 用来发送推送内容.

Window/Devices/Console 用于查看输出日志.

必看文档

Creating the Remote Notification Payload

Payload Key Reference

坚持原创技术分享,您的支持将鼓励我继续创作!