iOS App 集成极光推送的总结

Posted by Calvin on 2017-04-16

前段时间时间写了一篇配置腾讯信鸽推送证书的文章,本来打算在项目内集成一下信鸽推送,但后来看了文档和极光做了对比后,还是打算使用极光推送,现在就项目内添加极光推送做一下总结

这里先不对 APNs 做过多赘述,写一下极光推送的集成。

创建应用并上传对应的证书

关于证书的制作可以参考之前的文章:iOS 推送证书配置并为腾讯信鸽生成pem文件

腾讯信鸽需要制作好 .p12 证书后再生成对应的 .pem 证书,而极光相对简单,只需要导出 .p12 证书后上传并生成对应的 AppKey 即可。

配置工程

导入 SDK

导入极光 SDK 推荐使用 Cocoapods,相对于手动导入更简单便捷。

pod 'JPush'

如果需要安装指定版本则使用以下方式(以3.0.2版本为例):

pod 'JPush', '3.0.2'

Build Settings

如果你的工程需要支持小于7.0的iOS系统,请到 Build Settings 关闭 bitCode 选项,否则将无法正常编译通过。

  • 设置 Search Paths 下的 User Header Search Paths 和 Library Search Paths,比如SDK文件夹(默认为lib)与工程文件在同一级目录下,则都设置为”$(SRCROOT)/{静态库所在文件夹名称}”即可。

如使用 Xcode8 及以上环境开发,请开启 Application Target的Capabilities->Push Notifications 选项,如图:

20170416149233637219018.jpg

允许 Xcode7 支持 Http 传输方法

如果使用的是 2.1.9 及以上的版本则不需要配置此步骤

选择1:根据域名配置

  • 在项目的info.plist中添加一个Key:NSAppTransportSecurity,类型为字典类型。
  • 然后给它添加一个NSExceptionDomains,类型为字典类型;
  • 把需要的支持的域添加給NSExceptionDomains。其中jpush.cn作为Key,类型为字典类型。
  • 每个域下面需要设置2个属性:NSIncludesSubdomains、NSExceptionAllowsInsecureHTTPLoads。 两个属性均为Boolean类型,值分别为YES、YES。

如图:
20170416149233646697605.jpg

选择2:全局配置

<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>

添加头文件

AppDelegate.m 引用头文件

// 引入JPush功能所需头文件
#import "JPUSHService.h"
// iOS10注册APNs所需头文件
#ifdef NSFoundationVersionNumber_iOS_9_x_Max
#import <UserNotifications/UserNotifications.h>
#endif
// 如果需要使用idfa功能所需要引入的头文件(可选)
#import <AdSupport/AdSupport.h>

添加Delegate

@interface AppDelegate ()<JPUSHRegisterDelegate>
@end

添加初始化代码

添加初始化APNs代码

-(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 方法内添加如下代码:

//Required
//notice: 3.0.0及以后版本注册可以这样写,也可以继续用之前的注册方式
JPUSHRegisterEntity * entity = [[JPUSHRegisterEntity alloc] init];
entity.types = JPAuthorizationOptionAlert|JPAuthorizationOptionBadge|JPAuthorizationOptionSound;
if ([[UIDevice currentDevice].systemVersion floatValue] >= 8.0) {
// 可以添加自定义categories
// NSSet<UNNotificationCategory *> *categories for iOS10 or later
// NSSet<UIUserNotificationCategory *> *categories for iOS8 and iOS9
}
[JPUSHService registerForRemoteNotificationConfig:entity delegate:self];
添加初始化JPush代码

-(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 方法内添加如下代码:

// Optional
// 获取IDFA
// 如需使用IDFA功能请添加此代码并在初始化方法的advertisingIdentifier参数中填写对应值
NSString *advertisingId = [[[ASIdentifierManager sharedManager] advertisingIdentifier] UUIDString];
// Required
// init Push
// notice: 2.1.5版本的SDK新增的注册方法,改成可上报IDFA,如果没有使用IDFA直接传nil
// 如需继续使用pushConfig.plist文件声明appKey等配置内容,请依旧使用[JPUSHService setupWithOption:launchOptions]方式初始化。
[JPUSHService setupWithOption:launchOptions appKey:appKey
channel:channel
apsForProduction:isProduction
advertisingIdentifier:advertisingId];

部分参数说明:

  • appKey
    • 填写管理Portal上创建应用后自动生成的AppKey值。请确保应用内配置的 AppKey 与 Portal 上创建应用后生成的 AppKey 一致。
  • channel
    • 指明应用程序包的下载渠道,为方便分渠道统计,具体值由你自行定义,如:App Store。
  • apsForProduction
    • 1.3.1版本新增,用于标识当前应用所使用的APNs证书环境。
    • 0 (默认值)表示采用的是开发证书,1 表示采用生产证书发布应用。
    • 注:此字段的值要与Build Settings的Code Signing配置的证书环境一致。
注册APNs成功并上报DeviceToken

在AppDelegate.m 实现该回调方法并添加回调方法中的代码

- (void)application:(UIApplication *)application
didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
/// Required - 注册 DeviceToken
[JPUSHService registerDeviceToken:deviceToken];
}

实现注册APNs失败接口(可选)

- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error {
//Optional
NSLog(@"did Fail To Register For Remote Notifications With Error: %@", error);
}
添加处理APNs通知回调方法

在AppDelegate.m 实现该回调方法并添加回调方法中的代码

系统是 iOS 10 应用在前台运行时候的处理方法

#pragma mark- JPUSHRegisterDelegate
// iOS10 应用在前台的时候
- (void)jpushNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(NSInteger))completionHandler {
// Required
NSDictionary * userInfo = notification.request.content.userInfo;
if([notification.request.trigger isKindOfClass:[UNPushNotificationTrigger class]]) {
[JPUSHService handleRemoteNotification:userInfo];
}
completionHandler(UNNotificationPresentationOptionAlert); // 需要执行这个方法,选择是否提醒用户,有 Badge、Sound、Alert 三种类型可以选择设置
// 收到推送通知后的处理
}

系统是 iOS 10 应用在后台运行并没有完全并杀死进程时候的处理方法

// iOS 10 Support 应用在后台还没有被杀死后台时
- (void)jpushNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)())completionHandler {
// Required
NSDictionary * userInfo = response.notification.request.content.userInfo;
if([response.notification.request.trigger isKindOfClass:[UNPushNotificationTrigger class]]) {
[JPUSHService handleRemoteNotification:userInfo];
}
completionHandler(); // 系统要求执行这个方法
// 收到推送通知后的处理
}

iOS 7 系统之后的处理方法

#pragma mark --
#pragma mark -- iOS7之后
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
// Required, iOS 7 Support
[JPUSHService handleRemoteNotification:userInfo];
completionHandler(UIBackgroundFetchResultNewData);
if (application.applicationState == UIApplicationStateActive || application.applicationState == UIApplicationStateBackground) {
// 程序当前正处于前台或者处于后台没有被杀死时
}else if(application.applicationState == UIApplicationStateInactive)
{
NSLog(@"inactive");
// 程序完全处于后台
}
}

iOS 7 系统之前的处理方法

#pragma mark --
#pragma mark -- iOS7.0之前
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo {
[JPUSHService handleRemoteNotification:userInfo];
application.applicationIconBadgeNumber = 0;
// 应用在前台 或者后台开启状态下,不跳转页面,让用户选择。
if (application.applicationState == UIApplicationStateActive || application.applicationState == UIApplicationStateBackground) {
}else//杀死状态下,直接跳转到跳转页面。
{
[self goToMssageViewControllerWith:userInfo];
}
XZMLog(@"收到的推送信息 == %@",userInfo);
}

大家可能会比较疑惑,现在 App 大部分系统都是 iOS 10,但是 iOS 10 的应用进程被完全杀死以后的推送处理在哪里进行呢?

其实,对于 iOS 10 系统,如果用户几天没有打开 App,进程已经完全被杀死的情况下,用户点击推送消息之后直接在系统的- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 方法里进行处理,可以增加以下代码:

NSDictionary *remoteNotification = [launchOptions objectForKey: UIApplicationLaunchOptionsRemoteNotificationKey];
if (remoteNotification)
{
// 程序完全退出时,点击通知,添加需求。。。
}else{
// 正常进入程序
}

本地的 badge 设置

[application setApplicationIconBadgeNumber:0]; //清除角标
[application cancelAllLocalNotifications];

JPush SDK 相关事件监听

extern NSString *const kJPFNetworkIsConnectingNotification; // 正在连接中
extern NSString * const kJPFNetworkDidSetupNotification; // 建立连接
extern NSString * const kJPFNetworkDidCloseNotification; // 关闭连接
extern NSString * const kJPFNetworkDidRegisterNotification; // 注册成功
extern NSString *const kJPFNetworkFailedRegisterNotification; //注册失败
extern NSString * const kJPFNetworkDidLoginNotification; // 登录成功
  • Registration id 需要添加注册kJPFNetworkDidLoginNotification通知的方法里获取,也可以调用[registrationIDCompletionHandler:]方法,通过completionHandler获取

小结

1、通知内容可以按照系统方法或者自定义通知或者富媒体消息进行。

系统方法有对应的格式,推送消息的展示只需要在对应格式上添加字段即可(需要根据推送的内容和后台商定相关的字段)。

自定义通知和富媒体消息的推送可以参考极光官方文档进行配置。

2、大部分 iOS 端通知都是涉及到 badge 的,具体 手机上的展示以及后台推送时该怎么传参,需要移动端和后台配合,并把推送消息的是否已读的状态传到后端做对应的处理,才能达到好的用户体验。

根据这几天的观察,大部分 App 存在一个问题:当我 App 已经接受到了多条推送消息时,当我点击某一条推送进入到 App 后 badge 直接清除,通知栏也随之清除,我看了一条推送消息后想再查看其他不错的推送时已经找不到了。

当然也有部分 App 做的比较好,就是有多条推送消息时,点击某一条推送消息后,badge 对应的 -1,看完以后可以继续查看其他推送消息,后台 badge 也会对应更新,再有新的推送时 badge 对应 +1。所以,在做推送时,这些小的细节还需要完善,提升用户体验也要根据实际情况做对应的处理。这里不再过多赘述。

3、当在开发环境想要转到生产环境时,务必修改apsForProduction为生产环境。我在本地配置时,因为是第一版我想转到生产环境进行测试,但是试了几次之后都没有成功。后来我直接改了 apsForProduction 参数然后打包上传到 App Store,当审核通过后下载下来的 App 对应的就是生产环境。

[JPUSHService setupWithOption:launchOptions appKey:appKey
channel:channel
apsForProduction:isProduction
advertisingIdentifier:advertisingId];

查了这个问题,官方说不仅要修改字段,还需要更改本地的证书为生产证书,尝试无果后搁置,不过没有影响开发环境和生产环境。

4、通篇看来,整个推送的集成分为以下步骤

  • SDK 的导入和编译设置
  • 添加头文件并初始化 APNs 和 JPush
  • 初始化成功后实现对应的代理方法并对通知的情况做对应的处理

整体步骤相对不是很复杂,但是对于推送消息的处理,用户体验的处理还是比较难的。这就需要考量整个产品的设计和程序员对产品的理解,有时候产品可能也考虑不到诸如 badge 如何变化的细节,这就需要程序员在集成时多从产品角度思考,多优化细节了。


「04.17 更新」别名的设置

某些时候,我们需要推送给信息给个别用户。今天下午设置了一下别名,更新总结下:

首先注册通知,等待 JPush 登录成功后再去设置别名:

// JPush 监听登陆成功
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(networkDidLogin:)
name:kJPFNetworkDidLoginNotification
object:nil];

再实现别名设置的方法

/**
* 登录成功,设置别名,移除监听
*
* @param notification <#notification description#>
*/
- (void)networkDidLogin:(NSNotification *)notification {
NSLog(@"已登录");
[JPUSHService setTags:nil alias:@"123" fetchCompletionHandle:^(int iResCode,NSSet *iTags,NSString *iAlias) {
XZMLog(@"rescode: %d, \ntags: %@, \nalias: %@\n", iResCode, iTags , iAlias);// 对应的形态码前往为0,代表成功
}];
// 移除通知
[[NSNotificationCenter defaultCenter] removeObserver:self
name:kJPFNetworkDidLoginNotification
object:nil];
}

注意:iResCode == 0 时代表注册别名成功,在注册别名时可能会出现连接超时或者别名格式不正确的情况,错误状态参考『极光 SDK API 文档』

关于推送别名、标签详细的理解在『推送人群的选择-技术篇』有详细的说明。