MobPushde 原理介绍

230 阅读8分钟

###一 了解苹果推送 苹果推送分为本地推送、在线推送、离线推送三种模式, 本地推送是使用一套本地推送的机制和网络服务器无关,可用于闹钟之类的需求,在线推送是应用处于前台模式,使用APP自己的长链接推送 ,和APP设置通知的开关没有关系 ,离线推送就是APNs推送了,app服务器发送推送请求到APNs服务器,APNs服务器发送推送到指定的设备,离线推送就和APP设置的通知开关有关系了。以微信为例,当微信处于前台时是进入微信自己的长链接推送通道,和手机APP设置推送是否开启无关, 处于后台模式、锁屏或者kill了则使用APNs通道,通知关闭则收不到推送了 ####1 远程推送 远程推送又可以可以分为静默推送和正常推送 正常推送用户会收到推送内容、声音,应用处于后台或者kill点击推送内容进入APP后才会会进入

-(void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler

静默推送是一种特殊的远程推送,没有推送内容声音,不用点击推送内容也不用进入APP就会执行,用户毫无感觉

-(void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler

静默推送是iOS7.0之后推出的,又被称为:Background Remote Notification(后台远程推送)可以不用打开APP就可以运行代码,(大多是用户毫无察觉的处理 服务器传到APP的数据,更新APP内容) ####2本地推送 本地推送和远程推送一样都是通知APP做事情,远程推送是需要联网的,只有联网才能和苹果服务器APNs建立长链接、接受APNs的消息,本地推送是不需要联网的,APP内部实现推送功能,本地推送的目标是安装了APP的设备,受APP在该设备的通知是否开启影响。最常用的就是闹钟APP 注意:iOS8 - iOS10的本地推送:当应用处于前台是不会有横幅或者弹框,用户无感知,可以在didReceiveLocalNotification 回调中处理弹框使用户感知,让应用处于后台才会有横幅和弹框铃声等, 推送的注册和回调,64条是苹果官方设置的上限。

-(void)registerAPNs:(UIApplication *)application
{
//    iOS8~iOS10 与 iOS10之后的系统本地推送是不同的
    if (@available(iOS 10.0, *)) {
        UNUserNotificationCenter *unCenter = [UNUserNotificationCenter currentNotificationCenter];
        unCenter.delegate = self;
        [unCenter requestAuthorizationWithOptions:(UNAuthorizationOptionAlert | UNAuthorizationOptionBadge | UNAuthorizationOptionSound) completionHandler:^(BOOL granted, NSError * _Nullable error) {
            if (!error) {
                NSLog(@"注册成功");
                
                dispatch_async(dispatch_get_main_queue(), ^{
                    [[UIApplication sharedApplication] registerForRemoteNotifications];
                });
            }
        }];
        [unCenter getNotificationSettingsWithCompletionHandler:^(UNNotificationSettings * _Nonnull settings) {
            NSLog(@"regist success settting is  =====+%@",settings);
        }];
    } else {
        if ([application respondsToSelector:@selector(registerUserNotificationSettings:)]) {
            UIUserNotificationSettings *setttings = [UIUserNotificationSettings settingsForTypes:(UIUserNotificationTypeSound | UIUserNotificationTypeAlert | UIUserNotificationTypeBadge) categories:nil];
            [application registerUserNotificationSettings:setttings];
            [application registerForRemoteNotifications];
        }
    }
    
}

#pragma mark -- ios10 推送代理
//不实现通知不会有提示
- (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler
API_AVAILABLE(ios(10.0)) API_AVAILABLE(ios(10.0)) API_AVAILABLE(ios(10.0)){
    
    completionHandler(UNNotificationPresentationOptionBadge | UNNotificationPresentationOptionSound | UNNotificationPresentationOptionAlert);
}

//对通知响应
-(void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)(void))completionHandler
API_AVAILABLE(ios(10.0)) API_AVAILABLE(ios(10.0)){
    if ([response.notification.request.content.categoryIdentifier isEqualToString:@"request1"]) {
        [self handleResponse:response];
    }
    completionHandler();
}
//点击通知 进入APP的回调对通知响应
-(void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)(void))completionHandler
API_AVAILABLE(ios(10.0)) API_AVAILABLE(ios(10.0)){
    NSString *categoryIdentifier = response.notification.request.content.categoryIdentifier;
    if ([categoryIdentifier isEqualToString:@"categoryIdentifier"]) {
        [self handleResponse:response];
        
        
    }
    completionHandler();
}

#pragma mark ---------------处理点击通知进入APP后的事件
-(void)handleResponse:(UNNotificationResponse *)response
API_AVAILABLE(ios(10.0)){
    [UIApplication sharedApplication].applicationIconBadgeNumber = 0;
    if ([response.actionIdentifier isEqualToString:@"commitActionTitle"])
    {
        NSLog(@"commit Action =====");
    }else if ([response.actionIdentifier isEqualToString:@"textAction1"]){
        UNTextInputNotificationResponse *textResponse = (UNTextInputNotificationResponse *)response;
        NSString *userText = textResponse.userText;
        NSLog(@"input text is ======%@",userText);
    }else if ([response.actionIdentifier isEqualToString:@"cancelActionTitle"]){
        NSLog(@"cancel Action -------");
    }
        
    NSLog(@"%@",@"处理通知");
}

设置推送内容 fireDate、timeZone、repeatInterval和repeatCalendar的含义 1 fireDate是UILocalNotification的激发的确切时间。 2 timeZone是UILocalNotification激发时间所根据的时区,如果设置为nil的话,那么UILocalNotification将在一段时候后被激发,而不是某一个确切时间被激发。 3 repeatInterval是UILocalNotification被重复激发之间的时间差,不过时间差是完全根据日历单位(NSCalendarUnit)的,例如每周激发的单位,NSWeekCalendarUnit,如果不设置的话,将不会重复激发。 4 repeatCalendar是UILocalNotification重复激发所使用的日历单位需要参考的日历,如果不设置的话,系统默认的日历将被作为参考日历。 5添加category按钮,和用户点击推送内容进行交互 UNTextInputNotificationAction:用户输入text,可以在didReceiveNotificationResponse回调中获取text内容 UNNotificationAction:根据需求自定义推送点击按钮

-(void)setLocalNotification
{
//   只创建一个通知,重复一次创建一次太恐怖了
    NSArray *notificatinArr = [[UIApplication sharedApplication] scheduledLocalNotifications];
    if (notificatinArr.count) {
        return;
    }
    NSString *title = @"通知-title";
    NSString *subTitle = @"通知-subTitle";
    NSString *body = @"通知-body";
    NSInteger badge = 1;
    NSInteger timeIntevel = 60;
    NSDictionary *userInfo = @{@"id":@"LOCAL_NOTIFY_SCHEDULE_ID"};
    if (@available(iOS 10.0, *)) {
        UNUserNotificationCenter *NotifCenter = [UNUserNotificationCenter currentNotificationCenter];
        UNMutableNotificationContent *notificationContent = [[UNMutableNotificationContent alloc] init];
        notificationContent.sound = [UNNotificationSound defaultSound];
        notificationContent.title = title;
        notificationContent.subtitle = subTitle;
        notificationContent.body = body;
        notificationContent.badge = @(badge);
        notificationContent.userInfo = userInfo;
        
        NSError *error = nil;
        NSString *path = [[NSBundle mainBundle] pathForResource:@"jjy2" ofType:@"jpg"];
        
//        设置通知附件内容
        UNNotificationAttachment *att = [UNNotificationAttachment attachmentWithIdentifier:@"att1" URL:[NSURL fileURLWithPath:path] options:nil error:&error];
        notificationContent.attachments = @[att];
        notificationContent.launchImageName = @"jjy2.png";
//        设置声音
        UNNotificationSound *sound = [UNNotificationSound soundNamed:@"sound02.wav"]; //要有后缀
        notificationContent.sound = sound;
        
//        标识符   推送用户的交互 左拉,点击管理按钮 会出现category 按钮
        notificationContent.categoryIdentifier = @"categoryIdentifier";
        
        UNTextInputNotificationAction *textAction = [UNTextInputNotificationAction actionWithIdentifier:@"textAction1" title:@"textActionTitle1" options:UNNotificationActionOptionForeground textInputButtonTitle:@"textInpuButtonTitle1" textInputPlaceholder:@"textInputPlaceholder1"];
        UNNotificationAction *commitAction = [UNNotificationAction actionWithIdentifier:@"commitAction" title:@"commitActionTitle" options:UNNotificationActionOptionForeground];
        UNNotificationAction *cancelAction = [UNNotificationAction actionWithIdentifier:@"cancelAction" title:@"cancelActionTitle" options:UNNotificationActionOptionForeground];
        UNNotificationCategory *notifCategory = [UNNotificationCategory categoryWithIdentifier:@"categoryIdentifier" actions:@[textAction,commitAction,cancelAction] intentIdentifiers:@[] options:UNNotificationCategoryOptionNone];
        NSSet *categorySet = [[NSSet alloc] initWithObjects:notifCategory, nil];
        [NotifCenter setNotificationCategories:categorySet];


//        设置触发模式
        UNTimeIntervalNotificationTrigger *timeIntervalTrigger = [UNTimeIntervalNotificationTrigger triggerWithTimeInterval:timeIntevel repeats:YES];
//        设置UNNotificationRequest
        UNNotificationRequest *request = [UNNotificationRequest requestWithIdentifier:@"request1" content:notificationContent trigger:timeIntervalTrigger];
//        把通知加到UNUserNotificationCenter 到指定触发点会被触发
        [NotifCenter addNotificationRequest:request withCompletionHandler:^(NSError * _Nullable error) {
            if (!error) {
                NSLog(@"addNotificationRequest success :error is  %@",error);
            }
            else{
                NSLog(@"addNotificationRequest failed");
            }
        }];
        if (error) {
            NSLog(@"attachment error %@",error);
        }
    } else {
//        iOS10之前的系统 APP处于后台才会有提示,但是会收到推送
        UILocalNotification *localNotification = [[UILocalNotification alloc] init];
        localNotification.alertTitle = [self setLowVersionLocalNotification:title];
        localNotification.alertBody = [self setLowVersionLocalNotification:body];
        localNotification.alertLaunchImage = [[NSBundle mainBundle] pathForResource:@"jjy2" ofType:@"jpg"];
//        锁屏状态下显示的文字
         localNotification.alertAction = @"锁屏状态下";
        localNotification.timeZone = [NSTimeZone defaultTimeZone];
        localNotification.fireDate = [NSDate dateWithTimeIntervalSinceNow:1];
        localNotification.repeatInterval = NSCalendarUnitMinute;
        
        localNotification.soundName = @"sound01.wav";//UILocalNotificationDefaultSoundName;
        localNotification.userInfo = @{@"keyInfo":@"valueInfo",
                                       @"id"     :@"LOCAL_NOTIFY_SCHEDULE_ID"};
        localNotification.applicationIconBadgeNumber = 1;

        [[UIApplication sharedApplication] scheduleLocalNotification:localNotification];
    }
}

取消推送

//不重复推送:推送一次后就会自动取消推送,重复推送的话需要手动取消,不然即使卸载应用也会残留,下次重装也会继续推送
-(void)cancelLocalNotifications
{
    
    NSArray *notificationArr = [[UIApplication sharedApplication] scheduledLocalNotifications];
    if (!notificationArr || notificationArr.count <= 0) {
        return;
    }
    for (UILocalNotification *localNotify in notificationArr) {
        if ([[localNotify.userInfo valueForKey:@"id"] isEqualToString:@"LOCAL_NOTIFY_SCHEDULE_ID"]) {
            if (@available(iOS 10.0, *)) {
                [[UNUserNotificationCenter currentNotificationCenter] removePendingNotificationRequestsWithIdentifiers:@[@"request1"]];
            } else {
                [[UIApplication sharedApplication] cancelLocalNotification:localNotify];
            }
        }
    }
}

###3远程推送 ######1 远程推送原理: 客户端注册远程推送发送Token key ,APNs服务器根据Token key 下发给客户端deviceToken; 客户端把DeviceToken发送给自己的服务器,自己的服务器发送推送消息给APNs服务器,APNs服务器将消息发给DeviceToken对应设备上的客户端,如下图所示

image.png
######2 deviceToken介绍 deviceToken其实就是根据注册远程通知的时候向APNs服务器发送的token key(Token key 包括设备的UDID和APP的bundle id),APNs服务器 根据token key 生产一个deviceToken,deviceToken包含了用户的设备信息和App信息,根据deviceToken可以找到对应设备的对应App,从而把消息推送给该应用 deviceToken唯一性:同一个设备和bundle id ,deviceToken是一样的,deviceToken 对应为一个设备的应用,但是当用户升级系统是,deviceToken 会有变化 ######3 指定用户推送 根据userToken 可以推送指定的用户,同一个APP的同一个推送,有些用户可以收到有些用户不能收到,userToken一般都是根据自己公司自定义的规则去生成的,例如:用户的APP账号和密码,结合上面的deviceToken,deviceToken找到对用设备的对应APP,userToken找到APP的对应用户。上传deviceToken要上传userToken给APNs服务器 ######4 客户端和服务器的交互 每一条通知的消息都会组成一个json字典对象,其格式如下,示例中的key是官方的key,自定义的key 不要与之重复

{
     "aps" : {  
        "alert"              :              {   // string or dictionary
          "title"          :   "string"
            "body"           :   "string",
            "title-loc-key"  :   "string or null"
            "title-loc-args" :   "array of strings or null"
            "action-loc-key" :   "string or null"
            "loc-key"        :   "string"
            "loc-args"       :   "array of strings"
            "launch-image"   :   "string"
        },
        "badge"             :    number,
        "sound"             :    "string"
        "content-available" :    number;
        "category"          :    "string"
     },
}

aps:推送消息必须有的key alert:推送消息包含此key值,系统根据设备显示弹框 badge:在APP图标右上角显示的消息数目,缺少此key,消息数目不会改变,消除标记时把此key对应的value设置为0 sound:设置推送声音的key值,value 为default时会用系统默认的提示音 content-available:此key设置为1时,在收到消息之前会进入extent server 的回调方法,修改显示的值 category:UNNotificationCategory的identifier, 用户可操作的类型的key值 title:推送消息的标题:iOS8.2之后 body:推送内容 title-loc-key:功能类似title,附加功能是国际化,iOS8.2之后 title-loc-args:配合title-loc-key字段使用,iOS8.2之后 action-loc-key:可操作通知类型的key loc-key:参考title-loc-key loc-args:参考title-loc-args launch-image:点击推送消息或者移动时间滑块时显示的图片,如果缺少此key值,会加载App默认的启动图片 自定义ke值

{
    "aps" : {
        "alert" : "Provider push messag.",
        "badge" : 9,
        "sound" : "toAlice.aiff"
    },
    "Id"   : 1314,               //  自定义key值
    "type" : "customType"        //  自定义key值
}

1模拟器不能接受推送,没有deviceToken,必须真机

MobPush文档链接

参考链接:www.jianshu.com/p/48c3d6eca… cloud.tencent.com/developer/a… www.cocoachina.com/ios/2016101… www.jianshu.com/p/78ef7bc04… www.jianshu.com/p/174b55d5e… 远程推送:blog.csdn.net/leiyutinghu…