当系统通知栏接收到JPush远程推送消息,并实现点击消息跳转指定页面,处理推送消息的回调函数如下:
- (void)jpushNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)())completionHandler {
NSDictionary * userInfo = response.notification.request.content.userInfo;
UNNotificationRequest *request = response.notification.request; // 收到推送的请求
UNNotificationContent *content = request.content; // 收到推送的消息内容
NSNumber *badge = content.badge; // 推送消息的角标
NSString *body = content.body; // 推送消息体
UNNotificationSound *sound = content.sound; // 推送消息的声音
NSString *subtitle = content.subtitle; // 推送消息的副标题
NSString *title = content.title; // 推送消息的标题
if([response.notification.request.trigger isKindOfClass:[UNPushNotificationTrigger class]]) {
if (userInfo) {
[self push:userInfo];
}else{
return;
}
[JPUSHService handleRemoteNotification:userInfo];
}
else {
}
completionHandler(); // 系统要求执行这个方法
}
一、我们需要分析有哪些跳转的情景:
1、程序在前台运行时接收到消息
2、程序在后台运行时接收到消息
3、程序处于终止(杀死)状态下接收到消息
那么本文重点来探讨下当程序处于终止状态下时接收到消息如何处理?
不设悬念直入主题吧,仿照QQ、微信等其他APP的推送机制可以了解到,当程序被终止状态下,点击通知栏的消息只打开app并不能跳转特定页面,原因是appDelegate的main函数不执行,那么Push的载体导航器也不存在。系统只能根据通知栏所点击消息对应远程推送注册码来选择启动哪一个APP.
所以消息处理的逻辑即:
1、程序未被终止状态下,编写正常跳转特定页面的代码
2、程序被终止状态下,屏蔽Push跳转代码,只启动app即可
二、问题来了,进程被终止回调机制是什么呢?
1、程序进程被终止会调用此函数
// 程序进程被终止时调用
- (void)applicationWillTerminate:(UIApplication *)application{
[[NSUserDefaults standardUserDefaults] setObject:applicationWillTerminate forKey:applicationWillTerminate];
[[NSUserDefaults standardUserDefaults] synchronize];
}
需要注意:此回调函数不能主动被调起,还需要在程序进入后台的回调函数中开启UIBackgroundTaskIdentifier
任务,即如下操作
2、声明全局变量
UIBackgroundTaskIdentifier _bgTask;
3、开启后台任务
- (void)applicationDidEnterBackground:(UIApplication *)application {
_bgTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
// Synchronize the cleanup call on the main thread in case
// the task actually finishes at around the same time.
dispatch_async(dispatch_get_main_queue(), ^{
if (_bgTask != UIBackgroundTaskInvalid)
{
[[UIApplication sharedApplication] endBackgroundTask:_bgTask];
_bgTask = UIBackgroundTaskInvalid;
}
});
}];
}
亦可在程序进入后台执行一些保存、清理操作
[[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^(){
//程序在10分钟内未被系统关闭或者强制关闭,则程序会调用此代码块,可以在这里做一些保存或者清理工作
NSLog(@"程序关闭");
}];
三、如何判断当前的程序是否已经被终止呢?
显然普通的BOOL或者属性记录是行不通的,appDelegate的代码就不执行,那么找到的解决方案是:轻量级存储。
1、定义常量
// 定义当前程序被终止常量
static NSString *const applicationWillTerminate = @"applicationWillTerminate";
2、记录进程被强制终止
// 程序进程被终止时调用
- (void)applicationWillTerminate:(UIApplication *)application{
[[NSUserDefaults standardUserDefaults] setObject:applicationWillTerminate forKey:applicationWillTerminate];
[[NSUserDefaults standardUserDefaults] synchronize];
}
3、当已启动进程时remove掉存储对象。
[[NSUserDefaults standardUserDefaults] removeObjectForKey:applicationWillTerminate];
[[NSUserDefaults standardUserDefaults] synchronize];
四、runtime处理推送消息跳转特定页面源码
- (void)push:(NSDictionary *)userInfo
{
NSDictionary *params;
if ([[NSString stringWithFormat:@"%@",userInfo[@"aps"][@"badge"]] isEqualToString:@"1"]) {
//邀请面试
params = @{
@"class": @"InterviewTimeViewController",
@"property": @{
@"ID": @"123",
@"channelType": @"12"
}
};
}else if ([[NSString stringWithFormat:@"%@",userInfo[@"extrasKey"]] isEqualToString:@"0"]){
//查看简历状态
params = @{
@"class": @"ResumeStateViewController",
@"property": @{
@"ID": @"234",
@"channelType": @"13"
}
};
}
// 类名
NSString *classStr =[NSString stringWithFormat:@"%@", params[@"class"]];
const char *className = [classStr cStringUsingEncoding:NSASCIIStringEncoding];
// 从一个字串返回一个类
Class newClass = objc_getClass(className);
if (!newClass)
{
// 创建一个类
Class superClass = [NSObject class];
newClass = objc_allocateClassPair(superClass, className, 0);
// 注册你创建的这个类
objc_registerClassPair(newClass);
}
// 创建对象
id instance = [[newClass alloc] init];
// 对该对象赋值属性
NSDictionary * propertys = params[@"property"];
[propertys enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
// 检测这个对象是否存在该属性
if ([self checkIsExistPropertyWithInstance:instance verifyPropertyName:key]) {
// 利用kvc赋值
[instance setValue:obj forKey:key];
}
}];
NSString *terminate = [[NSUserDefaults standardUserDefaults] objectForKey:applicationWillTerminate];
if (![terminate isEqualToString:applicationWillTerminate]) {
// 获取导航控制器
UITabBarController *tabVC = (UITabBarController *)self.window.rootViewController;
UINavigationController *pushClassStance = (UINavigationController *)tabVC.viewControllers[tabVC.selectedIndex];
// 跳转到对应的控制器
[pushClassStance pushViewController:instance animated:YES];
}
}
- (BOOL)checkIsExistPropertyWithInstance:(id)instance verifyPropertyName:(NSString *)verifyPropertyName
{
unsigned int outCount, i;
// 获取对象里的属性列表
objc_property_t * properties = class_copyPropertyList([instance
class], &outCount);
for (i = 0; i < outCount; i++) {
objc_property_t property =properties[i];
// 属性名转成字符串
NSString *propertyName = [[NSString alloc] initWithCString:property_getName(property) encoding:NSUTF8StringEncoding];
// 判断该属性是否存在
if ([propertyName isEqualToString:verifyPropertyName]) {
free(properties);
return YES;
}
}
free(properties);
return NO;
}