iOS开发-集成个推远程推送
证书配置+个推应用配置略过
Xcode集成
在项目中添加 Notification Service Extension
- 打开
Xcode 9
,菜单中选择File
->New
->Target
->Notification Service Extension
:
填写Target信息时需要注意以下两点:
Extension 的 Bundle Identifier 不能和 Main Target(也就是你自己的 App Target)的 Bundle Identifier 相同,否则会报 BundeID 重复的错误。
Extension 的 Bundle Identifier 需要在 Main Target 的命名空间下,比如说 Main Target 的 BundleID 为 ent.getui.xxx,那么Extension的BundleID应该类似与ent.getui.xxx.yyy这样的格式。如果不这么做,会引起命名错误。
因此我们建议使用<Main Target Bundle ID>.NotificationService
格式作为Extension的BundleID.
- 添加 Notification Service Extension 后会生成相应的 Target。点Finish按钮后会弹出是否激活该 Target 对应 scheme 的选项框,选择 Activate,如果没有弹出该选项框,需要自行添加相应的 scheme。如下图:
- 开启多媒体地址 Http 访问支持:
[图片上传失败...(image-e4dca5-1526711478100)]
CocoaPods集成
#个推
pod 'GTSDK', '2.0.0.0-noidfa'
target 'NotificationService' do
platform :ios, "10.0"
pod 'GTExtensionSDK'
end
开启推送功能
在 Xcode 8.x
以上,必须开启Push Notification
能力。找到应用Target
设置中的Capabilities
-> Push Notifications
,确认开关已经设为ON
状态。如果没有开启该开关,在 Xcode 8.x
上编译后的应用将获取不到DeviceToken
:
后台运行权限设置
为了更好支持消息推送,提供更多的推送样式,提高消息到达率,需要配置后台运行权限:
image在项目设置中添加以下系统库支持:
libc++.tbd
libz.tbd
libsqlite3.tbd
Security.framework
MobileCoreServices.framework
SystemConfiguration.framework
CoreTelephony.framework
AVFoundation.framework
CoreLocation.framework
UserNotifications.framework (iOS 10 及以上需添加,使用 Optional 方式接入)
AdSupport.framework (如果使用无IDFA版本SDK,则需删除该 AdSupport 库)
添加 GtExtensionSdk 依赖库
libz.tbd
libsqlite3.tbd
GTExtensionSDK.framework
UserNotifications.framework
NotificationService.m
#import <GTExtensionSDK/GeTuiExtSdk.h>
- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler {
self.contentHandler = contentHandler;
self.bestAttemptContent = [request.content mutableCopy];
[GeTuiExtSdk handelNotificationServiceRequest:request
withAttachmentsComplete:^(NSArray *attachments, NSArray *errors) {
//TODO:用户可以在这里处理通知样式的修改,eg:修改标题
//self.bestAttemptContent.title = [NSString stringWithFormat:@"%@ [Success]", self.bestAttemptContent.title];
self.bestAttemptContent.attachments = attachments; //设置通知中的多媒体附件
NSLog(@"处理个推APNs展示遇到错误:%@", errors); //如果APNs处理有错误,可以在这里查看相关错误详情
self.contentHandler(self.bestAttemptContent); //展示推送的回调处理需要放到个推回执完成的回调中
}];
}
- (void)serviceExtensionTimeWillExpire {
// Called just before the extension will be terminated by the system.
// Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used.
//销毁SDK,释放资源
[GeTuiExtSdk destory];
self.contentHandler(self.bestAttemptContent);
}
AppDelegate
- 为
AppDelegate
增加回调接口类。在iOS 10
以前的设备,回调事件通过GeTuiSdkDelegate来
进行,在iOS 10
以后,可以使用UserNotifications
框架来实现。示例代码如下:
#import <UIKit/UIKit.h>
#import <GTSDK/GeTuiSdk.h> // GetuiSdk头文件应用
// iOS10 及以上需导入 UserNotifications.framework
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
#import <UserNotifications/UserNotifications.h>
#endif
/// 使用个推回调时,需要添加"GeTuiSdkDelegate"
/// iOS 10 及以上环境,需要添加 UNUserNotificationCenterDelegate 协议,才能使用 UserNotifications.framework 的回调
@interface AppDelegate : UIResponder <UIApplicationDelegate, GeTuiSdkDelegate, UNUserNotificationCenterDelegate>
// 用来判断是否是通过点击通知栏开启(唤醒)APP
@property (nonatomic, assign) BOOL isLaunchedByNotification;
- 初始化SDK并注册APNs
/// 个推开发者网站中申请App时,注册的AppId、AppKey、AppSecret
#define kGtAppId @"iMahVVxurw6BNr7XSn9EF2"
#define kGtAppKey @"yIPfqwq6OMAPp6dkqgLpG5"
#define kGtAppSecret @"G0aBqAD6t79JfzTB6Z5lo5"
// 通过个推平台分配的appId、 appKey 、appSecret 启动SDK,注:该方法需要在主线程中调用
dispatch_async(dispatch_get_main_queue(), ^{
[GeTuiSdk startSdkWithAppId:kGtAppId appKey:kGtAppKey appSecret:kGtAppSecret delegate:self];
});
// 注册 APNs
[self registerRemoteNotification];
- 注册
APNs
获取DeviceToken
的流程,根据项目设置的不同以及手机系统版本的不同,注册代码会有所区别,可以参考如下方式进行适配:
// 注册 APNs
- (void)registerRemoteNotification {
/*
警告:Xcode8 需要手动开启"TARGETS -> Capabilities -> Push Notifications"
*/
/*
警告:该方法需要开发者自定义,以下代码根据 APP 支持的 iOS 系统不同,代码可以对应修改。
以下为演示代码,注意根据实际需要修改,注意测试支持的 iOS 系统都能获取到 DeviceToken
*/
if ([[UIDevice currentDevice].systemVersion floatValue] >= 10.0) {
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 // Xcode 8编译会调用
if (@available(iOS 10.0, *)) {
UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
center.delegate = self;
[center requestAuthorizationWithOptions:(UNAuthorizationOptionBadge | UNAuthorizationOptionSound | UNAuthorizationOptionAlert | UNAuthorizationOptionCarPlay) completionHandler:^(BOOL granted, NSError *_Nullable error) {
if (!error) {
DLog(@"request authorization succeeded!");
}
}];
} else {
// Fallback on earlier versions
}
[[UIApplication sharedApplication] registerForRemoteNotifications];
#else // Xcode 7编译会调用
UIUserNotificationType types = (UIUserNotificationTypeAlert | UIUserNotificationTypeSound | UIUserNotificationTypeBadge);
UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:types categories:nil];
[[UIApplication sharedApplication] registerForRemoteNotifications];
[[UIApplication sharedApplication] registerUserNotificationSettings:settings];
#endif
} else if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 8.0) {
UIUserNotificationType types = (UIUserNotificationTypeAlert | UIUserNotificationTypeSound | UIUserNotificationTypeBadge);
UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:types categories:nil];
[[UIApplication sharedApplication] registerForRemoteNotifications];
[[UIApplication sharedApplication] registerUserNotificationSettings:settings];
} else {
#pragma clang diagnostic push
#pragma clang diagnostic ignored"-Wdeprecated-declarations"
//写在这个中间的代码,都不会被编译器提示-Wdeprecated-declarations类型的警告
UIRemoteNotificationType apn_type = (UIRemoteNotificationType)(UIRemoteNotificationTypeAlert |UIRemoteNotificationTypeSound |UIRemoteNotificationTypeBadge);
[[UIApplication sharedApplication] registerForRemoteNotificationTypes:apn_type];
#pragma clang diagnostic pop
}
}
- 向个推服务器注册
DeviceToken
// 远程通知注册成功委托
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
NSString *token = [[deviceToken description] stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"<>"]];
token = [token stringByReplacingOccurrencesOfString:@" " withString:@""];
DLog(@"\n>>>[DeviceToken Success]:%@\n\n", token);
// 向个推服务器注册deviceToken
[GeTuiSdk registerDeviceToken:token];
}
- 统计APNs通知的点击数
在iOS 10
以前,为处理 APNs
通知点击事件,统计有效用户点击数,需在AppDelegate.m
里的didReceiveRemoteNotification
回调方法中调用个推SDK统计接口:
/** APP已经接收到“远程”通知(推送) - 透传推送消息 {app 在后台没杀死或者杀死 点击通知 首先进入的方法} */
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler {
// 处理APNs代码,通过userInfo可以取到推送的信息(包括内容,角标,自定义参数等)。如果需要弹窗等其他操作,则需要自行编码。
DLog(@"\n>>>[Receive RemoteNotification - Background Fetch]:%@\n\n",userInfo);
self.isLaunchedByNotification = YES;
//静默推送收到消息后也需要将APNs信息传给个推统计
[GeTuiSdk handleRemoteNotification:userInfo];
completionHandler(UIBackgroundFetchResultNewData);
}
- 对于
iOS 10
及以后版本,为处理APNs
通知点击,统计有效用户点击数,需先添加UNUserNotificationCenterDelegate
,然后在AppDelegate.m
的didReceiveNotificationResponse
回调方法中调用个推SDK统计接口:
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
// iOS 10: App在前台获取到通知
- (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler API_AVAILABLE(ios(10.0)){
DLog(@"willPresentNotification:%@", notification.request.content.userInfo);
// 根据APP需要,判断是否要提示用户Badge、Sound、Alert
completionHandler(UNNotificationPresentationOptionBadge | UNNotificationPresentationOptionSound | UNNotificationPresentationOptionAlert);
}
// iOS 10: 点击通知进入App时触发,在该方法内统计有效用户点击数
- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)(void))completionHandler API_AVAILABLE(ios(10.0)){
DLog(@"didReceiveNotification:%@", response.notification.request.content.userInfo);
self.isLaunchedByNotification = YES;
// [ GTSdk ]:将收到的APNs信息传给个推统计
[GeTuiSdk handleRemoteNotification:response.notification.request.content.userInfo];
completionHandler();
}
#endif
- 获取CID信息
/** SDK启动成功返回cid */
- (void)GeTuiSdkDidRegisterClient:(NSString *)clientId {
//个推SDK已注册,返回clientId
DLog(@"-=-=-=----\n>>>[GeTuiSdk RegisterClient]:%@\n\n", clientId);
// 通过接口上传的APP服务器
}
- 接收个推通道下发的透传消息
当 SDK
在线时(即 App
在前台运行时)进行消息推送,该消息将直接通过个推通道发送给 App
,通常这种方式比通过APNs
发送来得更及时更稳定;当 SDK
离线时(即停止 SDK
或 App
后台运行 或 App
停止状态时)进行消息推送,个推平台会给苹果 APNs
推送消息,同时保存个推通道的离线消息,当 SDK
重新上线后,个推平台会重新推送所有离线的消息
APP
可以通过[GeTuiSdkDelegate GeTuiSdkDidReceivePayloadData]
回调方法获取透传消息,其中payloadData
参数为透传消息数据,offLine
参数则表明该条消息是否为离线消息。示例代码如下:
/** SDK收到透传消息回调 {app在前台 只走这一个方法}{app在后台,进来前台第二个走的方法} */
- (void)GeTuiSdkDidReceivePayloadData:(NSData *)payloadData andTaskId:(NSString *)taskId andMsgId:(NSString *)msgId andOffLine:(BOOL)offLine fromGtAppId:(NSString *)appId {
[UIApplication sharedApplication].applicationIconBadgeNumber = 0;
//收到个推消息
NSString *payloadMsg = nil;
if (payloadData) {
payloadMsg = [[NSString alloc] initWithBytes:payloadData.bytes length:payloadData.length encoding:NSUTF8StringEncoding];
}
NSDictionary *payLoadMegDic = [NSJSONSerialization JSONObjectWithData:payloadData options:NSJSONReadingMutableLeaves error:nil];
DLog(@"%@",payLoadMegDic);
if (offLine) {
if (payLoadMegDic) {
if (self.isLaunchedByNotification) { // app是通过点击通知栏进入前台
UITabBarController *_tab = (UITabBarController *)(self.window.rootViewController);
UINavigationController *nva = (UINavigationController *)_tab.viewControllers[_tab.selectedIndex];
// 根据payLoadMegDic判断跳转类型
self.isLaunchedByNotification = NO;
}else{
// app是通过点击icon进入前台,在这里不做操作
}
}
NSString *msg = [NSString stringWithFormat:@"taskId=%@,messageId:%@,payloadMsg:%@%@",taskId,msgId, payloadMsg,offLine ? @"<离线消息>" : @""];
DLog(@"\n>>>[GexinSdk ReceivePayload]:%@\n\n", msg);
}else if(!self.isLaunchedByNotification){
// app已经处于前台,提示框提示
// 在这里拿不到apns 里的title body! 如果想要处理的话可以让服务端把aps 的内容传到transmissionContent 透传内容里 ,应用在线的时候可以拿到这个透传
self.isLaunchedByNotification = NO;
// 转换成一个本地通知,显示到通知栏,你也可以直接显示出一个alertView,只是那样稍显aggressive
}
}