教程:使用 Azure 通知中心向特定 iOS 设备发送推送通知
概述
本教程演示如何使用 Azure 通知中心将突发新闻通知广播到 iOS 应用。 完成后,可注册感兴趣的突发新闻类别并仅接收这些类别的推送通知。 此方案对于很多应用来说是常见模式,在这些应用中必须将通知发送到以前声明过对它们感兴趣的用户组,这样的应用包括 RSS 阅读器、针对音乐迷的应用等。
在创建通知中心的注册时,通过加入一个或多个 标记 来启用广播方案。 将通知发送到标记时,已注册该标记的设备会接收通知。 因为标签是简单的字符串,它们不必提前设置。 有关标记的详细信息,请参阅通知中心路由和标记表达式。
在本教程中,我们将执行以下步骤:
- 向应用中添加类别选择
- 发送带标记的通知
- 从设备发送通知
- 运行应用并生成通知
先决条件
本主题基于以下教程中创建的应用:教程:使用 Azure 通知中心向 iOS 应用推送通知。 在开始学习本教程之前,必须已完成教程:使用 Azure 通知中心向 iOS 应用推送通知。
向应用程序中添加类别选择
第一步是向现有 Storyboard 添加 UI 元素,这些元素允许用户选择要注册的类别。 用户选择的类别存储在设备上。 应用程序启动时,使用所选类别作为标签在通知中心创建设备注册。
在 MainStoryboard_iPhone.storyboard 中,从对象库添加以下组件:
具有“Breaking News”文本的标签
具有“World”、“Politics”、“Business”、“Technology”、“Science”、“Sports”类别文本的标签
六个开关,每个类别一个。默认情况下,各开关的“状态” 均设置为“关闭” 。
一个标有“Subscribe”的按钮
Storyboard 应类似于:
在助手编辑器中,为所有开关创建插座并称它们为“WorldSwitch”、“PoliticsSwitch”、“BusinessSwitch”、“TechnologySwitch”、“ScienceSwitch”、“SportsSwitch”
为名为
subscribe
的按钮创建一个操作;ViewController.h
应包含以下代码:@property (weak, nonatomic) IBOutlet UISwitch *WorldSwitch; @property (weak, nonatomic) IBOutlet UISwitch *PoliticsSwitch; @property (weak, nonatomic) IBOutlet UISwitch *BusinessSwitch; @property (weak, nonatomic) IBOutlet UISwitch *TechnologySwitch; @property (weak, nonatomic) IBOutlet UISwitch *ScienceSwitch; @property (weak, nonatomic) IBOutlet UISwitch *SportsSwitch; - (IBAction)subscribe:(id)sender;
创建名为
Notifications
的新 Cocoa Touch 类。 在文件 Notifications.h 的接口部分中复制以下代码:@property NSData* deviceToken; - (id)initWithConnectionString:(NSString*)listenConnectionString HubName:(NSString*)hubName; - (void)storeCategoriesAndSubscribeWithCategories:(NSArray*)categories completion:(void (^)(NSError* error))completion; - (NSSet*)retrieveCategories; - (void)subscribeWithCategories:(NSSet*)categories completion:(void (^)(NSError *))completion;
将以下导入指令添加到 Notifications.m:
#import <WindowsAzureMessaging/WindowsAzureMessaging.h>
在文件 Notifications.m 的实现部分中复制以下代码。
SBNotificationHub* hub; - (id)initWithConnectionString:(NSString*)listenConnectionString HubName:(NSString*)hubName{ hub = [[SBNotificationHub alloc] initWithConnectionString:listenConnectionString notificationHubPath:hubName]; return self; } - (void)storeCategoriesAndSubscribeWithCategories:(NSSet *)categories completion:(void (^)(NSError *))completion { NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults]; [defaults setValue:[categories allObjects] forKey:@"BreakingNewsCategories"]; [self subscribeWithCategories:categories completion:completion]; } - (NSSet*)retrieveCategories { NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults]; NSArray* categories = [defaults stringArrayForKey:@"BreakingNewsCategories"]; if (!categories) return [[NSSet alloc] init]; return [[NSSet alloc] initWithArray:categories]; } - (void)subscribeWithCategories:(NSSet *)categories completion:(void (^)(NSError *))completion { NSString* templateBodyAPNS = @"{\"aps\":{\"alert\":\"$(messageParam)\"}}"; [hub registerTemplateWithDeviceToken:self.deviceToken name:@"simpleAPNSTemplate" jsonBodyTemplate:templateBodyAPNS expiryTemplate:@"0" tags:categories completion:completion]; }
此类使用本地存储来存储和检索此设备接收的新闻类别。 此外,它还包含一个用于通过模板注册来注册这些类别的方法。
在
AppDelegate.h
文件中,为Notifications.h
添加 import 语句,并为Notifications
类的实例添加一个属性:#import "Notifications.h" @property (nonatomic) Notifications* notifications;
在
AppDelegate.m
中的didFinishLaunchingWithOptions
方法开头,添加用于初始化 notifications 实例的代码。
在hubinfo.h
中定义的HUBNAME
和HUBLISTENACCESS
内,<hub name>
和<connection string with listen access>
占位符应已替换为通知中心名称和前面获取的 DefaultListenSharedAccessSignature 的连接字符串self.notifications = [[Notifications alloc] initWithConnectionString:HUBLISTENACCESS HubName:HUBNAME];
注意
由于使用客户端应用程序分发的凭据通常是不安全的,只应使用客户端应用程序分发具有侦听访问权限的密钥。 侦听访问权限允许应用程序注册通知,但是无法修改现有注册,也无法发送通知。 在受保护的后端服务中使用完全访问权限密钥,以便发送通知和更改现有注册。
将
AppDelegate.m
中didRegisterForRemoteNotificationsWithDeviceToken
方法内的代码替换为以下代码,以将设备令牌传递给notifications
类。notifications
类将通知注册到类别。 如果用户更改类别选择,则调用subscribeWithCategories
方法以响应“订阅”按钮,从而进行更新 。注意
由于 Apple Push Notification 服务 (APNS) 分配的设备标记随时可能更改,因此应该经常注册通知,以免通知失败。 此示例在每次应用程序启动时注册通知。 对于经常运行(一天一次以上)的应用程序,如果每次注册间隔时间不到一天,可以跳过注册来节省带宽。
self.notifications.deviceToken = deviceToken; // Retrieves the categories from local storage and requests a registration for these categories // each time the app starts and performs a registration. NSSet* categories = [self.notifications retrieveCategories]; [self.notifications subscribeWithCategories:categories completion:^(NSError* error) { if (error != nil) { NSLog(@"Error registering for notifications: %@", error); } }];
此时,
didRegisterForRemoteNotificationsWithDeviceToken
方法中不应有任何代码。完成通知中心入门教程后,以下方法应已存在于
AppDelegate.m
中。 否则,请添加这些方法。- (void)MessageBox:(NSString *)title message:(NSString *)messageText { UIAlertView *alert = [[UIAlertView alloc] initWithTitle:title message:messageText delegate:self cancelButtonTitle:@"OK" otherButtonTitles: nil]; [alert show]; } - (void)application:(UIApplication *)application didReceiveRemoteNotification: (NSDictionary *)userInfo { NSLog(@"%@", userInfo); [self MessageBox:@"Notification" message:[[userInfo objectForKey:@"aps"] valueForKey:@"alert"]]; }
此方法通过显示简单的 UIAlert处理运行应用程序时收到的通知。
在
ViewController.m
中,为AppDelegate.h
添加import
语句,并将以下代码复制到 XCode 生成的subscribe
方法中。 此代码会更新通知注册,使用用户在用户界面中选择的新类别标记。#import "Notifications.h" NSMutableArray* categories = [[NSMutableArray alloc] init]; if (self.WorldSwitch.isOn) [categories addObject:@"World"]; if (self.PoliticsSwitch.isOn) [categories addObject:@"Politics"]; if (self.BusinessSwitch.isOn) [categories addObject:@"Business"]; if (self.TechnologySwitch.isOn) [categories addObject:@"Technology"]; if (self.ScienceSwitch.isOn) [categories addObject:@"Science"]; if (self.SportsSwitch.isOn) [categories addObject:@"Sports"]; Notifications* notifications = [(AppDelegate*)[[UIApplication sharedApplication]delegate] notifications]; [notifications storeCategoriesAndSubscribeWithCategories:categories completion: ^(NSError* error) { if (!error) { UIAlertView *alert = [[UIAlertView alloc] initWithTitle:"Notification" message:"Subscribed" delegate:self cancelButtonTitle:@"OK" otherButtonTitles: nil]; [alert show]; } else { NSLog(@"Error subscribing: %@", error); } }];
此方法创建类别的
NSMutableArray
,使用Notifications
类将该列表存储在本地存储中,并将相应的标签注册到通知中心。 更改类别时,使用新类别重新创建注册。在
ViewController.m
中,在viewDidLoad
方法中添加以下代码,以根据前面保存的类别来设置用户界面。// This updates the UI on startup based on the status of previously saved categories. Notifications* notifications = [(AppDelegate*)[[UIApplication sharedApplication]delegate] notifications]; NSSet* categories = [notifications retrieveCategories]; if ([categories containsObject:@"World"]) self.WorldSwitch.on = true; if ([categories containsObject:@"Politics"]) self.PoliticsSwitch.on = true; if ([categories containsObject:@"Business"]) self.BusinessSwitch.on = true; if ([categories containsObject:@"Technology"]) self.TechnologySwitch.on = true; if ([categories containsObject:@"Science"]) self.ScienceSwitch.on = true; if ([categories containsObject:@"Sports"]) self.SportsSwitch.on = true;
应用程序现在可以在设备的本地存储区中存储一组类别,每当应用程序启动时,会使用这些类别注册到通知中心。 用户可以在运行时更改选择的类别,并单击 subscribe
方法来更新设备注册。 接下来,更新应用,直接从应用本身发送突发新闻通知。
(可选)发送带标记的通知
如果无权访问 Visual Studio,可以跳到下一部分,并从应用内部发送通知。 还可以在 Azure 门户 中使用通知中心的调试选项卡发送适当的模板通知。
本部分说明如何从 .NET 控制台应用以标记模板通知的形式发送突发新闻。
在 Visual Studio 中创建新的 Visual C# 控制台应用程序:
- 在菜单中,选择“文件”>“新建”>“项目”。
- 在“创建新项目”中,选择模板列表中适用于 C# 的“控制台应用(.NET Framework)”,然后选择“下一步”。
- 输入应用程序的名称。
- 对于“解决方案”,选择“添加到解决方案”,然后选择“创建”以创建项目。
选择“工具”>“NuGet 包管理器”>“包管理器控制台”,然后在控制台窗口中运行以下命令 :
Install-Package Microsoft.Azure.NotificationHubs
此操作会使用 Microsoft.Azure.NotificationHubs 包添加对 Azure 通知中心 SDK 的引用。
打开文件 Program.cs 并添加以下
using
语句:using Microsoft.Azure.NotificationHubs;
在
Program
类中,添加以下方法,或替换此方法(如果已存在):private static async void SendTemplateNotificationAsync() { // Define the notification hub. NotificationHubClient hub = NotificationHubClient.CreateClientFromConnectionString("<connection string with full access>", "<hub name>"); // Apple requires the apns-push-type header for all requests var headers = new Dictionary<string, string> {{"apns-push-type", "alert"}}; // Create an array of breaking news categories. var categories = new string[] { "World", "Politics", "Business", "Technology", "Science", "Sports"}; // Send the notification as a template notification. All template registrations that contain // "messageParam" and the proper tags will receive the notifications. // This includes APNS, WNS, and MPNS template registrations. Dictionary<string, string> templateParams = new Dictionary<string, string>(); foreach (var category in categories) { templateParams["messageParam"] = "Breaking " + category + " News!"; await hub.SendTemplateNotificationAsync(templateParams, category); } }
此代码将针对字符串数组中的所有 6 个标记发送模板通知。 使用标记是为了确保设备仅接收已注册类别的通知。
在前面的代码中,将
<hub name>
和<connection string with full access>
占位符替换为通知中心名称和从通知中心仪表板获取的 DefaultFullSharedAccessSignature 的连接字符串。在
Main()
方法中添加以下行:SendTemplateNotificationAsync(); Console.ReadLine();
生成控制台应用。
(可选)从设备发送通知
通常,通知由后端服务发送,但你也可以直接从应用发送突发新闻通知。 为此,需更新通知中心入门教程中定义的 SendNotificationRESTAPI
方法。
在
ViewController.m
中,按如下所示更新SendNotificationRESTAPI
方法,使其接受类别标记的参数并发送适当的模板通知。- (void)SendNotificationRESTAPI:(NSString*)categoryTag { NSURLSession* session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:nil delegateQueue:nil]; NSString *json; // Construct the messages REST endpoint NSURL* url = [NSURL URLWithString:[NSString stringWithFormat:@"%@%@/messages/%@", HubEndpoint, HUBNAME, API_VERSION]]; // Generated the token to be used in the authorization header. NSString* authorizationToken = [self generateSasToken:[url absoluteString]]; //Create the request to add the template notification message to the hub NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; [request setHTTPMethod:@"POST"]; // Add the category as a tag [request setValue:categoryTag forHTTPHeaderField:@"ServiceBusNotification-Tags"]; // Template notification json = [NSString stringWithFormat:@"{\"messageParam\":\"Breaking %@ News : %@\"}", categoryTag, self.notificationMessage.text]; // Signify template notification format [request setValue:@"template" forHTTPHeaderField:@"ServiceBusNotification-Format"]; // JSON Content-Type [request setValue:@"application/json;charset=utf-8" forHTTPHeaderField:@"Content-Type"]; //Authenticate the notification message POST request with the SaS token [request setValue:authorizationToken forHTTPHeaderField:@"Authorization"]; //Add the notification message body [request setHTTPBody:[json dataUsingEncoding:NSUTF8StringEncoding]]; // Send the REST request NSURLSessionDataTask* dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*) response; if (error || httpResponse.statusCode != 200) { NSLog(@"\nError status: %d\nError: %@", httpResponse.statusCode, error); } if (data != NULL) { //xmlParser = [[NSXMLParser alloc] initWithData:data]; //[xmlParser setDelegate:self]; //[xmlParser parse]; } }]; [dataTask resume]; }
在
ViewController.m
中更新Send Notification
操作(如以下代码中所示)。 如此,它会使用各个标记向多个平台分别发送通知。- (IBAction)SendNotificationMessage:(id)sender { self.sendResults.text = @""; NSArray* categories = [NSArray arrayWithObjects: @"World", @"Politics", @"Business", @"Technology", @"Science", @"Sports", nil]; // Lets send the message as breaking news for each category to WNS, Baidu and APNS // using a template. for(NSString* category in categories) { [self SendNotificationRESTAPI:category]; } }
重新生成项目,并确定没有生成错误。
运行应用并生成通知
按“运行”按钮生成项目并启动应用程序。 选择要订阅的一些突发新闻选项,然后按“订阅” 按钮。 此时应显示一个对话框,指示已订阅通知。
选择“订阅” 时,应用将所选类别转换为标记,并针对所选标记从通知中心请求注册新设备。
输入要以突发新闻形式发送的消息,然后按“发送通知” 按钮。 或者,运行 .NET 控制台应用以生成通知。
每个订阅突发新闻的设备都会收到刚刚发送的突发新闻通知。
后续步骤
在本教程中,向注册类别的特定 iOS 设备发送了广播通知。 若要了解如何推送本地化通知,请转到以下教程: