APNs(苹果推送通知服务)设置指南
APNs 是 Apple 的推送通知服务,它使第三方应用程序开发人员能够向安装在 Apple 设备上的应用程序发送通知数据。
此文主要介绍如何让 Apple 设备上的应用程序支持 APNs 以及如何在开发者中心设置相关的配置。
启用推送功能
应用要启用推送功能,分为两步:
- 开发项目开启推送的权限。
- 在苹果开发者网站为对应的 App ID 启用推送功能。
开启开发项目的推送权限
要在应用程序中添加所需的权限,请在 Xcode 项目中启用推送通知功能。
打开 Xcode 项目,在 Project > Target > Capabilities 页面中点击红框中的加号按钮,然后选择并添加 Push Notifications,添加后的结果如图中黄框所示:
启用苹果 App ID 的推送功能
登录苹果开发者网站,进入 Certificates, Identifiers & Profiles 页面,点击侧边栏中的 Identifiers,然后在列表中找到项目对应的 App ID(即 Xcode 项目中的 Bundle Identifier),点击并进入配置编辑页面,然后选择 Push Notifications 旁边的复选框,最后点击右上的 Save(保存)按钮,结果如下图所示:
选择推送方式
苹果提供了两种方式来发送通知,这两种方式各有优点和缺点,可以根据需要选择其中一种推送方式。
- 基于Token的推送方式(推荐)。
- 理论上它比基于证书的方式更快。
- 支持多个服务应用使用同一个Key。
- 支持用同一个Key给苹果开发者账号下的多个应用推送通知。
- 支持用同一个Key给苹果开发者账号下的测试、上架生产应用推送通知。
- 生产的Key不再有过期时间,无需像证书方式那样需要定期重新生成证书。
- 基于证书的推送方式。
- 证书和苹果的App ID绑定,一个证书只能向其绑定的苹果应用推送通知。
- APNS有开发、生产两个环境,可能需要为不同环境下的苹果应用配置对应的证书。
- 证书有过期时间,需要定期重新生成并配置。
总的来说,基于Token的推送方式在配置步骤、易用性以及功能性上,都要基于证书的推送方式,因此我们推荐使用基于Token的推送方式。(如果同时配置了两种方式,那么基于Token的方式优先级高于证书方式)
基于Token的推送方式(推荐)
优点:
APNs(苹果推送通知服务)设置指南
APNs 是 Apple 的推送通知服务,它使第三方应用程序开发人员能够向安装在 Apple 设备上的应用程序发送通知数据。
此文主要介绍如何让 Apple 设备上的应用程序支持 APNs 以及如何在开发者中心设置相关的配置。
启用推送功能
应用要启用推送功能,分为两步:
- 开发项目开启推送的权限。
- 在苹果开发者网站为对应的 App ID 启用推送功能。
开启开发项目的推送权限
要在应用程序中添加所需的权限,请在 Xcode 项目中启用推送通知功能。
打开 Xcode 项目,在 Project > Target > Capabilities 页面中点击红框中的加号按钮,然后选择并添加 Push Notifications,添加后的结果如图中黄框所示:
启用苹果 App ID 的推送功能
登录苹果开发者网站,进入 Certificates, Identifiers & Profiles 页面,点击侧边栏中的 Identifiers,然后在列表中找到项目对应的 App ID(即 Xcode 项目中的 Bundle Identifier),点击并进入配置编辑页面,然后选择 Push Notifications 旁边的复选框,最后点击右上的 Save(保存)按钮,结果如下图所示:
选择推送方式
苹果提供了两种方式来发送通知,这两种方式各有优点和缺点,可以根据需要选择其中一种推送方式。
- 基于Token的推送方式(推荐)。
- 理论上它比基于证书的方式更快。
- 支持多个服务应用使用同一个Key。
- 支持用同一个Key给苹果开发者账号下的多个应用推送通知。
- 支持用同一个Key给苹果开发者账号下的测试、上架生产应用推送通知。
- 生产的Key不再有过期时间,无需像证书方式那样需要定期重新生成证书。
- 基于证书的推送方式。
- 证书和苹果的App ID绑定,一个证书只能向其绑定的苹果应用推送通知。
- APNS有开发、生产两个环境,可能需要为不同环境下的苹果应用配置对应的证书。
- 证书有过期时间,需要定期重新生成并配置。
总的来说,基于Token的推送方式在配置步骤、易用性以及功能性上,都要基于证书的推送方式,因此我们推荐使用基于Token的推送方式。(如果同时配置了两种方式,那么基于Token的方式优先级高于证书方式)
基于Token的推送方式(推荐)
优点:
- 更安全:基于Token的推送采用JWT(JSON Web Token)认证,相比于证书,Token的安全性更高,因为其加密和签名机制能够有效的防止伪造。
- 长效性:Token的有效期通常可以长达一小时,在过期前自动续期,不需要频繁更新,这样省去了证书定期更新的麻烦。
- 方便管理和分发: 通过Token,可以在一个苹果开发者账号下支持多个应用或者服务使用同一个Key认证,不需要为每个APP生成单独的证书。
- 支持多设备部署: Token可以通过配置支持不同的服务器环境(如开发、生产),方便在多台服务器之间部署,提升可扩展性。
缺点:
- 配置稍微复杂:相较于证书,Token的生成和配置稍显复杂,需要在开发者账号中生成密钥和Token信息。
- 额外开发工作:基于Token的推送需要开发者在代码中实现JWT的生成和管理,尤其在服务端推送时,需要额外实现Token生成和更新逻辑。
iOS 流程简介
第一步:
CSR文件:首先我们要通过证书助手生成一个Certificate Signing Request(也就是CSR)的请求文件。如果你已经有证书了,跳过该步骤
继续之后选择保存位置,点击保存
这时该位置上会有一个CertificateSigningRequest.certSigningRequest的请求文件,也就是我们说的CSR文件。
第二步:
生成带有Push Notifications功能的AppID
左边边框取个名称,最好是取个能够辨别自己app的名称,不能输入特殊符号
右边是输入自己的bundle ID
这里要把Push Notifications勾选上,应用才能带有推送能力。
全部设定好之后继续,保存,生成了一个新的APP ID
生成完了之后,点击该AppID 给该AppID的Push Notifications配置CSR:
可以看到配置部分有两个:
Development SSL Certificate:开发推送证书配置,开发环境可以收到推送。
Production SSL Certificate:生产推送证书配置,线上环境可以收到推送。
点击Create Certificate, 添加如我们刚才的生成好的证书 CertificateSigningRequest.certSigningRequest
添加好之后下载下来 会生成一个aps.cer的文件。双击之后会在钥匙串中看到这个证书
至此,ios端需要的东西都已经准备结束。
我们ios端现在不需要给后台p12证书 也无需生成pem文件。(2021年之后就不需要生成pem文件了)
第三步:
接下来介绍一下后台的工作,后台需要配置Key ID和.p8密钥文件
要使用基于Token的推送方式,首先需从苹果开发者网站上生成并下载密钥.p8文件(Auth Key),之后把密钥传给服务端配置。
后台需要Keys:
创建好后会生成Key ID与密钥.p8文件。 需注意的是:密钥.p8文件只能下载一次,丢失就需要重新生成密钥文件。
写到这里基本差不多了 接下来是后台去配置其他token 推送内容之类的东西了。
还有我们自己去代码里面配置以及发送deviceToken给后台。还有接收到推送的代码
代码如下
在Appdelegate.m导入头文件
#import <UserNotifications/UserNotifications.h>
最后这是关于推送后台服务器那边需要配置的:developer.apple.com/documentati…
以后推送不成功!!:
1、让后台检查下是否发送成功(后台无需更改任何文件)。
2、iOS端检查下证书是否过期。
基于证书的推送方式
准备工作 在苹果后台给对应的App ID开通Push Notifications权限,生成profile(用于真机测试),生成APNs证书(用于服务端),
推送流程
deviceToken:手机的UDID + APP的BundleID (每个手机的UDID都不一样)
要使用基于证书的推送方式,需要从苹果开发者网站上为每个苹果应用生成单独的推送证书,每个苹果应用的推送证书又可分为开发环境推送证书和开发&生产环境推送证书,之后在开发者中心上传对应的推送证书,成功后即可使用该方式推送通知。
生成证书
登录苹果开发者网站,进入 Certificates, Identifiers & Profiles 页面,点击侧边栏中的 Certificates,然后点击左上方的添加按钮(+),接着按以下步骤操作:
选择对应的证书类型。一般选择 Apple Push Notification service SSL 类型的推送证书,这类证书又分为 Sandbox 和 Sandbox & Production 两种,其中 Sandbox 证书只能用于开发环境的苹果应用,而 Sandbox & Production 证书既可用于开发环境,也可用于生产环境的苹果应用。具体区别可以看选项下的描述,如下图所示:
选择完证书后,点击继续,接着选择 App ID(即 Xcode 项目中的 Bundle Identifier),选完后点击继续进入下一页面,会要求上传一个 CSR 文件。
在你的 Mac 上生成一个 CSR(Certificate Signing Request)文件,生成步骤如下:
启动位于 /Applications/Utilities 的 Keychain Access(钥匙串访问)。
选择 Keychain Access > Certificate Assistant > Request a Certificate From a Certificate Authority…(钥匙串访问 > 证书助理 > 从证书颁发机构请求证书…)。
证书助理对话框中,在 User Email Address(用户电子邮件地址)栏中输入一个电子邮件地址;在 Common Name(通用名称)字段中,输入密钥的名称(例如,Gita Kumar Dev Key)。
将 CA Email Address(CA 电子邮件地址)字段留空。
选择 Saved to disk(保存到磁盘),然后点击继续。
上传你的 CSR 文件(上一步中保存到本地的 .certSigningRequest 文件)并点击继续,最后下载生成的证书。
设置证书
证书生成后,需要将下载的证书和私钥上传到开发者中心。请按以下步骤操作:
在生成 CSR 文件的 Mac 上,双击下载的证书,macOS 会将其导入到 Keychain 中并和之前创建的 CSR 的密钥归为一组,如下图所示:
在 Keychain Access > login > My Certificates 中右键点击导入的证书(点击证书,不要点击对应的密钥),选择 Export(导出),将证书以 .p12 格式保存到磁盘,这时会有弹窗提示输入密码来保护导出的证书,请不要设置密码,将两个输入框留空,点击 OK;之后可能还有一个弹窗,要求输入 macOS 的 login(登录)密码以允许让证书导出,请输入密码并点击 Allow。
key转换:
需要将上面的2个.p12文件转成.pem格式:
openssl pkcs12 -clcerts -nokeys -out cert.pem -in Push_Chat_cert.p12
openssl pkcs12 -nocerts -out key.pem -in Push_Chat_cert_key.p12
如果需要对key不进行加密:
openssl rsa -in key.pem -out key.unencrypted.pem
然后就可以 合并两个.pem文件, 这个ck.pem就是服务端需要的证书了:
cat cert.pem key.unencrypted.pem > apns-dev.pem
iOS代码
注册推送: 首先引入UserNotifications.framework,引入头文件
#import <UserNotifications/UserNotifications.h>
在didFinishLaunchingWithOptions方法中注册通知:
if (@available(iOS 10.0, *)) {
UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
center.delegate = self;
[center requestAuthorizationWithOptions:UNAuthorizationOptionAlert | UNAuthorizationOptionSound | UNAuthorizationOptionBadge completionHandler:^(BOOL granted, NSError *_Nullable error) { if (granted) { dispatch_async(dispatch_get_main_queue(), ^{ [[UIApplication sharedApplication] registerForRemoteNotifications];
});
} else {
NSLog(@"APNs注册失败");
}
}];
} else if (@available(iOS 8.0, *)) {
UIUserNotificationType types = UIUserNotificationTypeBadge | UIUserNotificationTypeSound | UIUserNotificationTypeAlert;
UIUserNotificationSettings *mySettings = [UIUserNotificationSettings settingsForTypes:types categories:nil];
[[UIApplication sharedApplication] registerUserNotificationSettings:mySettings];
//register for deviceToken
[[UIApplication sharedApplication] registerForRemoteNotifications];
}
获取deviceToken 一般来说会将获取到的deviceToken发送给后台,用于指定设备发送通知。
//注册成功
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken{
NSString *deviceTokenStr = [[[[deviceToken description]
stringByReplacingOccurrencesOfString:@"<" withString:@""]
stringByReplacingOccurrencesOfString:@">" withString:@""]
stringByReplacingOccurrencesOfString:@" " withString:@""];
NSLog(@"deviceTokenStr:\n%@",deviceTokenStr);
}
//注册失败
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error{
NSLog(@"error -- %@",error);
}
注意,如果注册失败,先检查证书是否配置正确,工程是否打开了push开关。另外,app只有在第一次安装时才会弹出系统权限提示。如果没有弹出,可以把app删除,重新build运行一次。
处理推送过来的消息,iOS10及以上:
//在前台是否展示
- (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler{
// 这个方法表明应用在前台也会通知声音、角标和提示
completionHandler(UNNotificationPresentationOptionBadge|
UNNotificationPresentationOptionSound|
UNNotificationPresentationOptionAlert);
}
//点击收到的消息,会触发下面方法,处理收到的数据
- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)())completionHandler{
// [self handlePushMessage:response.notification.request.content.userInfo];
completionHandler();
}
iOS8以上iOS10以下:
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary * _Nonnull)userInfo fetchCompletionHandler:(void (^ _Nonnull)(UIBackgroundFetchResult))completionHandler{
NSLog(@"didReceiveRemoteNotification:%@",userInfo);
/*
UIApplicationStateActive 应用程序处于前台
UIApplicationStateBackground 应用程序在后台,用户从通知中心点击消息将程序从后台 调至前台
UIApplicationStateInactive 用用程序处于关闭状态(不在前台也不在后台),用户通过点击通知中心的消息将客户端从关闭状态调至前台
*/
//应用程序在前台给一个提示特别消息
if (application.applicationState == UIApplicationStateActive) {
//应用程序在前台
[self createAlertViewControllerWithPushDict:userInfo];
}else{
//其他两种情况,一种在后台程序没有被杀死,另一种是在程序已经杀死。用户点击推送的消息进入app的情况处理。
[self handlePushMessage:userInfo];
}
completionHandler(UIBackgroundFetchResultNewData);
}
后台的证书是.pem格式,可通过命令将.p12文件转为.pem文件:
openssl pkcs12 -in origin.p12 -out dis.pem -nodes
AppDelegate.m参考
#import "AppDelegate.h"
#import <UserNotifications/UserNotifications.h>
@interface AppDelegate ()<UNUserNotificationCenterDelegate>
@end
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
if ([[UIDevice currentDevice].systemVersion floatValue] >= 10.0) {
UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
center.delegate = self;
[center requestAuthorizationWithOptions:(UNAuthorizationOptionBadge | UNAuthorizationOptionSound | UNAuthorizationOptionAlert) completionHandler:^(BOOL granted, NSError * _Nullable error) {
if (!error) {
NSLog(@"succeeded!");
}
}];
} else if ([[UIDevice currentDevice].systemVersion floatValue] >= 8.0){
//ios8以后的
/**
UIUserNotificationTypeNone = 0,
UIUserNotificationTypeBadge = 1 << 0,
UIUserNotificationTypeSound = 1 << 1,
UIUserNotificationTypeAlert = 1 << 2
*/
UIUserNotificationType type = UIUserNotificationTypeBadge | UIUserNotificationTypeSound|UIUserNotificationTypeAlert;
UIUserNotificationSettings *setting = [UIUserNotificationSettings settingsForTypes:type categories:nil];
//注册通知类型
[application registerUserNotificationSettings:setting];
//申请使用通知
[application registerForRemoteNotifications];
}else{
/**
*枚举类型
UIRemoteNotificationTypeNone = 0, //不接收推送消息
UIRemoteNotificationTypeBadge = 1 << 0, 接收图标数字
UIRemoteNotificationTypeSound = 1 << 1, 接收时的音效
UIRemoteNotificationTypeAlert = 1 << 2, 接收消息文字弹窗
UIRemoteNotificationTypeNewsstandContentAvailability = 1 << 3, 接收订阅消息
*/
UIRemoteNotificationType type = UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound | UIRemoteNotificationTypeAlert;
/**
当用户第一次启动程序的时候就获取deviceToken
该方法在iOS8就过期le
*/
//调用该方法,系统就会自动发送UDID和当前程序的BundleID到苹果的APNS服务器
[application registerForRemoteNotificationTypes:type];
}
//取出推送通知:
//当程序被杀死或者退出了,接受到的推送信息会保留在launchOptions中,当我们重新打开软件的时候再取出来
//取出在程序退出的时候接受到的推送消息
NSDictionary *userInfo = launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey];
if (userInfo) {
NSLog(@"%@", userInfo);
}
return YES;
}
/**
获取到用户当前程序的deviceToken后会会调这个方法
*/
-(void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken{
NSLog(@"%@",deviceToken);
//deviceToken:就是我们需要上传服务器的deviceToken;
}
/*
*iOS7以前
*ios7以前苹果支持多任务, iOS7以前的多任务是假的多任务
*而iOS7开始苹果才真正的推出了多任务
*接收到远程服务器推送过来的内容就会调用
*注意: 只有应用程序是打开状态(前台/后台(程序没有杀死)), 才会调用该方法
*如果应用程序是关闭状态会调用didFinishLaunchingWithOptions
*/
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
{
/*
如果应用程序在后台 , 只有用户点击了通知之后才会调用
如果应用程序在前台, 会直接调用该方法
即便应用程序关闭也可以接收到远程通知,但是改方法不会调用
*/
NSLog(@"%@", userInfo);
}
/**
*ios7以后
*ios7以后用这个处理后台任务接收到得远程通知
*接收到远程服务器推送过来的内容就会调用
*/
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
{
NSLog(@"%@", userInfo);
NSNumber *contentid = userInfo[@"content-id"];
if (contentid) {
//注意: 在此方法中一定要调用这个调用block, 告诉系统是否处理成功.
// 以便于系统在后台更新UI等操作
/*
UIBackgroundFetchResultNewData, 成功接收到数据
UIBackgroundFetchResultNoData, 没有;接收到数据
UIBackgroundFetchResultFailed 接收失败
*/
completionHandler(UIBackgroundFetchResultNewData);
}else
{
completionHandler(UIBackgroundFetchResultFailed);
}
}
/**
*iOS10.0以后的方法
*/
//App在后台运行及程序退出杀死 会调用的方法
- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(nonnull UNNotificationResponse *)response withCompletionHandler:(nonnull void (^)(void))completionHandler{
NSDictionary *userInfo = response.notification.request.content.userInfo;
NSLog(@"App在后台时候-%@", userInfo);
completionHandler();
}
// App在前台时候回调:用户正在使用状态
- (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler {
NSDictionary *userInfo = notification.request.content.userInfo;
NSLog(@"App在前台时候回调-%@", userInfo);
//可以设置当收到通知后, 有哪些效果呈现(声音/提醒/数字角标)
completionHandler(UNNotificationPresentationOptionBadge | UNNotificationPresentationOptionSound | UNNotificationPresentationOptionAlert);
}
@end