• iOS 推送简介


    一、Push 的实现大体上可以分为三种:信道、轮询、长链接

    方式1:【信道】

    信道是最佳方案,但是需要运营商支持。

    控制信道push,不利用TCP/ IP,而是利用底层的移动通信的控制信道进行push(就是呈现我们手机是哪家信号,是否有电话呼入,是否注册在网的那个信道),短信也是走的这个信道,我们通常可以看到的通过短信push-alert机制,或者BB的push机制都是这样的机制。而BB这样的做法,是一定要和运营商深度合作才能够实现的,这也是BB目前销售运营模式的要点。

     

    方式2:【长连接】

    socket常连接机制(http协议本身,是一个典型的请求-返回的偶连接模式,所有http下的推送基本都是依赖轮询实现)。

    优点:

      1.更为及时一点。

      2.现在云端的成本变得越来越低,所以这种方式的成本也变的可以承受。

    缺点:

      1.长连接对于服务器和数据通道来讲是比较大的负担。
      2.采用这种方式,其实要求TCP/IP一直处在连通的方式,3G来说的话(1)耗电开销太大。(2)要处理关于断线重连以及IP注册的问题。
     
    实施方案:
      通常维护一个长连接也需要两端做工作,客户端需要智能的判断通道的活跃情况,因为有时候一个静默的长链接,有可能会被运营商网关GGSN / SGSN给切断(为了保证服务更多的用户),判断方法有动态心跳等;服务器也需要知道这个通道是否活跃,并在适当的时候发送Push内容;BTW:静默的通道其实对于终端电量的消耗还是比较小的。
     
    案例:
      顺丰做的项目里采用了这种方式,因为他们是效率和服务质量第一的要求,快递员随身携带多块备用电池(他们还只是使用GPRS,没有3G耗电厉害) 多数情况下,是一个所谓的push-alert机制以保证push协议的可复用性。
      iOS 也是此种方式。

    方式3:【轮询】:

    优点:可以相对的 优化算法来节省系统资源。

    缺点:算法相对要求比较高

      云服务相比推送通知来说,要相对简单一些,因为云服务的推送的内容,很多是在特定应用启动以后才需要使用的,可以在应用内部做文章,所以轮询可以做得更方便一些(拿通讯录为例,可以在启动通讯录的时候插入一个云端轮询同步,甚至可以在手指滑动或者搜索的时候加入云端查询同步),这样会让人感觉及时性很高。

      通知通常是在应用执行之前到达,用户的行为更不好判断,情况更多,所以麻烦一些。

    轮询不一定是定时定周期的轮询,可以和特定的算法,操作,结合,在一些应用的情况下,可以让人感觉到是及时的更新。

    轮询算法优化角度:

      1.设定一个最小的轮询间隔,例如5秒,也就是说不能无休止的添加轮询密度,这个数字一般感觉是用户在此系统和使用环境下2~3次正常单次操作-响应过程的时间。

        这样对于一般的用户体验来说会比较能够接受。

      2.屏幕解锁时插入一次轮询。

      3.手机处在使用状态,降低轮询周期

      4.使用WIFI时,降低轮询周期。

      5.1分钟内发生过3次高密度推送的,暂时降低轮询周期

      6.启动特定应用(例如注册了推送的应用),插入一次轮询 

    因为有些算法优化的东西都是第三方开发者无法做到的,所以iOS的推送一定是要apple自己来提供比较好。

    二、iOS 推送机制 

    苹果的推送服务APNs基本原理简单来说就是苹果利用自己专门的推送服务器(APNs)接收来自我们自己应用服务器的需要被推送的信息,然后推送到指定的iOS设备上,然后由设备通知到我们的应用程序,设备以通知或者声音的形式通知用户有新的消息。

    推送的前提是装有我们应用的设备需要向APNs服务器注册,注册成功后APNs服务器会返给我们一个device_token,拿到这个token后我们将这个token发给我们自己的应用服务器,当有需要被推送的消息时,我们的应用服务器会将消息按指定的格式打包,然后结合设备的device_token一并发给APNs服务器,由于我们的应用和APNs维持一个基于TCP的长连接,APNs将新消息推送到我们设备上,然后在屏幕上显示出新消息来。

    1.【远程推送注册方式】

    1.Device连接APNs服务器并携带设备序列号

    2.连接成功,APNs经过打包和处理产生device_token并返回给注册的Device

    3.Device携带获取的device_token向我们自己的应用服务器注册

      

    2.【远程推送推送过程】

     

    Provider就是我们自己程序的后台服务器,APNS是Apple Push Notification Service的缩写,也就是苹果的推送服务器。
    下图可以分为三个阶段:
    第一阶段:应用程序的服务器端把要发送的消息、目的iPhone的标识打包,发给APNS。
    第二阶段:APNS在自身的已注册Push服务的iPhone列表中,查找有相应标识的iPhone,并把消息发送到iPhone。
    第三阶段:iPhone把发来的消息传递给相应的应用程序,并且按照设定弹出Push通知。

    上图显示了我们的应用服务器将消息推送到我们的App的完整路径,其实真正完成推送的是APNS服务器,我们自己的应用服务器只是将需要推送的消息告诉苹果服务器,至于如何维护消息队列或如何保证消息能被推送到指定的设备上,这些都由苹果APNS给我们做完了。

    3.【远程推送数据结构】

      上图显示的这个消息体就是我们的服务器(Provider)发送给APNS服务器的消息结构,APNS验证这个结构正确并提取其中的信息后,再将消息推送到指定的设备。

    这个结构体包括五个部分

    第一个部分是命令标示符。

    第二个部分是我们的device_token的长度。

    第三部分是我们的device_token字符串。

    第四部分是推送消息体(Payload)的长度。

    第五部分也就是真正的消息内容了,里面包含了推送消息的基本信息,比如消息内容,应用Icon右上角显示多少数字以及推送消息到达时所播放的声音等。

    下面看下(消息体)的结构:

     这其实就是个JSON结构体。

    alert标签的内容就是会显示在用户手机上的推送信息。

    badge显示的数量(注意是整型)是会在应用Icon右上角显示的数量,提示有多少条未读消息等。

    sound就是当推送信息送达是手机播放的声音,传defalut就标明使用系统默认声音,如果传比如“beep.wav”就会播放在我们应用工程目录下名称为beep.wav的音频文件,比如当手机锁屏时QQ在后台收到新消息时的滴滴声。

    4.【应用卸载后的远程推送注销】

      有这么一种情况,当我们将应用从设备卸载后,推送的消息改如何处理呢?

      我们知道,当我们将应用从设备卸载后,我们是收不到Provider给我们推送的消息的,但是,如何让APNS和Provider都知道不去向这台卸载了应用的设备推送消息呢?针对这个问题,苹果也已经帮我们解决了,那就是Feedback service。他是APNS的一部分,APNS会持续的更新Feedback service的列表,当我们的Provider将信息发给APNS推送到我们的设备时,如果这时设备无法将消息推送到指定的应用,就会向APNS服务器报告一个反馈信息,而这个信息就记录在feedback service中。按照这种方式,Provider应该定时的去检测Feedback service的列表,然后删除在自己数据库中记录的存在于反馈列表中的device_token,从而不再向这些设备发送推送信息。连接Feedback service的过程同样使用Socket的方式,连接上后,直接接收由APNS传输给我们的反馈列表,传输完成后断开连接,然后我们根据这个最新的反馈列表在更新我们自己的数据库,删除那些不再需要推送信息的设备的device_token。

    从Feedback service读取的数据结构如下: 

    结构中包含三个部分:

    第一部分是一个时间戳,记录的是设备失效后的时间信息

    第二个部分是device_token的长度

    第三部分就是失效的device_token,我们所要获取的就是第三部分,跟我们的数据库进行对比后,删除对应的device_token,下次不再向这些设备发送推送信息。

    【注:最后以 极光推送  设备系统>=iOS 10为例 说下调用顺序】

    1. 用户在使用当前app应用 收到推送时候:调用这两个方法

    - (void)jpushNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(NSInteger))completionHandler
    
    
    - (void)jpushNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)())completionHandler
    

      

    2. 用户在使用其他app应用、锁屏(包括:当前应用下锁屏、其他应用下锁屏、桌面锁屏) 收到推送时候:直接调用上面第二个方法。

    特别提醒的是:从这点看苹果还是这样希望, 在当前app处于不活跃状态的时候(包括:锁屏,未开启,后台等),此app里面的代码是不调用的。

    【远程推送】

    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
        // Override point for customization after application launch.
        
        //APNS(苹果推送服务器)注册远程推送通知,APNS返回device token
        float version = [[[UIDevice currentDevice] systemVersion] floatValue];
        if (version >= 8.0) {
            [[UIApplication sharedApplication] registerUserNotificationSettings:[UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeAlert|UIUserNotificationTypeSound|UIUserNotificationTypeBadge categories:nil]];
            
            //注册远程推送通知
            [[UIApplication sharedApplication] registerForRemoteNotifications];
        }else{
            //ios8以下
            [[UIApplication sharedApplication] registerForRemoteNotificationTypes:UIRemoteNotificationTypeAlert|UIRemoteNotificationTypeSound|UIRemoteNotificationTypeBadge];
        }
        
        return YES;
    }
    
    //当向APNS注册成功时的回调函数
    - (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
    {
        NSLog(@"%@",deviceToken);
        //需要把deviceToken上传到服务端,要服务端提供接口
    }
    
    - (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error{
        NSLog(@"%@",error.localizedDescription);
    }
    
    - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
    {
        NSLog(@"收到了远程推送通知");
    }
    

      

    【本地推送】

    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
        // Override point for customization after application launch.
        application.applicationIconBadgeNumber = 0;
        
        //申请用户许可
        float version = [[[UIDevice currentDevice] systemVersion] floatValue];
        if (version >= 8.0) {
            UIUserNotificationSettings *setting = [UIUserNotificationSettings settingsForTypes:
                                                   UIUserNotificationTypeSound|UIUserNotificationTypeBadge|UIUserNotificationTypeAlert
                                                                                    categories:nil];
            [[UIApplication sharedApplication] registerUserNotificationSettings:setting];
        }
       
        
        UILocalNotification * localNotificaiton = [launchOptions objectForKey:UIApplicationLaunchOptionsLocalNotificationKey];
        if (localNotificaiton != nil) {
            [self processLocalNotifiction:localNotificaiton];
        }
        
        return YES;
    }
    
    -(void)processLocalNotifiction:(UILocalNotification*)localNotification
    {
        UIAlertView *alterView = [[UIAlertView alloc]initWithTitle:@"lanchApp" message:@"loc" delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil];
        [alterView show];
    }
    
    //注册设置提醒后,调用的代理方法
    -(void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings
    {
        if (notificationSettings.types != UIUserNotificationTypeNone ) {
            //注册本地推送,首先生成UILocalNotification对象
            UILocalNotification *localNotifcation = [[UILocalNotification  alloc]init];
            //提示出发的时间
            localNotifcation.fireDate = [NSDate dateWithTimeIntervalSinceNow:10];
            //提示的内容
            localNotifcation.alertBody = @"这是一个测试";
            //制定消息到来时的播放的声音文件,一定要在bundle内,而且声音的持续时间不能超过30s
            localNotifcation.soundName = @"CAT2.WAV";
            
            //设置系统角标
            localNotifcation.applicationIconBadgeNumber = 1;
            
            //注册本地通知道系统中,这样系统在指定的时间会出发该通知
            [[UIApplication sharedApplication] scheduleLocalNotification:localNotifcation];
        }
    }
    
    //当程序运行在后台,或者程序没有启动,当注册的本地通知到达时。ios会弹框提示,并播放你设置的声音。
    
     //当应用程序运行在前台会调用该代理方法,不会播放声音,不会弹框
    - (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification
    {
        //判断应用程序状态来决定是否弹框
        if (application.applicationState == UIApplicationStateActive) {
            UIAlertView *alterView = [[UIAlertView alloc]initWithTitle:@"本地推送" message:notification.alertBody delegate:nil cancelButtonTitle:@"ok" otherButtonTitles:nil];
            [alterView show];
        }else if (application.applicationState == UIApplicationStateInactive)
        {
            NSLog(@"UIApplicationStateInactive");
        }else{
            //background
            NSLog(@"UIApplicationStateBackground");
        }
    }
    

      

    github 地址:里面附带有推送证书   https://github.com/lc081200/pushExample

     

    此外】:

      

    【远程推送 App服务端相关】

    客户端实现了,服务端怎么实现了?

    原理就是实现 SSL Socket 并按照协议给苹果的服务器发送数据。

    iOS服务端工作有:

    1.证书配置,app 申请的证书iOS来做(p12证书有密码),服务器用证书做一些配置转换。

    2.还有设备的 token(一个设备一个 token)

    3.定义给设备发送推送的消息

    <?php
    //手机注册应用返回唯一的deviceToken
    $deviceToken = '6ad7b13f b05e6137 a46a60ea 421e5016 4b701671 cc176f70 33bb9ef4 38a8aef9';
    //ck.pem通关密码
    $pass = 'jetson';   
    //消息内容
    $message = 'A test message!';
    //badge 消息红色个数 
    $badge = 4;
    //sound(推送消息到手机时的提示音)
    $sound = 'Duck.wav';
    //建设的通知有效载荷(即通知包含的一些信息)
    $body = array();
    $body['id'] = "4f94d38e7d9704f15c000055";
    $body['aps'] = array('alert' => $message);
    if ($badge)
      $body['aps']['badge'] = $badge;
    if ($sound)
      $body['aps']['sound'] = $sound;
    //把数组数据转换为json数据
    $payload = json_encode($body);
    echo strlen($payload),"
    ";
    //下边的写法就是死写法了,一般不需要修改,
    //唯一要修改的就是:ssl://gateway.sandbox.push.apple.com:2195这个是沙盒测试地址,ssl://gateway.push.apple.com:2195正式发布地址
    $ctx = stream_context_create();
    stream_context_set_option($ctx, 'ssl', 'local_cert', 'ck.pem');  
    stream_context_set_option($ctx, 'ssl', 'passphrase', $pass);
    $fp = stream_socket_client('ssl://gateway.sandbox.push.apple.com:2195', $err, $errstr, 60, STREAM_CLIENT_CONNECT, $ctx);
    if (!$fp) {
        print "Failed to connect $err $errstr
    ";
        return;
    }
    else {
       print "Connection OK
    <br/>";
    }
    // send message
    $msg = chr(0) . pack("n",32) . pack('H*', str_replace(' ', '', $deviceToken)) . pack("n",strlen($payload)) . $payload;
    print "Sending message :" . $payload . "
    ";  
    fwrite($fp, $msg);
    fclose($fp);
    ?>
    

      

    服务器参考连接:

    http://blog.csdn.net/sinat_34380438/article/details/53582199

    http://blog.csdn.net/chenyong05314/article/details/8725763

    C# 的实现 网上下载的我也没看

    https://github.com/lc081200/pushServer_C3

    关于后台长连接的参考

    http://www.jianshu.com/p/174fd2673897

  • 相关阅读:
    分组排序并显示序号
    power-design--tables-export-usage
    cache implement
    get system properties
    jbpm
    JVM内存管理机制和垃圾回收机制
    java读取excel
    Java编程中“为了性能”尽量要做到的一些地方
    json串与java对象互转
    apidoc的使用
  • 原文地址:https://www.cnblogs.com/saytome/p/7122456.html
Copyright © 2020-2023  润新知