iOS本地通知和远程通知的使用

iOS的通知即在方式上分为两种,一种是在开发应用中就固定时间或者固定条件下发送的通知即本地通知;另一种就是远程通知,由开发者向客户端提供的远程通知发送的方式。

本地通知 (Local Notification)

iOS本地通知是在程序中指定某个时间,或者在多少时间倒计时,或者在特定条件之后,出现在设备的状态栏消息中的功能。

iOS为我们提供了几种实现方法,其中最简单的是使用 UILocalNoticification,在iOS 10.0 之后苹果推出了更加强大的 UNUserNotification; 当然了还有一种方法是使用 Noticfication Extension 通知扩展实现自定义通知。

>> 下面是具体本地通知体现在代码中的实现:

> 1. 在AppDelegate.m中application方法中对通知方法执行注册

 

>> 1. 在Appdelegate.h中 -- 实现必要的通知注册
>> 
-(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

    /* UILocalNotification -- 本地通知的使用 */  
    if ([[UIDevice currentDevice].systemVersion doubleValue] >= 8.0) {
       UIUserNotificationSettings *settings = [UIUserNotificationSettingssettingsForTypes: UIUserNotificationTypeAlert   | UIUserNotificationTypeBadge |   UIUserNotificationTypeSound categories:nil];
        
        [application registerUserNotificationSettings:settings];
    }
    
    /* UNUserNotification -- 本地通知注册使用 */
    [self initNoticWithDic:launchOptions];
    
    
    return YES;
}

#pragma mark - 通知的初始化和 `点击通知` 的逻辑调用 --- 在程序被杀死的情况下调用否则未被杀死调用下面的方法
- (void)initNoticWithDic:(NSDictionary *)launchOptions{
// Method--1
    if ([[launchOptions allKeys]containsObject:UIApplicationLaunchOptionsRemoteNotificationKey]) {
        // 判断发送远程通知点击进入
    }else if ([[launchOptions allKeys]containsObject:UIApplicationLaunchOptionsLocalNotificationKey]) {
        // 通过本地通知点击进入
    }else{
        // 正常进入程序
    }
    
// Method--2--UNUserNotificationCenter另一种注册通知方法的使用
    if(@available(iOS 10.0,*)){
        UNUserNotificationCenter *noticCenter = [UNUserNotificationCenter currentNotificationCenter];
        [noticCenter requestAuthorizationWithOptions:(UNAuthorizationOptionBadge | UNAuthorizationOptionSound | UNAuthorizationOptionAlert) completionHandler:^(BOOL granted, NSError * _Nullable error) {
            if (granted) {
                NSLog(@"notic-通知开启");
            } else {
                NSLog(@"notic-关闭通知");
            }
        }];
        [noticCenter getNotificationSettingsWithCompletionHandler:^(UNNotificationSettings * _Nonnull settings) {
            NSLog(@"notic-settings-%@", settings);
        }];
    }
    
}

获取用户设置 -- 判断是否允许发送通知的回调,以便可进行相应的处理。

如果点击 允许 发送通知则 notificationSettings.type有值,否则点击 不允许 其值为0

 

/**
 *  1. 查看注册成功的通知类型
 *  2. 拿到注册结果进行自定义操作(获取发送通知权限失败予以提示)
 */
 在 AppDelegate 中直接实现即可实现自动回调
- (void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings{
    // 如果点击允许发送通知则 `notificationSettings.type`有值,否则其值为0
    if (notificationSettings.types && UIUserNotificationTypeBadge) {
        NSLog(@"Badge Nofitication type is allowed");
    }
    if (notificationSettings.types && UIUserNotificationTypeAlert) {
        NSLog(@"Alert Notfication type is allowed");
    }
    if (notificationSettings.types && UIUserNotificationTypeSound) {
        NSLog(@"Sound Notfication type is allowed");
    }   
}

> 2. 在AppDelegate.m中 响应通知的点击进入回调操作

程序没有被杀死(处于前台或后台),点击通知后会调用此程序方法

 

- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification
{
    // 必须要监听--应用程序在后台的时候进行的跳转
    if (application.applicationState == UIApplicationStateInactive) {
        NSLog(@"进行界面的跳转");
        // 如果在上面的通知方法中设置了一些,可以在这里打印额外信息的内容,就做到监听,也就可以根据额外信息,做出相应的判断
        NSLog(@"%@", notification.userInfo);
        //点击弹窗之后会出现的视图
//        UIView *redView = [[UIView alloc] init];
//        redView.frame = CGRectMake(0, 0, 00, 100);
//        redView.backgroundColor = [UIColor redColor];
//        [self.window.rootViewController.view addSubview:redView];
    }
}

> 3.1 封装UILocalNotification通知方法的创建

 

>> 2. 
+ (void)sendLocalNotificaiton
{
// 1.创建本地通知
    UILocalNotification *localNotic = [[UILocalNotification alloc] init];
// 2.设置通知触发的时间 -- 
    所有时间触发时间是从开始创建这个通知开始计算,并且只有此应用不在活跃状态时才会发出通知
    // 2.1 倒计时设置为通知时间
    localNotic.fireDate = [NSDate dateWithTimeIntervalSinceNow:3.0];
    // 2.2 设置固定时间
        NSDateComponents *dateComponents = [calendar components:NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay fromDate:[NSDate date]];
        dateComponents.hour = 20; //20h
        dateComponents.minute = 0; // 00min
        NSDate *fireDate = [calendar dateFromComponents:dateComponents];
    notification.fireDate = fireDate;

// 3.设置通知的内容 -- 通知内容是在本次创建通知时不可变更的,在下次再创建通知后,会在执行创建一次通知。
    localNotic.alertBody = NSLocalizedString(@"localNotificationContent_title", nil);
// 4.设置滑块的文字(锁屏状态下:滑动来“解锁”)
    localNotic.alertAction = @"解锁";
// 5.决定alertAction是否生效
    localNotic.hasAction = YES;
// 6.设置点击通知的启动图片
    localNotic.alertLaunchImage = @"123Abc";
// 7.设置alertTitle
    localNotic.alertTitle = NSLocalizedString(@"localNotificationTitle_title", nil);
// 8.设置有通知时的音效 -- 音效文件可提前加入应用bundle中
     localNotic.soundName = @"buyao.wav";
// 9.设置应用程序图标右上角的表示通知数目的数字
    localNotic.applicationIconBadgeNumber = 1;
// 10.设置通知的useInfo -- 这里设置的userInfo信息主要可以在删除此通知通过获取key和value对应此通知
    localNotic.userInfo = @{@"localNoticKey" : @"localNoticValue"};   
// 11.将通知加入系统通知队列中
    [[UIApplication sharedApplication] scheduleLocalNotification: localNotic];    
}

> 3.2 UNUserNotification通知方法的创建 -- 还可创建图片通知

 

+ (void)packageUNUSerNotificationCreate:(NSString *)imgPathStr withBodyStr:(NSString *)bodyStr {
     NSURL *url = [NSURL fileURLWithPath:imgPathStr];
     UNNotificationAttachment *imageAttachment = [UNNotificationAttachment attachmentWithIdentifier:@"imageAttachment" URL:url options:nil error:nil];
    if(imageAttachment == nil){
        // 如果是空就发送一次本地不带图片通知
        return;
    }
    UNMutableNotificationContent *noticeContent = [[UNMutableNotificationContent alloc] init];
    noticeContent.attachments = @[imageAttachment];
    noticeContent.title = NSLocalizedString(@"AppNavTitle", @"应用标题名");
    //content.subtitle = @""; //副标题不再调用
    noticeContent.body = bodyStr;
    noticeContent.sound = [UNNotificationSound defaultSound];
    // 如果repeats为YES,那么triggerWithTimeInterval的值要大于60s
    NSInteger countDownInt = 3600;  // 1h-3600
    UNTimeIntervalNotificationTrigger *trigger1 = [UNTimeIntervalNotificationTrigger triggerWithTimeInterval:countDownInt repeats:NO]; // 时间设置为不重复的只是执行一次发送通知
    
    NSString *requestIdentifier = @"unfinishID";
    UNNotificationRequest *request = [UNNotificationRequest requestWithIdentifier:requestIdentifier content:noticeContent trigger:trigger1];
    // 发送通知
    [[UNUserNotificationCenter currentNotificationCenter] addNotificationRequest:request withCompletionHandler:^(NSError * _Nullable error) {
        NSLog(@"notic-发送通知出错: %@", error);
    }];
}

> 4. iOS 移除指定通知 和 移除全部通知

在加入系统中,加入指定通知,可以根据通知的userInfo移除指定通知

 

    // 1. 移除全部通知
+ (void)removeAllNoticfication{
    [[UIApplication sharedApplication] removeAllNoticfications];
}
    // 2. 根据创建通知时设置的userInfo移除指定通知
+ (void)cancelLocalNotificationWithKey:(NSString *)key
{
    // 获取所有本地通知数组
    NSArray *localNotifications = [UIApplication sharedApplication].scheduledLocalNotifications;

    if (localNotifications) {     
        for (UILocalNotification *notification in localNotifications) {
            NSDictionary *userInfo = notification.userInfo;
            if (userInfo) {
                // 根据设置通知参数时指定的key来获取通知参数
                NSString *info = userInfo[key];

                // 如果找到需要取消的通知,则取消
                if ([info isEqualToString:key]) {
                    if (notification) {
                        [[UIApplication sharedApplication] cancelLocalNotification:notification];
                    }
                    break;
                }
            }
        }
    } //for code.
}

远程通知 (Remote Notification)

一般情况下如果一个程序退到后台就不能运行代码(Audio、VoIP等等可以在后台运行),或者程序退出后,那么它就和对应应用的后台服务器断开了链接,就收不到服务器发送的信息了,但是每台设备只要联网就会和苹果的 APNs服务器(Apple Push Notification service) 建立一个 长连接(persistent IP connection),这样只要通过苹果的APNs服务器,我们自己的服务器就可以间接的和设备保持连接了,示意图如下:

iOS本地通知和远程通知的使用

iOS-RemoteNotic-APNs.png

远程推送步骤如下:

1. 进行相应的工程配置,如下:

iOS本地通知和远程通知的使用

iOS-RemoteNotic-BgMode.png

iOS本地通知和远程通知的使用

iOS-RemoteNotic-PushNotic.png

2. 注册通知并作出相应的处理 --- 类似等同于上面的本地通知的注册

3. 获取deviceToken

如果用户同意,苹果会根据应用的 bundleID 和 手机UDID 生成 deviceToken,然后调用 -application: didRegisterForRemoteNotificationsWithDeviceToken : 方法返回 devicetoken

程序应该把 devicetoken 发给应用的服务器,服务器有义务将其存储 (如果允许多点登录,可能存多个 devicetoken)。deviceToken也是会变的: ”If the user restores backup data to a new device or computer, or reinstalls the operating system, the device token changes“,因此应每次都发给服务器(provider)

 


// 用户同意后,会调用此程序,获取系统的deviceToken,应把deviceToken传给服务器保存,此函数会在程序每次启动时调用(前提是用户允许通知)
//会自动执行下面的方法
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
    NSLog(@"log--didRegisterForRemoteNotificaionWith:%@",deviceToken);
}

4. 响应远程推送通知

 

/*
 1.如果应用程序在后台,则通知到,点击查看,该方法自动执行
 2.如果应用程序在前台,则通知到,该方法自动执行
 3.如果应用程序被关闭,则通知到,点击查看,先执行didFinish方法,再执行该方法
 4.可以开启后台刷新数据的功能
 step1:点击target-->Capabilities-->Background Modes-->Remote Notification勾上
 step2:在给APNs服务器发送的要推送的信息中,添加一组字符串如:
 {"aps":{"content-available":"999","alert":"bbbbb.","badge":1}}
 其中content-availabel就是为了配合后台刷新而添加的内容,999可以随意定义
 */
 
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
    NSLog(@"log--didReceiveRemoteNotification");
//   xxx
//   xxx
//   xxx   
    completionHandler(UIBackgroundFetchResultNewData);
}

推送证书的下载和使用

服务端推送格式

 

{
    "aps" : {                  // 必须有
        "alert" : "string",
        "body"  : "string",
        "badge" : number,
        "sound" : "string"
    },
    "NotiId"   : 20190114,     // 自定义key值
}

推送的大小限制

远程通知负载的大小根据服务器使用的API不同而不同。当使用HTTP/2 provider API时,负载最大为4kB;当使用legacy binary interface时,负载最大为2kB。当负载大小超过规定的负载大小时,APNs会拒绝发送此通知。

从网上找的以微信发送消息为例的示意图:

iOS本地通知和远程通知的使用

iOS-RemoteNotic-WeChatEx.png

推送实现

在真正实现远程通知推送,一般有很多第三方的SDK可以实现我们需要的推送,如 极光推送、百度推送、友盟推送

这些第三方SDK还在是基本原理上面进行了扩展。对于用不用第三方SDK其实对我们客户端影响不大,推送第三方SDK主要是方便了服务端开发者。主要表现为服务端开发者不需要去开发维护自己的推送服务器与 APNs 对接,不必自己维护更新 deviceToken。当然了,第三方SDK也会提供一些额外的附属功能例如JPush提供了应用内消息推送,这在类似于聊天的场景里很方便的。



作者:罂粟之城
链接:https://www.jianshu.com/p/2992b2e8ed5a

上一篇:PostgreSQL 9.6 并行计算 优化器算法浅析


下一篇:IOS基础之 (二) 面向对象思想