前言
推送服务可以说是所有 App 的标配,不论是哪种类型的 App,推送都从很大程度上决定了 App 的 打开率、使用率、存活率 。因此,熟知并掌握推送原理及方法,对每一个开发者来说都是必备技能,对每一个依赖 App 的公司来说都至关重要。本文主要讨论项目中使用极光推送来实现推送业务。
为什么要使用第三发推送
自己做推送从开发成本上来说需要专人进行开发,并且需要一定数量的服务器和带宽支持,在开发完成后的使用过程中还需要有专人进行维护。
但是如果使用第三方,你只需要集成SDK就可以实现功能,不仅减小了开发成本与维护成本,甚至在推送稳定性上第三方也会比自己做的推送更好一些。第三方推送目前开始尝试精准推送,说白了就是将不同的内容推送给不同的人群,比如将北京的新闻推送给北京的用户。
现在做第三方推送的服务提供商有百度云推送、友盟推送、极光推送等,各有各的优势,但是实现底层都差不多。因此除非公司特别大,特别有实力,不然使用第三方推送服务提供商要远比自己开发好的多。
源码实现推送服务
注册推送
// 引 JPush功能所需头 件
#import "JPUSHService.h"
// iOS10注册APNs所需头 件
#ifdef NSFoundationVersionNumber_iOS_9_x_Max
#import <UserNotifications/UserNotifications.h>
#endif
<JPUSHRegisterDelegate>
- (void)appSetForLaunchOptions :(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];
// Required
// init Push
// notice: 2.1.5版本的SDK新增的注册 法,改成可上报IDFA,如果没有使 IDFA直接传nil
// 如需继续使 pushConfig.plist 件声明appKey等配置内容,请依旧使 [JPUSHService setupWithOption:launchOptions] 式初始化。
[JPUSHService setupWithOption:launchOptions
appKey:@"a8731XXXXXXXXXXXXXXXX"
channel:@"App Store"
apsForProduction:0
advertisingIdentifier:nil];
[JPUSHService registrationIDCompletionHandler:^(int resCode, NSString *registrationID) {
if(resCode == 0){
NSLog(@"registrationID获取成功:%@",registrationID);
[[MethodTool shareTool]setUserDefaults:registrationID :@"registrationID"];
}
else{
NSLog(@"registrationID获取失败,code:%d",resCode);
}
}];
//*******************************************************************
}
// 调用
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[self appSetForLaunchOptions:launchOptions];
}
#pragma mark ----------------------推送注册结果返回--------------
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
/// Required - 注册 DeviceToken
[JPUSHService registerDeviceToken:deviceToken];
}
-(void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error {
//Optional
NSLog(@"did Fail To Register For Remote Notifications With Error: %@", error);
}
iOS 10 系统之前
这个系统级别的方法,被触发的条件:
- 程序在前台收到推送 (不会有顶部横条提示)
- 通过点击推送启动程序
- iOS10的静默式推送
//ios 7 前端 、点击、静默式推送有 包括iOS10的静默式推送
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
NSLog(@"fetchCompletionHandler: /n %@",userInfo);
[[MethodTool shareTool]setUserDefaults:@"推送后台" :@"tuisong" ];
// Required, iOS 7 Support
[JPUSHService handleRemoteNotification:userInfo];
completionHandler(UIBackgroundFetchResultNewData);
[JPUSHService setBadge:0];//清空JPush服务器中存储的badge值。
[self getMessageDetail:[[MethodTool shareTool]cleanData:userInfo[@"fid"]]];
}
这个方法可以不用实现
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo {
// Required,For systems with less than or equal to iOS6
NSLog(@"didReceiveRemoteNotification: /n %@",userInfo);
[JPUSHService handleRemoteNotification:userInfo];
}
iOS 10 系统及其之后
这个方法是推送横幅出现之前会被调用
// iOS 10 Support
- (void)jpushNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(NSInteger)
)completionHandler {
// Required
NSDictionary * userInfo = notification.request.content.userInfo;
NSLog(@"111111 didReceiveNotificationResponse: /n %@",userInfo);
if([notification.request.trigger isKindOfClass:[UNPushNotificationTrigger class]
]) {
[JPUSHService handleRemoteNotification:userInfo];
}
completionHandler(UNNotificationPresentationOptionAlert);
}
这个方法被触发的条件:
- 程序在前台受收到推送 ,会有顶部横条提示,点击横幅时
- 通过点击推送启动程序时
// 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(); // 系统要求执 这个 法
[JPUSHService setBadge:0];//清空JPush服务器中存储的badge值。
//用户点击横幅进来时
[self getMessageDetail:[[MethodTool shareTool]cleanData:userInfo[@"fid"]]];
或者
//跳转到学校预警页面
ManageSchoolListViewController *shcool = [[ManageSchoolListViewController alloc]init];
shcool.notificationForPush = YES;
UIViewController *controller = [UIApplication sharedApplication].keyWindow.rootViewController;
if ([controller isKindOfClass:[UINavigationController class]]) {
[(UINavigationController *)controller pushViewController:shcool animated:YES];
}else if ([controller isKindOfClass:[UITabBarController class]]){
UINavigationController *VC = (UINavigationController *)[(UITabBarController *)controller selectedViewController];
[VC pushViewController:shcool animated:YES];
}
}
注销角标
- (void)applicationDidEnterBackground:(UIApplication *)application {
[[UIApplication sharedApplication] setApplicationIconBadgeNumber:0];
}
极光推送注意事项
registrationID 只需要在登录时跟账号绑定,在退出的时候跟账号解绑(账号绑定的registrationID为空即可)。如果不在退出的时候清空registrationID,会出现账号退出仍然给该账号发送指定推送的Bug。
registrationID 不会随便改变,在每次登陆后绑定一个就够了。在APP卸载后就会发生改变。
-
使用极光推送平台时,添加附加字段
APP收到的推送信息
{ "_j_business" = 1; "_j_msgid" = 36028797200845676; "_j_uid" = 6126767282; aps = { alert = "\U6d4b\U8bd5"; badge = 1; sound = default; }; fid = 1; }
-
使用极光推送平台实现静默式推送
后台式推送需要开启后台模式
-
下面的代码一直执行不了,并且在控制台打印,JIGUANG服务器链接失败,我还以为是我的证书失效了,APPKEY变化了,纠结了一会,过一会居然自动好了,推测是极光服务器异常造成的注册registrationID 没有响应。
[JPUSHService registrationIDCompletionHandler:^(int resCode, NSString *registrationID) { if(resCode == 0){ NSLog(@"registrationID获取成功:%@",registrationID); [[MethodTool shareTool]setUserDefaults:registrationID :@"registrationID"]; } else{ NSLog(@"registrationID获取失败,code:%d",resCode); } }];
-
系统推送授权弹框是否允许,如果你选允许,就会走下面第一条代理,上传完 deviceToken 极光会进行下一步 registrationID 的注册返回。
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { /// Required - 注册 DeviceToken [JPUSHService registerDeviceToken:deviceToken]; } -(void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error { //Optional NSLog(@"did Fail To Register For Remote Notifications With Error: %@", error); }
-
在使用极光推送管理平台的时候,一定要看清楚推送的iOS设备是开发环境还是生产环境。
-
获取App 运行状态:
UIApplicationState state = [UIApplication sharedApplication].applicationState; 说明: UIApplication.h typedef NS_ENUM(NSInteger, UIApplicationState) { UIApplicationStateActive, UIApplicationStateInactive, UIApplicationStateBackground } NS_ENUM_AVAILABLE_IOS(4_0); 1、判断如何启动App 启动App时会自动调用didFinishLaunchingWithOptions方法:如果launchOptions包含UIApplicationLaunchOptionsRemoteNotificationKey ,则表示用户是通过点击APNs 启动App; 如果不含有对应键值,则表示用户可能是直接点击icon启动App。 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // apn 内容获取:NSDictionary *remoteNotification = [launchOptions objectForKey: UIApplicationLaunchOptionsRemoteNotificationKey] ... } 3、示例代码: // iOS 6 Remote Notificatio - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo { // 取得 APNs 标准信息内容 NSDictionary *aps = [userInfo valueForKey:@"aps"]; NSString *content = [aps valueForKey:@"alert"]; //推送显示的内容 NSInteger badge = [[aps valueForKey:@"badge"] integerValue]; //badge数量 NSString *sound = [aps valueForKey:@"sound"]; //播放的声音 // 取得Extras字段内容 NSString *customizeField1 = [userInfo valueForKey:@"customizeExtras"]; //服务端中Extras字段,key是自己定义的 NSLog(@"content =[%@], badge=[%d], sound=[%@], customize field =[%@]",content,badge,sound,customizeField1); }
-
更新了程序,再次安装后还是没有推送能收到?
- 【1】关机重启手机
- 【2】卸载这个程序重新安装(很关键,大部分因为这)
-
【3】极光平台的证书配置正确,本地的APPKEY也是正确的,registrationID和iAlias 也都获取到了,但是就是死活收不到推送,请检查一下工程的 Bundle identifier 和极光配置的证书的Bundle identifier 是否一致,我就遇到过这样的低级错误,浪费了好多时间!最好把 info.plist里面的Bundle identifier 手动复制后粘贴上!确保工程和平台的Bundle identifier一致!
-
发布到蒲公英中的内测版本的APP,极光推送时,也需要推送生产环境,开发环境下的推送收不到,我记得以前是可以的,不知道什么时候变了。
彻底杀掉程序,点击通知进入应用会加载 application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
然后才会加载通知的点击事件。
{
_j_msgid = 54043216545735443;
from = "Jpush";
_j_business = 1;
_j_uid = 27562363129;
aps = {
alert = {
title = "notice";
body = "hello world,welcome"
};
badge = 7;
sound = "default"
};
param = "{"link":"www.dsfk","id":"9"}";
sound = "default";
type = "32"
}
给用户设置标签
消息推送,有时候只想推送给指定的人或者指定的版本,那么这时候我们就需要对设备设置标签或者别名了,这样推送的时候可以根据标签或者别名推送给指定的用户
//设置极光标签
[JPUSHService setTags:[NSSet setWithObject:@"yk"]callbackSelector:@selector(tagsAliasCallback:tags:alias:) object:self];
//设置标签的回调
- (void)tagsAliasCallback:(int)iResCode
tags:(NSSet *)tags
alias:(NSString *)alias {
NSLog(@"TagsAlias回调:%d", iResCode);
// 0 的时候是设置成功了。
}
+++++++++++++++++++++++++++++++++ 更新 ++++++++++++++++++++++++++++++++
设置别名
别名是为了关联账号的,registrationID 是对应一台手机的,需要在获得 registrationID 后绑定账号,生产对应的别名。后台就可以根据别名对单个用户进行推送了。
[JPUSHService registrationIDCompletionHandler:^(int resCode, NSString *registrationID) {
if(resCode == 0){
NSLog(@"registrationID获取成功:%@",registrationID);
[[MethodTool shareTool]setUserDefaults:registrationID :@"registrationID"];
[JPUSHService setAlias:[NSString stringWithFormat:@"%@",@"广东100008"] completion:^(NSInteger iResCode, NSString *iAlias, NSInteger seq) {
NSLog(@"rescode: %ld, \ntags: %@, \iAlias: %@\n", (long)iResCode, @"tag" , iAlias);
} seq:0];
}
else{
NSLog(@"registrationID获取失败,code:%d",resCode);
}
}];
如何在APP未启动的时候,获取到推送的内容
首先要开启APP的后台模式,然后需要设置推送为静默式的推送,才能在APP未启动的时候触发代理方法,在代理方法中获取到推送的内容
//ios 7 前端 、点击、静默式推送有 包括iOS10的静默式推送
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
NSLog(@"fetchCompletionHandler: /n %@",userInfo);
[[MethodTool shareTool]setUserDefaults:@"推送后台" :@"tuisong" ];
// Required, iOS 7 Support
[JPUSHService handleRemoteNotification:userInfo];
completionHandler(UIBackgroundFetchResultNewData);
}
更多极光推送问题可参考:iOS 不点击通知栏,怎么获取消息内容?
如何根据推送内容动态展示角标
首先,普通的推送是无法在APP未启动的时候获取到推送内容的,除非是静默式推送。但是极光的 API中有直接设置这个推送要显示的角标 setBadge,直接设置即可。
角标始终为1?
不管我们发送几次通知,这个APP角标永远为1
去极光官网,在推送消息的时候,设置badge属性,改为+1,这样每次收到通知,角标会加1了。
小结
在实际的产品和运营中经常是使用组合技巧: Push,短信,微信推送组合协同,提高效率。可以在服务端来统计分析用户行为,然后将指定的 tags 发送至手机,手机接收后再为用户打上对应的 tags。这样就可以做到定向产品推广了。