操作指南:与 Graph 通知集成 (iOS)

Graph 通知可让应用跨多个设备发送和管理面向用户的通知。

使用 iOS 上的 Project Rome 客户端 SDK,iOS 应用可以在注册后接收应用服务器发布的,面向已登录用户的通知。 该 SDK 可让应用客户端接收新的传入通知有效负载,管理现有通知的状态,以及检索通知历史记录。 有关通知及其如何实现以人为本的通知传送的详细信息,请参阅 Microsoft Graph 通知概述

Project Rome SDK 中的所有功能(包括 Graph 通知等)都是建立在一个称作“互联设备平台”的底层平台基础之上。 本指南旨在引导你完成所需的步骤来开始使用互联设备平台,并介绍如何使用该 SDK 中的 API 实现 Graph 通知特定的功能。

以下步骤将会参考 GitHub 上提供的 Project Rome iOS 示例应用中的代码。

如需与通知方案相关的参考文档链接,请参阅 API 参考页。

设置互联设备平台和通知

注册应用

Project Rome SDK 的几乎所有功能(就近共享 API 除外)都需要 Microsoft 帐户 (MSA) 或 Azure Active Directory (AAD) 身份验证。 如果你没有 MSA 但想要使用 MSA,请在 account.microsoft.com 上注册。

注意

设备中继 API 不支持 Azure Active Directory (AAD) 帐户。

使用所选的身份验证方法时,必须遵照有关应用程序注册门户的说明,将应用注册到 Microsoft。 如果没有 Microsoft 开发人员帐户,需要创建一个。

使用 MSA 注册应用时,应会收到一个客户端 ID 字符串。 请保存此字符串,供稍后使用。 这样,应用便可以访问 Microsoft 的互联设备平台资源。 如果使用 AAD,请参阅 Azure Active Directory 身份验证库了解有关获取客户端 ID 字符串的说明。

添加 SDK

将互联设备平台添加到 iOS 应用的最简单方法是使用 CocoaPods 依赖项管理器。 转到 iOS 项目的 Podfile 并插入以下条目:

platform :ios, "10.0"
workspace 'iOSSample'

target 'iOSSample' do
  # Uncomment the next line if you're using Swift or would like to use dynamic frameworks
  # use_frameworks!

	pod 'ProjectRomeSdk'

  # Pods for iOSSample

注意

若要使用 CocoaPod,必须使用项目中的 .xcworkspace 文件。

设置身份验证和帐户管理

互联设备平台需要一个可在注册过程中使用的有效 OAuth 令牌。 你可以使用偏好的方法来生成和管理 OAuth 令牌。 但是,为了帮助开发人员开始使用该平台,我们在 iOS 示例应用中包含了一个身份验证提供程序,可用于在应用中生成和管理刷新令牌。

如果不使用提供的代码,则需要自行实现 MCDConnectedDevicesAccountManager 接口。

如果使用 MSA,请在登录请求中包含以下范围:"wl.offline_access""ccs.ReadWrite""dds.read""dds.register""wns.connect""asimovrome.telemetry""https://activity.windows.com/UserActivity.ReadWrite.CreatedByApp"

注意

设备中继 API 不支持 Azure Active Directory (AAD) 帐户。

如果使用 AAD 帐户,需要请求以下受众:"https://cdpcs.access.microsoft.com""https://cs.dds.microsoft.com""https://wns.windows.com/""https://activity.microsoft.com"

不管是否使用提供的 MCDConnectedDevicesAccountManager 实现,只要使用 AAD,就需要在 Azure 门户上的应用注册(portal.azure.com >“Azure Active Directory”>“应用注册”)中指定以下权限:

  • Microsoft 活动源服务
    • 传送和修改此应用的用户通知
    • 读取应用活动以及将其写入用户的活动源
  • Windows 通知服务
    • 将设备连接到 Windows 通知服务
  • Microsoft 设备目录服务
    • 查看设备列表
    • 添加到设备和应用列表
  • Microsoft 命令服务
    • 与用户设备通信
    • 读取用户设备

为推送通知注册应用程序

将应用程序注册到 Apple 以获得推送通知支持。 请务必记下收到的发送方 ID 和服务器密钥,因为稍后需要用到。

注册后,必须在应用中将推送通知功能关联到互联设备平台。

self.notificationRegistration = [[MCDConnectedDevicesNotificationRegistration alloc] init];
    if ([[UIApplication sharedApplication] isRegisteredForRemoteNotifications])
    {
        self.notificationRegistration.type = MCDNotificationTypeAPN;
    }
    else
    {
        self.notificationRegistration.type = MCDNotificationTypePolling;
    }
    self.notificationRegistration.appId = [[NSBundle mainBundle] bundleIdentifier];
    self.notificationRegistration.appDisplayName = (NSString*)[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"];
    self.notificationRegistration.token = deviceToken;
    self.isRegisteredWithToken = YES;

在 Microsoft Windows 开发人员中心注册应用以获得跨设备体验

警告

仅当你要使用 Project Rome 功能访问非 Windows 设备中的数据或对这些设备发出请求时,才需要执行此步骤。 如果只是针对 Windows 设备,则不需要完成此步骤。

注册应用以获得 Microsoft 开发人员仪表板的跨设备体验功能。 此过程不同于前面所述的 MSA 和 AAD 应用注册。 此过程的主要目的是将平台特定的应用标识映射到互联设备平台识别的跨平台应用标识。 此步骤还会使用与应用所用的移动平台对应的本机推送通知服务来启用通知发送。 对于 iOS,它会启用通过 APNS(Apple Push Notification 服务)向 iOS 应用终结点发送通知。

转到开发人员中心仪表板,在左侧导航窗格中导航到“跨设备体验”,并选择配置新的跨设备应用。 Dev Center Dashboard – Cross-Device Experiences

开发人员中心加入过程需要执行以下步骤:

  • 选择支持的平台 – 选择运行你的应用的、要为跨设备体验启用的平台。 对于 Graph 通知集成,可以根据所用的平台,选择“Windows”、“Android”和/或“iOS”。 Cross-Device Experiences – Supported Platforms

  • 提供应用 ID – 为所用的每个平台提供应用 ID。 对于 iOS 应用,这是创建项目时分配给应用的包名称。 请注意,如果相同的应用有多个版本,或者存在不同的应用,而这些应用希望能够收到应用服务器发送的面向同一用户的相同通知,则你可以为每个平台添加不同的 ID(最多 10 个)。 Cross-Device Experiences – App IDs

  • 提供应用 ID,或者选择在前面的 MSA 和/或 AAD 应用注册步骤中获取的应用 ID。 Cross-Device Experiences – MSA and AAD App Registrations

  • 提供与应用相关的本机通知平台(即,Windows 的 WNS、Android 的 FCM 和/或 iOS 的 APNS)的凭据,以便在发布面向用户的通知时,可以传送应用服务器发出的通知。 Cross-Device Experiences – Push Credentials

  • 最后,验证跨设备应用域,以确保应用拥有该域,并可将其用作应用的跨设备标识。 Cross-Device Experiences – Domain Verification

使用平台

创建平台的实例

若要开始,只需实例化平台即可。

MCDConnectedDevicesPlatform* platform = [MCDConnectedDevicesPlatform new];

订阅 MCDConnectedDevicesAccountManager

必须以经过身份验证的用户身份访问平台。 需要订阅 MCDConnectedDevicesAccountManager 事件,以确保使用有效帐户。

[MCDConnectedDevicesPlatform* platform.accountManager.accessTokenRequested
     subscribe:^(MCDConnectedDevicesAccountManager* _Nonnull manager __unused,
                 MCDConnectedDevicesAccessTokenRequestedEventArgs* _Nonnull request __unused) {

                    // Get access token

                 }
[MCDConnectedDevicesPlatform* platform.platform.accountManager.accessTokenInvalidated
     subscribe:^(MCDConnectedDevicesAccountManager* _Nonnull manager __unused,
                 MCDConnectedDevicesAccessTokenInvalidatedEventArgs* _Nonnull request) {

                      // Refresh and renew existing access token

                 }

订阅 MCDConnectedDevicesNotificationRegistrationManager

同样,平台将使用通知在设备之间传送命令。 因此,必须订阅 MCDConnectedDevicesNotificationRegistrationManager 事件,以确保云注册状态对于所用的帐户有效。 使用 MCDConnectedDevicesNotificationRegistrationState 验证状态

[MCDConnectedDevicesPlatform* platform.notificationRegistrationManager.notificationRegistrationStateChanged
     subscribe:^(MCDConnectedDevicesNotificationRegistrationManager* manager __unused,
                 MCDConnectedDevicesNotificationRegistrationStateChangedEventArgs* args __unused) {

                     // Check state using MCDConnectedDevicesNotificationRegistrationState enum

                 }

启动平台

初始化平台并准备好事件处理程序后,可以开始发现远程系统设备。

[MCDConnectedDevicesPlatform* platform start];

检索应用已知的用户帐户

必须确保设备已知的用户帐户列表已与 MCDConnectedDevicesAccountManager 正确同步。

使用 MCDConnectedDevicesAccountManager.addAccountAsync 添加新用户帐户。

[MCDConnectedDevicesPlatform* platform.accountManager
     addAccountAsync:self.mcdAccount
     callback:^(MCDConnectedDevicesAddAccountResult* _Nonnull result, NSError* _Nullable error) {

     // Check state using **MCDConnectedDevicesAccountAddedStatus** enum

     }

若要删除无效帐户,可以使用 MCDConnectedDevicesAccountManager.removeAccountAsync

 [MCDConnectedDevicesPlatform* platform.accountManager
     removeAccountAsync:existingAccount
     callback:^(MCDConnectedDevicesRemoveAccountResult* _Nonnull result __unused, NSError* _Nullable error) {

                    // Remove invalid user account

     }

初始化 Graph 通知通道

Project Rome SDK 允许应用订阅不同的通道以接收和管理各种类型的用户数据 - 包括 Graph 通知、用户活动,等等。 所有这些数据在 MCDUserDataFeed 中存储并同步。 MCDUserNotification 是对应于通过 Graph 通知发送的面向用户的通知的类和数据类型。 若要与 Graph 通知集成并开始接收应用服务器发布的 MCDUserNotification,首先需要通过创建 MCDUserNotificationChannel 来初始化用户数据源。 应该像上述平台初始化步骤一样处理此过程:每当应用转到前台(但不是在平台初始化之前),都应该检查此过程,并可能需要重新执行。

以下方法初始化 MCDUserNotificationChannel

// You must be logged in to use UserNotifications
NSArray<MCDUserAccount*>* accounts = [[AppDataSource sharedInstance].accountProvider getUserAccounts];
if (accounts.count > 0)
{
    // Get a UserNotification channel, getting the default channel
    NSLog(@"Creating UserNotificationChannel");
    NSArray<MCDUserAccount*>* accounts = [[AppDataSource sharedInstance].accountProvider getUserAccounts];
    MCDUserDataFeed* userDataFeed = [MCDUserDataFeed userDataFeedForAccount:accounts[0]
        platform:[AppDataSource sharedInstance].platform
        activitySourceHost:CROSS_PLATFORM_APP_ID];
    NSArray<MCDSyncScope*>* syncScopes = @[ [MCDUserNotificationChannel syncScope] ];
    [userDataFeed addSyncScopes:syncScopes];
    self.channel = [MCDUserNotificationChannel userNotificationChannelWithUserDataFeed:userDataFeed];
}
else
{
    NSLog(@"Must log in to receive notifications for the logged in user!");
    self.createNotificationStatusField.text = @"Need to be logged in!";
}

此时,channel 中应该包含一个 MCDUserNotificationChannel 引用。

创建 MCDUserNotificationReader 用于接收传入的 MCDUserNotifications 和访问 MCDUserNotification 历史记录

如前所示,抵达应用客户端的初始 APNS 静默消息仅包含一个肩带 (shoulder tap),你需要将该肩带有效负载(其中包含应用服务器发布的所有 MCDUserNotification)传递到互联设备平台,才能触发 SDK 来执行与互联设备服务器的完全同步。 这会提取应用服务器发布的、对应于此肩带的完整通知有效负载(如果前面的任何通知已发布,但由于设备连接或其他问题而未在此应用客户端上接收,则也会提取这些有效负载)。 凭借 SDK 不断执行的这种实时同步,应用客户端可以访问此登录用户的 MCDUserNotification 数据源的本地缓存。 在这种情况下,MCDUserNotificationReader 可让应用客户端访问此数据源 – 以通过事件侦听器接收最新的通知有效负载,或者访问可用作用户通知历史记录视图模型的完整 MCDUserNotification 集合。

接收 MCDUserNotification

首先需要实例化 MCDUserNotificationReader,如果你想要对尝试启用的体验使用该信息,则还需要获取读取器中所有现有的 MCDUserNotification。 如果此特定的设备终结点可能不是用户将你的应用安装到的唯一一个或第一个终结点,始终可以安全地假设应用服务器已将通知发布到此登录用户。 然后添加一个事件侦听器,当互联设备平台完成同步并且有需要通知你的新更改时,将触发该侦听器。 对于 Graph 通知,新的更改可能是应用服务器发布的新传入 MCDUserNotification,或者同一用户登录到的服务器或其他已注册终结点中发生的 MCDUserNotification 更新、删除和过期。

提示

你将在此事件侦听器中处理主要业务逻辑,以及根据自己的方案“使用”通知有效负载的内容。 如果你当前使用 APNS 静默通知在 OS 级别的通知中心构造可视通知,或者使用静默通知中的内容来更新某些应用内 UI,则需要在此侦听器中执行这些操作。

// Instantiate the reader from a MCDUserNotificationChannel
// Add a data change listener to subscribe to new changes when new notifications or notification updates are received
- (void)setupWithAccount:(MCDUserAccount*)account {
    dispatch_async(dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0), ^{
        @synchronized (self) {
            MCDUserDataFeed* dataFeed = [MCDUserDataFeed userDataFeedForAccount:account platform:_platform activitySourceHost:@"graphnotifications.sample.windows.com"];
            [dataFeed addSyncScopes:@[[MCDUserNotificationChannel syncScope]]];
            self.channel = [MCDUserNotificationChannel userNotificationChannelWithUserDataFeed:dataFeed];
            self.reader = [self.channel createReader];
            
            __weak typeof(self) weakSelf = self;
            _readerRegistrationToken = [self.reader addDataChangedListener:^(__unused MCDUserNotificationReader* source) {
                NSLog(@"ME123 Got a change!");
                if (weakSelf) {
                    [weakSelf forceRead];
                } else {
                    NSLog(@"ME123 WEAKSELF FOR CHANGES IS NULL!!!");
                }
            }];
            
            [self forceRead];
        }
    });
}

// this is your own business logic when the event listener is fired
// In this case, the app reads the existing batch of notifications in the store and handle any new incoming notifications or notification updates after that
- (void)forceRead {
    NSLog(@"ME123 Forced to read!");
    [self.reader readBatchAsyncWithMaxSize:NSUIntegerMax completion:^(NSArray<MCDUserNotification *> * _Nullable notifications, NSError * _Nullable error) {
        if (error) {
            NSLog(@"ME123 Failed to read batch with error %@", error);
        } else {
            [self _handleNotifications:notifications];
            NSLog(@"ME123 Have %ld listeners", self.listenerMap.count);
            for (void (^listener)(void) in self.listenerMap.allValues) {
                NSLog(@"ME123 Calling a listener about an update!");
                listener();
            }
        }
    }];
}

更新现有 MCDUserNotification 的状态

上一部分提到,通过读取器接收的 MCDUserNotification 更改有时可能是对现有 MCDUserNotification 的状态更新 – 不管它是标记为已消除还是标记为已阅读。 在这种情况下,应用客户端可以选择要执行哪种操作,例如,通过删除此特定设备上的相应可视通知来启用全局消除。 退一步讲,应用客户端通常从不同的设备发起此 MCDUserNotification 更改更新作为操作起点的一方。 可以选择更新 MCDUserNotification 状态的时间,但是,当该设备上的用户处理了相应的可视通知,或者启用的某些应用内体验中的用户进一步处理了通知时,通常就会更新 MCDUserNotification。 这是流呈现效果的一个示例:应用服务器发布一条面向用户 A 的通知。用户 A 在安装了应用客户端的电脑和手机上接收此通知。 该用户在电脑上单击该通知,并观察应用如何处理相应的任务。 然后,此电脑上的应用客户端将调用互联设备平台 SDK 来更新相应用户通知的状态,使此项更新在此用户的所有设备之间同步。 其他应用客户端在实时收到此状态更新后,会从设备的通知中心/通知托盘/操作中心删除相应的可视警报/消息/toast 通知。 通知就是以这种方式在各个用户设备上全局消除的。

提示

MCDUserNotification 类目前提供两种类型的状态更新 – 可以修改 MCDUserNotificationReadState 或 MCDUserNotificationUserActionState,并根据更新通知时要发生的情况定义自己的逻辑。 例如,可将操作标记为 Activated(已激活)或 Dismissed(已消除),并依据该值实现全局消除。 或者,可以同时将读取状态标记为 Read(已阅读)或 Unread(未阅读),并根据该值确定要在应用内通知历史记录视图中显示哪些通知。

- (void)dismissNotification:(MCDUserNotification*)notification {
    @synchronized (self) {
        notification.userActionState = MCDUserNotificationUserActionStateDismissed;
        [notification saveAsync:^(__unused MCDUserNotificationUpdateResult * _Nullable result, __unused NSError * _Nullable err) {
            NSLog(@"ME123 Dismiss notification with result %d error %@", result.succeeded, err);
        }];
    }
}