苹果推送(push Notification)的那点儿事

来源:互联网 发布:suse网络不通 编辑:程序博客网 时间:2024/05/17 06:55

苹果给我们的推送有两种,一种是本地推送,一种就是远程推送。本地推送比较简单,在项目中我们基本不会用到,不过还是大致说一下,也好更好的理解远程推送。

本地推送(Local Notification)

思想:

      1.客户端注册本地通知 
      2.客户端接收通知
      3.客户端处理通知中的数据
其实也就是这样,中间没有什么逻辑,就是你注册完之后,等着接收,接着处理通知来的数据就可以了。

Warning:

1.苹果系统版本号判定floatValue问题

因为苹果API更新还是挺快的,所以不同系统上的API或遭废弃,或增新API。所以需要判断系统,而判断系统的时候有一个坑,那就是floatValue的问题。例如:

[[[UIDevicecurrentDevice]systemVersion]floatValue];

乍一看是没有什么问题的,因为我整天都是这么写的啊,可是,floatValue确实有这个问题,单精度,相对而言精确度提高,也会带来一定的麻烦,比如我们手机系统是10.0,float之后可能会变成9.998796,但是如果一个人手机系统是10,而我肯定是想让他走10的API的,可是因为判断出问题,导致只能走iOS9的API,那么通知就注册不成,从而用不了通知了。有些同学说没有遇到我说的这种情况啊,那是你没遇到,floatValue确实有这个问题的,所以我们在判断系统版本的时候是不能用这个来做的,怎么弄呢?

代码:

 if ([applicationrespondsToSelector:@selector(isRegisteredForRemoteNotifications)]) {

        [[UIApplicationsharedApplication]registerForRemoteNotifications];

       if ([UNUserNotificationCentercurrentNotificationCenter]) {

           //iOS 10

            [[UNUserNotificationCentercurrentNotificationCenter]setDelegate:self];

           UNUserNotificationCenter *center = [UNUserNotificationCentercurrentNotificationCenter];

            [centerrequestAuthorizationWithOptions:UNAuthorizationOptionAlert |UNAuthorizationOptionBadge | UNAuthorizationOptionSoundcompletionHandler:^(BOOL granted,NSError *_Nullable error) {

               if (granted) {

                   DebugLog(@"notification push open succeed");

                }else {

                   //reminder user to open the push notification

                }

            }];

        }else {

           //ios8~ios10以下

            [[UIApplicationsharedApplication]registerForRemoteNotifications];

            [[UIApplicationsharedApplication]registerUserNotificationSettings:[UIUserNotificationSettingssettingsForTypes:UIUserNotificationTypeAlert |UIUserNotificationTypeBadge | UIUserNotificationTypeSound categories:nil]];

        }

    }else {

       //ios8以下

        [[UIApplicationsharedApplication]registerForRemoteNotificationTypes:UIUserNotificationTypeSound |UIUserNotificationTypeBadge |UIUserNotificationTypeAlert];

    }

至于为甚么可以这样来判断,你可以看看那几个API适用的系统版本就明白了。

2.不同位置拿推送来的userInfo

iOS中如果点击一个弹出通知(或者锁屏界面滑动查看通知),默认会自动打开当前应用。由于通知是由系统调度,那么此时进入应用有两种情况,如果应用程序完全退出,那么会调用“- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions”方法;如果此时应用程序还在运行,前台或者后台,那么会调用“- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification”方法,如果是前者,那么信息回调就放在nofication.userInfo中,但是当我们已经退出了程序,我们可以这样操作:

    UILocalNotification *notification = [launchOptions valueForKey:UIApplicationLaunchOptionsLocationKey];

    NSDictionary *userInfo = notification.userInfo;

核心代码

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

   if ([applicationrespondsToSelector:@selector(isRegisteredForRemoteNotifications)]) {

        [[UIApplicationsharedApplication]registerForRemoteNotifications];

       if ([UNUserNotificationCentercurrentNotificationCenter]) {

           //iOS 10

            [[UNUserNotificationCentercurrentNotificationCenter]setDelegate:self];

           UNUserNotificationCenter *center = [UNUserNotificationCentercurrentNotificationCenter];

            [centerrequestAuthorizationWithOptions:UNAuthorizationOptionAlert |UNAuthorizationOptionBadge |UNAuthorizationOptionSoundcompletionHandler:^(BOOL granted,NSError * _Nullable error) {

            }];

        }else {

           //ios8~ios10以下

            [[UIApplicationsharedApplication]registerForRemoteNotifications];

            [[UIApplicationsharedApplication]registerUserNotificationSettings:[UIUserNotificationSettingssettingsForTypes:UIUserNotificationTypeAlert |UIUserNotificationTypeBadge |UIUserNotificationTypeSoundcategories:nil]];

        }

    }else {

       //ios8以下

        [[UIApplicationsharedApplication]registerForRemoteNotificationTypes:UIUserNotificationTypeSound |UIUserNotificationTypeBadge |UIUserNotificationTypeAlert];

    }

   UILocalNotification *notification = [launchOptionsvalueForKey:UIApplicationLaunchOptionsLocationKey];

   NSDictionary *userInfo = notification.userInfo;

    

   NSLog(@"_________%@",userInfo);

   returnYES;

}


- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification {

   NSLog(@"............%@",notification.userInfo);

}


- (void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings {

   if (notificationSettings.types !=UIUserNotificationTypeNone) {

       // add local notification

        [selfaddLocalNotification];

    }

}


- (void)applicationWillEnterForeground:(UIApplication *)applicationNS_AVAILABLE_IOS(4_0) {

    [[UIApplicationsharedApplication]setApplicationIconBadgeNumber:0];

}


#pragma mark - private function


- (void)addLocalNotification {

   UILocalNotification *localNotification = [[UILocalNotificationalloc]init];

    localNotification.fireDate = [NSDatedateWithTimeIntervalSinceNow:10];

    localNotification.repeatInterval =2;

    localNotification.repeatCalendar = [NSCalendarcurrentCalendar];

    localNotification.alertBody =@"本地通知来了,你准备好打开了吗?";

    localNotification.applicationIconBadgeNumber =1;

    localNotification.alertAction =@"打开应用";

    localNotification.alertLaunchImage =@"Default";

    localNotification.userInfo =@{@"id":@1,@"user":@"Yao"};

    [[UIApplicationsharedApplication]scheduleLocalNotification:localNotification];

}


#pragma mark ---- remove notification


- (void)removeNotification {

    [[UIApplicationsharedApplication]cancelAllLocalNotifications];

}


远程推送(Remote Notification)

远程推送相对于本地推送来说要复杂点,因为核心东西不是我们自己控制的,本地推送都是我们自己控制的,想怎么玩都可以,但是远程推送涉及到了服务端,关键是还涉及到了苹果的推送服务器。对了,我没有接第三方的推送,我是直接走的原生API,因为我看了那些第三方推送,不还是把原生的API包了一层,然后又绕回道原生,所以就自己写了,没有接第三方,直接用苹果原油API来做的推送,需要接入第三方推送的,请移步。

思想:

1.客户端向苹果注册远程通知 
2.客户端拿到苹果给我们注册成功的信号(deviceToken返回成功)
3.客户端将这个deviceToken给服务端。
4.服务端把要发送的消息和设备令牌发送给苹果的消息推送服务器,即APNs.

5.APNs根据设备令牌(deviceToken)在已注册的苹果机型上(iphone,ipad,itouch,mac)查找对应的设备,将消息发送给相应的设备。

6.客户端将接收到的消息传递给相应的应用程序,应用程序根据用户设置弹出通知信息。

Warning: 

本地推送的注意事项同样适用于远程推送,在这里就不重复说上边的两个注意事项,远程推送还有几个点需要注意

1.当有通知来的时候,如果我们的APP处于杀死状态,我们点击通知的话,可能会出现这种情况,我们的launchView还没有remove掉,通知已经显现出来了,这显然是一个bug,需要处理,处理方法:

在appdelegate中声明一个变量,这个变量就是处理是不是点击通知启动的APP,初始值为NO,当APP将到前台的时候置为YES,当APP已经处于活跃状态的时候置为NO。为什么这么搞,你可以好好研究下Application中的几个代理方法的执行顺序。

2.推送的证书和bundleID和后台导入的都必须一致,如果中间没有收到推送消息,那就和后台联调,无非就是证书不对,bundleID不对啊什么的

3.苹果推送有时候会存在延迟,这个是没办法的,不用多想,我们再想也解决不了,这是苹果自身的问题

核心代码:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

   //注册消息通知

   //因为会出现floatValue问题,所以判断系统版本号可以这样比较

   if ([applicationrespondsToSelector:@selector(isRegisteredForRemoteNotifications)]) {

        [[UIApplicationsharedApplication]registerForRemoteNotifications];

       if ([UNUserNotificationCentercurrentNotificationCenter]) {

           //iOS 10

            [[UNUserNotificationCentercurrentNotificationCenter]setDelegate:self];

           UNUserNotificationCenter *center = [UNUserNotificationCentercurrentNotificationCenter];

            [centerrequestAuthorizationWithOptions:UNAuthorizationOptionAlert |UNAuthorizationOptionBadge |UNAuthorizationOptionSoundcompletionHandler:^(BOOL granted,NSError * _Nullable error) {

               if (granted) {

                   DebugLog(@"notification push open succeed");

                }else {

                   //reminder user to open the push notification

                }

            }];

        }else {

           //ios8~ios10以下

            [[UIApplicationsharedApplication]registerForRemoteNotifications];

            [[UIApplicationsharedApplication]registerUserNotificationSettings:[UIUserNotificationSettingssettingsForTypes:UIUserNotificationTypeAlert |UIUserNotificationTypeBadge |UIUserNotificationTypeSoundcategories:nil]];

        }

    }else {

       //ios8以下

        [[UIApplicationsharedApplication]registerForRemoteNotificationTypes:UIUserNotificationTypeSound |UIUserNotificationTypeBadge |UIUserNotificationTypeAlert];

    }

   NSDictionary *userInfo = [launchOptionsobjectForKey:UIApplicationLaunchOptionsRemoteNotificationKey];

   if (userInfo) {

        [mUIManagerControllerserverPushDataWithApplicationState:application.applicationStateuserInfoData:userInfoneedJump:YES];

    }

   returnYES;

}


//注册成功远程通知

- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {

  //保存token然后传服务端

}

//注册失败远程通知

- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error {

   NSString *errorStr = [NSStringstringWithFormat:@"%@",error];

   DebugLog(@"get token failed,error:%@",errorStr);

}

//ios10以下走

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

 //iOS10以下不管是在前台还是后台 当有推送来的时候都是在这里接收的 

//这里的JumpToHostApp是一个单例,专门处理通知来后跳转的逻辑

    [[JumpToHostApp sharedInstance] serverPushDataWithApplicationState:application.applicationStateuserInfoData:userInfoneedJump:mAPNSStart];

}


注意:这又是一个bug:当手机系统是10.0.几的时候可能会走下边这个方法,而不会走iOS10及以上的方法

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo {

   //iOS10有些手机可能是通过这个方法来接收推送数据的

    DebugLog(@"push information is %@",userInfo); //按需处理

}


//iOS10及以上

// The method will be called on the delegate only if the application is in the foreground. If the method is not implemented or the handler is not called in a timely manner then the notification will not be presented. The application can choose to have the notification presented as a sound, badge, alert and/or in the notification list. This decision should be based on whether the information in the notification is otherwise visible to the user.

- (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler__IOS_AVAILABLE(10.0)__TVOS_AVAILABLE(10.0)__WATCHOS_AVAILABLE(3.0) {

    completionHandler(UNNotificationPresentationOptionAlert);

    [[JumpToHostApp sharedInstance]serverPushDataWithApplicationState:UIApplicationStateActiveuserInfoData:notification.request.content.userInfoneedJump:mAPNSStart];

}


// The method will be called on the delegate when the user responded to the notification by opening the application, dismissing the notification or choosing a UNNotificationAction. The delegate must be set before the application returns from applicationDidFinishLaunching:.

- (void)userNotificationCenter:(UNUserNotificationCenter *)center

                              :(UNNotificationResponse *)response withCompletionHandler:(void(^)())completionHandler__IOS_AVAILABLE(10.0)__WATCHOS_AVAILABLE(3.0)__TVOS_PROHIBITED {

//do somer avaiable.it has some qusetion.

completionHandler();

       [[JumpToHostApp sharedInstance]serverPushDataWithApplicationState:UIApplicationStateBackground userInfoData:response.notification.request.content.userInfoneedJump:mAPNSStart];

}


- (void)applicationWillEnterForeground:(UIApplication *)application {

   // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.

   mAPNSStart =YES;

}


- (void)applicationDidBecomeActive:(UIApplication *)application {

    // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.

   mAPNSStart =NO;

   NSInteger badge = [[UIApplicationsharedApplication]applicationIconBadgeNumber];

   if (badge >0) {

        badge =0;

        [[UIApplicationsharedApplication]setApplicationIconBadgeNumber:badge];

    }else {

        [[UIApplicationsharedApplication]setApplicationIconBadgeNumber:1];

        [[UIApplicationsharedApplication]setApplicationIconBadgeNumber:0];

    }

}


然后在JumpToHostApp这个类中处理这个跳转逻辑,首先我们还需要加一个bool变量,为了看是不是通知来的 mNotifiNeedJump.初始化为NO,我们在移除launchView的时候调用方法:

- (void)removeLaunchView

{

     [selfinitUIFrame];

    if (mNotifNeedJump ==YES) {

          [selfserverPushDataWithApplicationState:[UIApplicationsharedApplication].applicationStateuserInfoData:mUserInfoDicneedJump:YES];

     }

    else {

         mNotifNeedJump =YES;

     }

}

方法实现,有些消息是打开APP后需要弹框的,有些消息是APP打开后需要跳转到对应界面的

- (void)serverPushDataWithApplicationState:(UIApplicationState)applicationState userInfoData:(NSDictionary *)userInfo needJump:(BOOL)jump {

    if (jump) {

         if (mNotifNeedJump ==YES) {

               [selfparserPushData:userInfobJump:YES];//可以跳跳到对应的View

          }else {

              mNotifNeedJump =YES;

          }

     }else {

         //显示弹框也就是APP处于活跃状态下

          [selfparserPushData:userInfobJump:NO];

     }

}


实战流程:

项目设置

project->Capabilities进行设置,如下图:需要打开Push Notifications,还有下边的Background Modes->Remote Notifications


注册推送证书

关于注册推送证书,可以参考我之前的一篇博客,里边是注册推送证书的流程,已经把所有步骤都用图片截取下来,点此链接:APNs消息推送开发证书创建图解。

写对应的code

和本地推送基本一样,不再赘述。因为我们肯定不是只管消息来,我们还得做消息来的时候的处理,所以我另单独写了个类,专门处理那些来的消息。appdelegate中越简单越好。


1 0