APNs推送

前期准备

创建请求证书

创建应用AppID

开启推送功能

配置证书

导出推送所需p12文件

证书上传

客户端配置

配置推送证书

NIMSDKOption中设置对应的推送证书名。

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    ...
    NSString *appKey        = @"your app key";
    NIMSDKOption *option    = [NIMSDKOption optionWithAppKey:appKey];
    option.apnsCername      = @"your APNs cer name";
    [[NIMSDK sharedSDK] registerWithOption:option];
    ...
}

上传devicetoken

客户端还需要将devicetoken上传至云信服务器用于后续的APNs推送,需要使用以下接口:

@interface NIMSDK : NSObject
/**
* @param token  当前设备的 devicetoken 
* @param key    自定义本端推送内容, 设置key可对应业务服务器自定义推送文案; 传@""清空配置, nil 则不更改

- (NSString *)updateApnsToken:(NSData *)token 
             customContentKey:(nullable NSString *)key
}

注:customContentKey的相关内容详见设置推送文案中的自定义默认推送文案部分。

示例代码:

- (void)registerPushService
{
    if (@available(iOS 11.0, *))
    {
        UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
        [center requestAuthorizationWithOptions:(UNAuthorizationOptionBadge | UNAuthorizationOptionSound | UNAuthorizationOptionAlert) completionHandler:^(BOOL granted, NSError * _Nullable error) {
            if (!granted)
            {
                dispatch_async_main_safe(^{
                    [[UIApplication sharedApplication].keyWindow makeToast:@"请开启推送功能否则无法收到推送通知" duration:2.0 position:CSToastPositionCenter];
                })
            }
        }];
    }
    else
    {
        UIUserNotificationType types = UIUserNotificationTypeBadge | UIUserNotificationTypeSound | UIUserNotificationTypeAlert;
        UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:types
                                                                                 categories:nil];
        [[UIApplication sharedApplication] registerUserNotificationSettings:settings];
    }

    [[UIApplication sharedApplication] registerForRemoteNotifications];


    // 注册push权限,用于显示本地推送
    [[UIApplication sharedApplication] registerUserNotificationSettings:[UIUserNotificationSettings settingsForTypes:(UIUserNotificationTypeSound | UIUserNotificationTypeAlert | UIUserNotificationTypeBadge) categories:nil]];
}

...

- (void)application:(UIApplication *)app didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
{
    // 上传devicetoken至云信服务器。
    [[NIMSDK sharedSDK] updateApnsToken:deviceToken];
}

全局免打扰

NIM SDK 提供全局 APNs 属性设置,用于设置全局免打扰时间,详细内容可以参考 NIMPushNotificationSetting

NIMPushNotificationSetting *setting = [[[NIMSDK sharedSDK] apnsManager] currentSetting];

注意:请务必连同设置开始和结束时间。

NIMPushNotificationSetting *setting = [NIMSDK sharedSDK].apnsManager.currentSetting;
// 开启免打扰开关
setting.noDisturbing = YES;
setting.noDisturbingStartH = s_hour;
setting.noDisturbingStartM = s_minute;
setting.noDisturbingEndH = e_hour;
setting.noDisturbingEndM = e_minute;
[[[NIMSDK sharedSDK] apnsManager] updateApnsSetting:setting  
                                         completion:^(NSError *error) {}];

多端推送策略配置

当桌面端在线时,SDK 支持设置是否需要发推送给手机端。

@protocol  NIMApnsManager <NSObject>
/**
* 获取多端推送策略配置
*/
- (nullable NIMPushNotificationMultiportConfig *)currentMultiportConfig;

/**
* 设置多端推送策略配置
*/
- (void)updateApnsMultiportConfig:(NIMPushNotificationMultiportConfig *)config 
                       completion:(nullable NIMApnsHandler)completion;
@end

NIMPushNotificationMultiportConfig参数一览:

参数 类型 说明
shouldPushNotificationWhenPCOnline BOOL 桌面端在线时是否需要发送推送给手机端,默认为 YES,即需要推送,桌面端包括 PC,web , macOS 等…

消息推送配置

设置消息是否需要推送

当需要指定某条消息无需推送时,可以通过NIMMessage - NIMMessageSetting - apnsEnabled来设置。

示例代码:

NIMMessage *message = [[NIMMessage alloc] init];
message.text = @"消息示例";
NIMMessageSetting *setting = [[NIMMessageSetting alloc] init];
setting.apnsEnabled = NO; //设置为 无需推送
message.setting = setting;

设置推送文案

当需要自定义APNs推送文案(通知栏现实的文案)时,可以通过NIMMessage - apnsContent来设置。如果不设置 apnsContent 属性,将使用云信内置文案。

属性原型

@interface NIMMessage : NSObject
/**
 *  消息推送文案,长度限制500
 */
@property (nullable,nonatomic,copy)                  NSString *apnsContent;
@end

示例代码:

NIMAudioObject *audioObject = [[NIMAudioObject alloc] initWithSourcePath:filePath];
NIMMessage *message = [[NIMMessage alloc] init];
message.messageObject = audioObject;
message.apnsContent = @"发来了一段语音";    //对方收到的推送文案 

此时,对方的手机将会收到一条苹果推送,内容形式为 "昵称:"+"推送文案"。其中,昵称为推送文案前缀。

如果开发者配置了「不显示推送详情」(配置方法见下),默认的推送文案是:“你收到一条新消息”。此时,开发者可以自定义推送文案(例如基于系统语言等场景)。注意:需要联系商务顾问申请开通才能使用。

登录云信控制台 - 选择应用 - 证书管理 - 自定义推送文案。可以配置最多 100 种自定义文案,每种自定义文案用一个自定义类型来标识。

当客户端配置为「不显示推送详情」时,自定义推送文案才会生效。

示例代码:

// 获取当前设置
NIMPushNotificationSetting *setting = [NIMSDK sharedSDK].apnsManager.currentSetting;
// 设置为不显示推送详情
setting.type = NIMPushNotificationDisplayTypeNoDetail;
[[[NIMSDK sharedSDK] apnsManager] updateApnsSetting:setting completion:^(NSError *error) {}];
- (void)application:(UIApplication *)app didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
{
    // 上传devicetoken时携带自定义推送文案类型。
    [[NIMSDK sharedSDK] updateApnsToken:deviceToken
                       customContentKey:@"customkey"];
}

设置推送文案前缀

可以通过NIMMessage - NIMMessageSetting - apnsWithPrefix来设置是否需要推送文案前缀。云信服务器向APNs请求推送时,前缀为用户昵称。

示例代码:

NIMMessage *message = [[NIMMessage alloc] init];
message.text = @"消息示例";
message.apnsContent = @"推送消息示例";    //对方收到的推送文案 
NIMMessageSetting *setting = [[NIMMessageSetting alloc] init];
setting.apnsWithPrefix = NO;           //不需要前缀
message.setting = setting;

角标未读数

角标未读数赋值

默认情况下,每一条消息的推送会让应用角标未读数加 1 。 如当应用处于后台时,收到5条消息推送,则应用的角标未读数增加5。

消息的发送方在发送消息时,可以通过NIMMessage - NIMMessageSetting - shouldBeCounted设置该消息推送时是否要计入角标未读数。

示例代码:

NIMMessage *message = [[NIMMessage alloc] init];
message.text = @"没有未读数的消息示例";
NIMMessageSetting *setting = [[NIMMessageSetting alloc] init];
setting.shouldBeCounted = NO;    //推送时不需要计入角标未读数
message.setting = setting;

苹果推送所附带的未读数是通过设置推送 payload 里的 badge 参数。在云信 服务中,不支持直接设置 badge ,服务器会维护对每个用户维护一个当前未读数,当服务器收到一条未读消息后,会自动在未读数上累加后填入推送的 badge 字段推送给接收端。

开发者应该在程序每次推到后台时,统计本地所有未读,并设置 badge , 保证程序在前后台未读数一致。例如:现在app在后台收到一条消息,此时app的角标会变成1;app回到前台,会话的总未读数是1,此时又收到一条消息,会话的总未读数变成2。当app再次切到后台时,开发者应调用iOS系统方法给角标赋值为2。

示例:

- (void)applicationDidEnterBackground:(UIApplication *)application {
    NSInteger count = [[[NIMSDK sharedSDK] conversationManager] allUnreadCount];
    [[UIApplication sharedApplication] setApplicationIconBadgeNumber:count];
}

如果应用本身除了云信未读之外还有其他未读的话,程序上的 icon 值应为 云信未读数 + 其他未读数 之和,但是由于云信服务器默认维护的未读数只有消息的未读数,这样会导致新来的消息推送未读数将不计入应用的其他未读。

这种情况下,需要客户端提交服务器应该管理的未读数。

原型

@protocol NIMApnsManager <NSObject>
/**
 *  注册获取 badge 数量的回调函数
 *
 *  @param handler 获取 badge 回调
 */
- (void)registerBadgeCountHandler:(NIMBadgeHandler)handler;
@end

如需注册此回调函数,建议的时机是在初始化SDK([[NIMSDK sharedSDK] registerWithOption:option])之后,就进行注册。并在回调中返回一个全局变量,后续在执行程序期间,按需修改此全局变量即可。SDK会自动去读取其中的返回值并传递给云信服务器。

角标未读数上限

云信侧发起的推送可以配置iOS应用角标未读数上限,配置位置:云信控制台 > 选择应用 > IM专业版/IM免费版 > 功能配置 > iOS应用角标未读数上限配置

推送铃声设置

消息推送的声音设置支持自定义,类似于推送的 payload , 开发者只需设置NIMMessage - apnsPayload,在其中插入一对键为sound的键值对。需要注意推送音频的具体格式,具体请参考苹果官方文档

示例

NIMMessage *message = [[NIMMessage alloc] init];
message.text = @"消息示例";
message.apnsContent = @"发来了一条信息";
message.apnsPayload = @{@"sound":@"message.wav"};

推送标题设置

目前云信发起的APNs推送支持两种方式设置推送标题:

开发者无法在推送参数payload中配置aps字段,IM提供推送配置payload - apsField字段,对应APNs的payload - aps字段。该字段仅适用于iOS接收,Android无法解析此字段。下面展示通过IM配置的带有apsField字段的payload:

{
    // 上述示例代码出于展示目的进行了格式化,实际发送的时候不要进行格式化。
    "key1": "value1", //自定义键值对
    "apsField": {
        "mutable-content": 1,
        "sound": "abc.wav",
        "alert": {
            "title": "推送标题",
            "body": "推送内容"
        },
        "key2": "value2"
    }
}

群消息强制推送

用户在发消息的时候,可以通过配置 NIMMessage 里的 apnsMemberOption 字段实现群消息强制推送。即接收者屏蔽了当前会话(如免打扰),仍能够推送当前这条推送。

@interface NIMMessageApnsMemberOption : NSObject
/**
* 需要特殊推送的用户列表。如果这个字段为nil,则表示推送给当前会话内的所有用户。
*/
@property (nullable, nonatomic, copy) NSArray<NSString*> *userIds

/**
* 默认为 YES. 设置为 YES 表示即使推送列表中的用户屏蔽了当前会话(如免打扰),仍能够推送当前这条推送内容给相应用户
*/
@property (nonatomic, assign) BOOL forcePush

/**
* 推送文案,长度限制500字
*/
@property (nullable, nonatomic, copy) NSString *apnsContent
@end

点击通知栏跳转

当收到APNs推送后,用户点击通知栏后,希望进入指定的聊天界面时,可以参考如下方案:

发送方在构造消息对象时,在NIMMessage - apnsPayload中插入表示会话标识的信息(如自身的账号、群id、会话类型等),便于接收端获取到APNs - payload时解析。如:

{
    // 上述示例代码出于展示目的进行了格式化,实际发送的时候不要进行格式化。
    "sessionid": "zhangsan", //表明该条消息的发送者的账号或者消息所属的群组id
    "sessiontype": "p2p",   //表明条消息对应的会话是单聊还是群聊等等
    "apsField": {
        "mutable-content": 1,
        "sound": "abc.wav",
        "alert": {
            "title": "推送标题",
            "body": "推送内容"
        },
        "key2": "value2"
    }
}

当接收端收到APNs推送时,解析APNs - payload得到sessionidsessiontype,并跳转到对应的界面。

通知栏内容覆盖

iOS 10及以上支持推送消息覆盖,即后一条推送内容覆盖前面推送内容。一种典型的场景是撤回消息的情况:

A发消息给B,产生APNs推送,文案内容为“你好“。然后A撤回了这条消息,此时通知栏中的“你好”变为预设的“对方撤回了一条消息”。

一种建议的实现方式是:

注意:这里以iOS-SDK为例,当其他端发起撤回消息时,也是如此。

示例:

NSDictionary *payload = @{@"apns-collapse-id": Id};
[[NIMSDK sharedSDK].chatManager revokeMessage:message
                                  apnsContent:@"撤回一条消息"
                                  apnsPayload:payload
                              shouldBeCounted:![[NTESBundleSetting sharedConfig] isIgnoreRevokeMessageCount]
                                   completion:^(NSError * _Nullable error){}];