通过 PhoneGap 使用 Apple 推送通知

 要求

其他必要产品

本文至少需要具备中级 PhoneGap 开发经验。

用户级别

中级

范例文件

  • PhoneGap Apple Push Notifications Plugin on GitHub

在这篇文章中,我将会介绍如何在 PhoneGap 移动应用程序中使用 Apple Push Notifications (APNs)。推送通知与本地通知的区别在于,推送通知是从第三方服务器向用户发送通知,而本地通知则由应用程序调度并在设备本身运行,不存在任何服务器交互。

例如,您可能会收到 Facebook 发送的大量推送通知,通知您某人将您加为好友,或者如果您是《填字游戏》(Words With Friends) 玩家,则可能会收到推送通知,表明轮到您填字了。本地通知的一个典型例子是,如果您为一个任务设置了日期/时间,系统会在一定的时间或以一定的时间间隔弹出提示进行提醒;在特定的时间会有提示弹出来对您进行提醒。对于*终用户而言,两种通知的显示形式完全相同,它们都会弹出提示,并且可以伴有相关声音及其他功能,但从开发角度来讲,两者却截然不同。

在 iOS 中,无论是本地通知还是推送通知都要用到 Cordova/PhoneGap 插件,但本系列教程将重点介绍如何开发推送通知。如果想要了解如何开发 Android 推送通知,这里还提供了推送通知的概念,但设置和过程略有不同,我们将会在另一篇文章中进行介绍。

以创建 APN 开启整个过程起初可能会觉得有些恐怖,但花时间开展这项工作*对值得,因为用例无穷无尽。本文旨在帮助您了解整个过程,包括创建过程以及应用程序和服务器端发生的事情,通过示例代码让您快速起步。

APN 工作流程说明

要开始操作,*好了解 Apple Push Notifications (APN) 的工作流程。

  • 一经启动,您的应用程序就会与 Apple Push Notification Service 通信,授权它接收推送通知。
  • Apple 会返回一个独特的令牌,供用户在所有未来通信中用来接收推送通知。
  • 您的应用程序将该令牌发送至第三方服务器(您自身的提供商或其他某个提供商,如 Urban Airship)进行存储,以便在日后需要向应用程序发送通知时使用。

图 1. Apple Push Notifications 的工作流程。

图片制作者:Ray Wenderlich。

SSL 证书和配置

不要让这个步骤吓住,一旦您开始操作,就会发现它并不那么可怕。Ray Wenderlich 创作了一篇不错的文章,Apple Push Notification Services 教程:第 1 部分(共 2 部分),其中记录了这个过程,并配有屏幕截图和细节信息,您将会在执行此步骤期间用到。

下面概括介绍一下您应当执行哪些操作,使应用程序启用推送通知。首先,您的应用程序需要使用 App ID (com.mysite.myapp) 通过 Apple iOS Provisioning Portal 启用推送通知,并通过包含支持推送功能的应用程序标识符的配置文件进行签名。接下来,您还需要将 App ID 与 SSL 证书关联起来,以便安全地与 Apple Push Notification Server 进行通信。当您通过该门户配置 App ID 时,向导将提示您创建 SSL 证书,该证书将与您的 App ID 关联,用于开展通信。与 App ID 关联可确保从您的服务器发送到 Apple APN Server 的通知仅会发送到 ID 匹配的应用程序。

证书流程完成后,为这个包含有效推送通知的新 App ID 下载一个新的配置文件。然后,将它拖动到 XCode 中,确保它就是您在 Code Signing 屏幕(位于项目的 “Build Settings” 下)中为您的应用程序选择的配置文件。

创建您的应用程序

本文假设您了解如何创建项目,包括 PhoneGap/Cordova,如果您不了解相关知识,请参阅PhoneGap/Cordova 网站的“iOS 入门”部分。现在,您已经准备好开始执行 HTML/JavaScript/PhoneGap 应用程序编码,我们来执行以下步骤创建该应用程序。

  1. 从 GitHub 获取*新的 PhoneGap Push Notifications 插件。
  2. 将 PushNotification 文件夹拖放到 XCode 中的 Plugins 文件夹。在复制选项中,选择 Create groups for any added folders,如屏幕截图中所示。

图 2. 创建您的应用程序。

  1. 离开 XCode,进入 Finder,并将 PushNotification.js 文件复制到 www\plugins(www 文件夹下的插件子文件夹)。它将自动出现在您的 XCode 中。
  2. 添加脚本标记,以便在 HTML 文件中引用 PushNotification.js 文件,如下所示:
    <script src=”js/plugins/PushNotification.js”></script>
  3. 将插件密钥和值添加到您的 Cordova.plist(在项目资源根文件夹下查找此文件)。

图 3. 将插件添加到您的Cordova.plist。

Cordova PushNotification 插件为您提供了一个很好的 JavaScript API,您可以从 HTML/JS 文件使用它来与底层本机代码进行交互,以处理注册并接收推送通知。部分函数如下所示:

  • registerDevice()
  • setApplicationIconBadgeNumber()
  • getRemoteNotificationStatus()
  • getPendingNotifications()

尽管如此,要使用 JavaScript API,您首先需要向本机应用程序代码中添加一些内容,以便连接特定的推送通知代码。应用程序的 AppDelegate 类(位于项目类文件夹下)用于实现应用程序级事件(如应用程序启动、终止等)处理程序。您还可以使用其他事件处理通知。您可以在 Handling Notifications 引用中查看事件列表。

默认情况下不会实现这些方法;不过,为了提供支持,您必须添加代码处理程序,并将它们委托给插件代码中纳入的 PushNotification.m 类。要添加的代码会显示在该插件的 README 文件中,但我们也会在这里进行讨论。您当前基本上需要处理以下三种事件:

  • didReceiveRemoteNotification
  • didRegisterForRemoteNotificationsWithDeviceToken
  • didFailToRegisterForRemoteNotificationsWithError

在 AppDelegate.m 类的 @end 前面添加下列代码块,以处理通知事件:

/* START BLOCK */
#pragma PushNotification delegation

– (void)application:(UIApplication*)app
didRegisterForRemoteNotificationsWithDeviceToken:(NSData*)deviceToken
{
PushNotification* pushHandler = [self.viewController getCommandInstance:@”PushNotification”];
[pushHandler didRegisterForRemoteNotificationsWithDeviceToken:deviceToken];
}

– (void)application:(UIApplication*)app didFailToRegisterForRemoteNotificationsWithError:(NSError*)error
{
PushNotification* pushHandler = [self.viewController getCommandInstance:@”PushNotification”];
[pushHandler didFailToRegisterForRemoteNotificationsWithError:error];
}

– (void)application:(UIApplication*)application didReceiveRemoteNotification:(NSDictionary*)userInfo
{
PushNotification* pushHandler = [self.viewController getCommandInstance:@”PushNotification”];
NSMutableDictionary* mutableUserInfo = [userInfo mutableCopy];

// Get application state for iOS4.x+ devices, otherwise assume active
UIApplicationState appState = UIApplicationStateActive;
if ([application respondsToSelector:@selector(applicationState)]) {
appState = application.applicationState;
}

[mutableUserInfo setValue:@”0″ forKey:@”applicationLaunchNotification”];
if (appState == UIApplicationStateActive) {
[mutableUserInfo setValue:@”1″ forKey:@”applicationStateActive”];
[pushHandler didReceiveRemoteNotification:mutableUserInfo];
} else {
[mutableUserInfo setValue:@”0″ forKey:@”applicationStateActive”];
[mutableUserInfo setValue:[NSNumber numberWithDouble: [[NSDate date] timeIntervalSince1970]] forKey:@”timestamp”];
[pushHandler.pendingNotifications addObject:mutableUserInfo];
}
}
/* STOP BLOCK */

从本质上而言,上面的代码用于创建 PushNotification 类引用。它会根据发生的事件设置或读取某些值,或者通过参数调用不同的方法。就算对 Objective-C 一无所知或者认为此处十分复杂也不用担心,我在“要求”部分提供了示例项目供您下载参考。

*后,向同一个 AppDelegate.m 类的 didFinishLaunchingWithOptions 方法中添加代码片段,以便从通知打开该应用程序(并将收到的对象添加到 pendingNotifications 供日后检索)。在结尾的 return YES前面添加此代码块,如下所示:

/* Handler when launching application from push notification */
// PushNotification – Handle launch from a push notification
NSDictionary* userInfo = [launchOptions objectForKey:UIApplicationLaunchOptionsRemoteNotificationKey];
if(userInfo) {
PushNotification *pushHandler = [self.viewController getCommandInstance:@”PushNotification”];
NSMutableDictionary* mutableUserInfo = [userInfo mutableCopy];
[mutableUserInfo setValue:@”1″ forKey:@”applicationLaunchNotification”];
[mutableUserInfo setValue:@”0″ forKey:@”applicationStateActive”];
[pushHandler.pendingNotifications addObject:mutableUserInfo];
}
/* end code block */

PushNotification 插件 – JavaScript API

现在,您已经完成了项目创建,包括上面的本机 Objective-C 处理程序,可以实际使用 Cordova PushNotification JavaScript 界面开始编码。下面是有关部分内容的进一步详情,以及以代码的形式开展互动的例子。还有一些其他方面,但我并未在本文中进行具体介绍。

注册设备

PhoneGap PushNotification 插件提供了 registerDevice() API,用于通过 Apple Push Notification Service 注册您的应用程序以接收推送通知。在该函数中,您可以明确指定支持哪些类型的通知 (alerts/badges/sounds)。结果会返回一个独特的设备令牌,以便服务器端以此向该设备发送通知。Apple 建议您每次在设备上运行推送通知时均注册应用程序使用推送通知,因为令牌可能会发生变化。

示例项目中提供了一个 registerDevice() 函数示例,您可以在本文开头的“要求”部分进行下载。我在下面展示了这个代码。

var pushNotification = window.plugins.pushNotification;
pushNotification.registerDevice({alert:true, badge:true, sound:true}, function(status) {
app.myLog.value+=JSON.stringify([‘registerDevice status: ‘, status])+”\n”;
app.storeToken(status.deviceToken);
});

上面的代码编写完成且应用程序运行后,您将会收到图 4 中显示的警报,询问用户是否要接收推送通知(单击 “OK” 或 “Don’t Allow”,将会显示一项设置,用户可以根据自身的设备设置进行修改)。

图 4. 要求获得发送推送通知权限的提示。

获取未决通知

未决通知是指应用程序处于非活动状态时收到的通知。当应用程序启动时,检索这些通知,以便根据需要处理数据并发送数据。API 中的 getPendingNotifications() 函数可用于达成这一目的。请参阅下面的示例代码:

var pushNotification = window.plugins.pushNotification;
pushNotification.getPendingNotifications(function(notifications) {
app.myLog.value+=JSON.stringify([‘getPendingNotifications’, notifications])+”\n”;
console.log(JSON.stringify([‘getPendingNotifications’, notifications]));
});

获取通知状态

此方法用于执行注册检查,并返回启用的通知(警报、声音、徽章)。

var pushNotification = window.plugins.pushNotification;
pushNotification.getRemoteNotificationStatus(function(status) {
app.myLog.value+=JSON.stringify([‘Registration check -getRemoteNotificationStatus’, status])+”\n”;
});

设置徽章编号

徽章编号是指通过在应用程序图标右上角显示编号来表示特定应用程序的未决通知的方法。您很可能需要在处理通知时于应用程序图标上设置徽章编号。例如,如果从您刚刚收到的推送通知中打开,您可能希望减少或消除编号。将徽标编号设置为零,可删除或清除徽标,如下所示。

var pushNotification = window.plugins.pushNotification;
app.myLog.value+=”Clear badge… \n”;
pushNotification.setApplicationIconBadgeNumber(num);

剖析 Apple Push Notification

通知有效负载的*大值为 256 字节。如果超出这个限制,将会遭到拒*。同时注意,Apple 文档明确说明,通知交付是“尽力而为”但并不作相应保证,因此不应用它来发送关键数据或敏感数据,只能用于通知是否存在新的可用数据。

通知有效负载是 JSON 字典对象,需要包含由主要 aps 识别的另一个字典。紧接着,aps 字典将包含用于指定要显示的警报的一个或多个属性、应用程序图标上要设置的徽标编号和/或出现通知时要播放的声音。它还可以创建自定义有效负载,但本文不会就此进行介绍。警报对象本身只需包含一个要显示的文本字符串或带有正文密钥的字典对象、要显示的自定义操作按钮文本和可以设置的自定义启动图像。请在 Apple Push Notification Service 文档中查看有关有效负载的具体细节信息。

下面是一个简单的有效负载示例(单位:字节):

图 5. Apple Push Notifications 剖析。

典型 aps 字典对象包含以下三个属性:alert、badge 和 sound。例如,我们来看看以下输出内容:

applicationLaunchNotification = 0;
applicationStateActive = 0;
aps = {
alert = {
“action-loc-key” = Play;
body = “Your turn!”;
“launch-image” = “mysplash.png”;
};
badge = 5;
sound = “notification-beep.wav”;
};
messageFrom = Holly;
timestamp = “1350416054.782263″;

任何自定义声音或启动图像均必须包含在 XCode 项目的资源文件夹中。例如,在下面的图像中,我指定了notification-beep.wav 和 mysplash.png,并以此指代该服务器发送的推送通知。

图 6. 指定自定义声音和图像。

服务器端代码处理

下面是一个代码示例,您可以用它启动服务器端解决方案以处理设备令牌注册,现在我调用应用程序示例,展示您可能希望与第三方服务器开展通信的方式和时机,以便在从 Apple 接收设备令牌后存储设备令牌。目前,系统除显示令牌和消息收到之外,并没有对令牌执行任何操作。接下来,我们需要将用户的设备令牌及其他所有信息存储在数据库中,以备日后使用。

var http = require(‘http’);
var apn = require(‘apn’);
var qs = require(‘querystring’);

var server = http.createServer(function (req, res) {
if(req.method === “POST”) {
var fullBody=””;

req.on(‘data’, function(chunk)
{
fullBody += chunk;
console.log(“Full body ” + fullBody);
});

req.on(‘end’, function()
{
var data = qs.parse(fullBody);
console.log(“Token ” +data.token);
console.log(“Message ” + data.message);
var myDevice = new apn.Device(data.token);
// Now we need to store it! Add code to interface with a db
below…

res.writeHead(200, {“Content-Type”: “text/plain”});
res.end(“Thank you for registering\n”);
res.end();
});
}
}).listen(8888);
console.log(“Server running at http://127.0.0.1:”+server.address().port);

我只需要在本地主机上使用 Node.js 运行此脚本。当从实际设备执行测试时(因为模拟器中不支持推送通知),您可以在设备的 Wi-Fi 设置中设置 Manual HTTP Proxy,使其指向您计算机的 IP 地址。转到您的 Wi-Fi 网络,滚动到设置底部以设置 Manual Proxy 服务器和端口。下面是我提供的一个示例:

图 7. 在您的设备上执行测试。

通过 Argon (Node.js API) 发送通知

下面是一个简单代码示例,用于显示如何使用 argon 开源 Node.js API 向用户设备发送推送通知。我只需要对设备令牌执行硬编码即可进行快速测试,但*终您将需要从数据库检索它们,以便用于发送推送通知。

var http = require(‘http’);
var apn = require(‘apn’);
var url = require(‘url’);

var myPhone = “d2d8d2a652148a5cea89d827d23eee0d34447722a2e7defe72fe19d733697fb0”;
var myiPad = “51798aaef34f439bbb57d6e668c5c5a780049dae840a0a3626453cd4922bc7ac”;

var myDevice = new apn.Device(myPhone);

var note = new apn.Notification();
note.badge = 1;
note.sound = “notification-beep.wav”;
note.alert = { “body” : “Your turn!”, “action-loc-key” : “Play” , “launch-image” : “mysplash.png”};
note.payload = {‘messageFrom’: ‘Holly’};

note.device = myDevice;

var callback = function(errorNum, notification){
console.log(‘Error is: %s’, errorNum);
console.log(“Note ” + notification);
}
var options = {
gateway: ‘gateway.sandbox.push.apple.com’, // this URL is different for Apple’s Production Servers and changes when you go to production
errorCallback: callback,
cert: ‘PushNotificationSampleCert.pem’,
key:  ‘PushNotificationSampleKey.pem’,
passphrase: ‘myPassword’,
port: 2195,
enhanced: true,
cacheLength: 100
}
var apnsConnection = new apn.Connection(options);
apnsConnection.sendNotification(note);

上面的代码用于生成通知,在设备上如下所示,假设您已将设备的 Settings > Notifications > MyAppName 设置为 Alerts 选项:

图 8. 您设备上的警报通知。

如果您将应用程序上的 Alert Style 设备设置设定为 Banners,它将显示如下:

图 9. 您设备上的横幅通知。

重要提示:您必须将 PEM 文件更改为您在安装期间创建的证书和私有密钥(您*终按照 SSL 证书和配置部分的说明合并到单一 PEM 文件的内容)。请记住,首先将 CER 转换为 PEM,然后将 .p12 转换为 PEM。这些就是您此刻所需的两个文件。有关 argon 参数的详细信息,请参阅 argon readme 文件。在我的示例中,PEM 文件位于 Node.js 代码所在的文件夹中。如果您并未为它们设置密码,则可以忽略密码属性。

下一步阅读方向

我已经在 GitHub 上包含了一个针对参考应用程序创建的示例项目链接,因为其中包含我上面讨论的所有内容。我不建议使用这个现成项目,因为您必须在项目中设置自己独特的 App ID 和配置文件。*好从头开始创建自己的项目,仅将这个示例作为参考。我创建这个项目旨在*终跨平台使用 cordova 客户端工具,如我的文章(瞬间创建跨平台 PhoneGap (aka Cordova) 项目模板!)中所述,但目前仅创建了 iOS。因此,请在特定的 iOS 代码中引用 PGPushNotificationSample/platforms/ios 路径。您将会在PGPushNotificationSample/platforms/ios/www/js/index.js 文件中找到所有 JavaScript 函数。