手记

APNs推送那些事

目录
  • 0.各版本推送特性
  • 1.证书配置
  • 2.测试脚本
  • 3.xcode7项目配置推送
  • 4.推送处理
  • 5.遇到的坑
  • 6.参考资料
  • 7.消息大批量发送方案
正文
0.各版本推送特性

APNs Apple Push Notification service (APNs)
推送字段长度
很显著的变化是从iOS8开始Apple对消息字节有很大的扩容

推送内容为字节=(x)个字节 - 45个设备Token/默认占位标示符
x<iOS8              256字节
x>=iOS8&&x<iOS9     2KB
x>=iOS9             4KB

推送通知样式

系统版本         显示样式
iOS8以前        只有3个地方展示,点击进入应用
iOS8版本          提供快捷处理Actions
iOS9版本          提供快捷回复TextInput
1.证书配置

这方面的资料有很多,优秀的博文也很多,按照下面接个配置即可。

http://blog.csdn.net/showhilllee/article/details/8631734 [是对raywenderlich的一个翻译版本,需要注意的是这个版本还是开发者中心改版之前的一个版本,但大体流程没有问题]
http://www.raywenderlich.com/32960/apple-push-notification-services-in-ios-6-tutorial-part-1
http://www.raywenderlich.com/32963/apple-push-notification-services-in-ios-6-tutorial-part-2

这里有几个坑需要注意一下

1.1 导出时点击证书上级 级别,即iPhone Developer:E.....这个

验证.p12的方式有很多种,我一切从简,直接把.p12传到jpush上去验证,很直观明了。

1.2 证书文件的有效期

无论是Development Push SSLCertificate还是Production Push SSL Certificate 都有过期时间的。
Development Push SSL Certificate有效期大概四个月左右,而ProductionPush SSL Certificate的有效期是一年。需要注意在过期之前生成新的证书,以免影响使用。

1.3 那段pushMe.php貌似有问题了,下面会贴出我的测试php代码。

2.测试脚本
<?php
    /**
    {
        "aps":{
            "alert":"此处有两个服务器需要选择,如果是开发测试用,选择第二名sandbox的服务器并使用Dev的pem证书,如果是正是发布,使用Product的pem并选用正式的服务器",     //消息首页展示内容
            "badge":10,                 //icon上未读消息标示个数
            "sound":"default",          //推送听到的铃声
            "data":{                    //推送消息主体,供程序启动时做相应处理
                "tid":1000001,          //通知Id 做设备打开通知上报数据使用
                "id":101,               //跳转id [可为课程id/计划id/文章id/ 是web页面默认为0]
                "type":0,   // 0 系统通知[默认] 1 好友新消息  2 新用户注册  3好友请求 4课程更新
                "systype":1,  //1 课程 2计划 3文章 4活动 5web页面跳转
                "url":"http://www.imooc.com/abc.html" //为4web页面跳转使用 |非4web页面清除url字段
            },
            "category"=>"CATEGORY_ID"     //用来快捷处理消息唯一标示
        }
    }

    */
    $deviceToken= '944a39dada1f6daf10b62fe7d135063a959ad568d1c5879656e043943.....'; //没有空格
    $body = [];
    $type = 5;  //1 课程 2计划 3文章 4活动 5web页面跳转 控制推送内容

    ///拼接推送字符串
    switch ($type) {
        case 1:
            $content = '从搭建Golang开发环境开始, 一步步介绍Golang系统库之输入输出的功能及特性。结合行数统计及图片读取,在实战中扎扎实实的学习Golang';
            $body = array("aps" => array("alert" => $content,"badge" => 10,"sound"=>'default','data'=>array('tid'=>10000,'id'=>492,'type'=>0,'systype'=>$type)));
            break;
        case 2:
            $content = '随着互联网的发展速度迅猛,前端工程师职业越来越火热,想学习Web前端技能吗 ? 该路径从基础知识到实战案例演练,一步步带您快速掌握如何搭建网站静态页面、开发网站交互特效,为您打开WEB前端工程师大门。还在等什么?快来学习吧!';
            $body = array("aps" => array("alert" => $content,"badge" => 11,"sound"=>'default','data'=>array('tid'=>10001,'id'=>32,'type'=>0,'systype'=>$type)));
            break;
        case 3:
            $content = 'CodeStriker CodeStriker是一个免费&开源的Web应用程序,可以帮助开发人员基于Web的代码审查。它不但允许开发人员将问题、意见和决定记录在数据库中,还为实际执行代码审查提供了一个舒适的工作区域。 官方网站:http://codestriker.sourceforge.net/index.html 2)RhodeCode Rhode';
            $body = array("aps" => array("alert" => $content,"badge" => 12,"sound"=>'default','data'=>array('tid'=>10002,'id'=>2493,'type'=>0,'systype'=>$type)));
            break;
        case 4:
            $content = '高薪捉拿程序大拿';
            $body = array("aps" => array("alert" => $content,"badge" => 12,"sound"=>'default','data'=>array('tid'=>27820,'id'=>2493,'type'=>0,'systype'=>$type,'url'=>'http://t.imooc.com')));
            break;
        case 5:
            $content = '如何从零开始开发一款AppleWatch App?';
            $body = array("aps" => array("alert" => $content,"badge" => 13,"sound"=>'default','data'=>array('tid'=>10003,'id'=>0,'type'=>0,'systype'=>$type,'url'=>'http://t.imooc.com'),"category"=>"CATEGORY_ID"));
            break;
        default:
            # code...
            break;
    }

    $ctx = stream_context_create();
    //如果在Windows的服务器上,寻找pem路径会有问题,路径修改成这样的方法:
    //$pem = dirname(__FILE__) . '/' . 'apns-dev.pem';
    //linux 的服务器直接写pem的路径即可
    stream_context_set_option($ctx,"ssl","local_cert","ck.pem");
    $pass = "1234";
    stream_context_set_option($ctx, 'ssl', 'passphrase', $pass);
    //此处有两个服务器需要选择,如果是开发测试用,选择第二名sandbox的服务器并使用Dev的pem证书,如果是正是发布,使用Product的pem并选用正式的服务器
    // $fp = stream_socket_client("ssl://gateway.push.apple.com:2195", $err, $errstr, 60, STREAM_CLIENT_CONNECT, $ctx);
    $fp = stream_socket_client("ssl://gateway.sandbox.push.apple.com:2195", $err, $errstr, 60, STREAM_CLIENT_CONNECT, $ctx);
    if (!$fp) {
    echo "Failed to connect $err $errstrn";
    return;
    }
    print "Connection OK\n";
    $payload = json_encode($body);
    $msg = chr(0) . pack("n",32) . pack("H*", str_replace(' ', '', $deviceToken)) . pack("n",strlen($payload)) . $payload;
    echo "sending message :" . $payload ."\n";
    fwrite($fp, $msg);
    fclose($fp);
?>
3.xcode7项目配置推送

3.1 配置你的项目bundleid和推送证书是一致的*

3.2 打开推送(貌似现在不打开,在info.plist中设置了也是直接可以用的)

3.3 选择推送测试证书

运行项目到手机,直接测试一下你的推送了,手机应该收到推送消息,若不对请自查上面各个步骤。

4.推送处理

以下方法均是AppDelegate中的

4.1 上报机器Token给服务器处理函数
- (void)application:(UIApplication*)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData*)deviceToken
{
    NSString* newToken = [deviceToken description];
    newToken = [newToken stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"<>"]];
    newToken = [newToken stringByReplacingOccurrencesOfString:@" " withString:@""];
    //此处将token上报给服务器,token有可能随着app的删除重装改变,请不要将它做的唯一标示。
 }

取token错误处理

- (void)application:(UIApplication*)application didFailToRegisterForRemoteNotificationsWithError:(NSError*)error
{   
    NSLog(@"Failed to get token, error: %@", error);
}
4.2 推送快捷处理

系统要求:

NS_CLASS_AVAILABLE_IOS(8_0)

效果图



在AppDeleate didFinishLaunchingWithOptions 中调用

//1.创建消息上面要添加的动作(按钮的形式显示出来)
UIMutableUserNotificationAction *action = [[UIMutableUserNotificationAction alloc] init];
        action.identifier = @"action";//按钮的标示
        action.title=@"Accept";//按钮的标题
        action.activationMode = UIUserNotificationActivationModeForeground;//当点击的时候启动程序
        //    action.authenticationRequired = YES;
        //    action.destructive = YES;

        UIMutableUserNotificationAction *action2 = [[UIMutableUserNotificationAction alloc] init];
        action2.identifier = @"action2";
        action2.title=@"Reject";
        action2.activationMode = UIUserNotificationActivationModeBackground;//当点击的时候不启动程序,在后台处理
        action.authenticationRequired = YES;//需要解锁才能处理,如果action.activationMode = UIUserNotificationActivationModeForeground;则这个属性被忽略;
        action.destructive = YES;

        //2.创建动作(按钮)的类别集合
        UIMutableUserNotificationCategory *categorys = [[UIMutableUserNotificationCategory alloc] init];
        categorys.identifier = @"CATEGORY_ID";//这组动作的唯一标示
        [categorys setActions:@[action,action2] forContext:(UIUserNotificationActionContextMinimal)];

        //3.创建UIUserNotificationSettings,并设置消息的显示类类型
        UIUserNotificationSettings *uns = [UIUserNotificationSettings settingsForTypes:(UIUserNotificationTypeAlert|UIUserNotificationTypeBadge|UIUserNotificationTypeSound) categories:[NSSet setWithObjects:categorys, nil]];

        //4.注册推送
        [[UIApplication sharedApplication] registerForRemoteNotifications];
        [[UIApplication sharedApplication] registerUserNotificationSettings:uns];

在AppDelegate中实现这几个方法

//处理通过点击Actions进来的事件
-(void)application:(UIApplication *)application handleActionWithIdentifier:(NSString *)identifier forRemoteNotification:(NSDictionary *)userInfo completionHandler:(void (^)())completionHandler
{
    //在没有启动本App时,收到服务器推送消息,下拉消息会有快捷回复的按钮,点击按钮后调用的方法,根据identifier来判断点击的哪个按钮
}

- (void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings
{
    NSLog(@"%@", notificationSettings);
}

接收消息处理

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
{
     if (userInfo&&application.applicationState==UIApplicationStateActive) {
        //这是程序运行的时候,收到通知[这时推送直接调此方法]
    }else if (userInfo&&application.applicationState==UIApplicationStateInactive){
       // 这是程序不活跃,收到通
    }else if (userInfo&&application.applicationState==UIApplicationStateBackground){
        //这是程序在后台,收到通知
    }else{

    }
}
4.3 推送快捷回复

系统要求:

NS_CLASS_AVAILABLE_IOS(9_0)

效果图



在Appdelegate 中 添加如下代码

UIMutableUserNotificationAction *textAction = [[UIMutableUserNotificationAction alloc] init];
        textAction.identifier = @"TEXT_ACTION";//快捷回复按钮的标示
        textAction.title=@"Reply";//快捷回复按钮的标题
        textAction.authenticationRequired = NO;
//        textAction.activationMode = UIUserNotificationActivationModeForeground;//当点击的时候启动程序
        textAction.destructive =  NO;
        textAction.behavior = UIUserNotificationActionBehaviorTextInput;

        //2.创建动作(回复)的类别集合
        UIMutableUserNotificationCategory *categorys = [[UIMutableUserNotificationCategory alloc] init];
        categorys.identifier = @"CATEGORY_ID";//这组动作的唯一标示
//        [categorys setActions:@[action,action2] forContext:(UIUserNotificationActionContextMinimal)];
        [categorys setActions:@[textAction] forContext:UIUserNotificationActionContextDefault];
        [categorys setActions:@[textAction] forContext:UIUserNotificationActionContextMinimal];
        //3.创建UIUserNotificationSettings,并设置消息的显示类类型
        UIUserNotificationSettings *uns = [UIUserNotificationSettings settingsForTypes:(UIUserNotificationTypeAlert|UIUserNotificationTypeBadge|UIUserNotificationTypeSound) categories:[NSSet setWithObjects:categorys, nil]];
        //4.注册推送
        [[UIApplication sharedApplication] registerForRemoteNotifications];
4.4 推送流程图

(根据具体情况而定)

当然你可以写一个很高大上的启动任务队列来处理AppDelegate的任务处理,AppDelegate一庞大起来,会带来很多逻辑问题。

4.5 推送状态统计

If you send a notification that is accepted by APNs, nothing is returned.

If you send a notification that is malformed or otherwise unintelligible, APNs returns an error-response packet and closes the connection. Any notifications that you sent after the

The packet has a command value of 8 followed by a one-byte status code and the notification identifier of the malformed notification. Table 5-1 lists the possible status codes and their meanings.

Table 5-1 Codes in error-response packet

Status       codeDescription
0              No errors encountered
1              Processing error
2              Missing device token
3              Missing topic
4              Missing payload
5              Invalid token size
6              Invalid topic size
7              Invalid payload size
8              Invalid token
10             Shutdown
255            None (unknown)

A status code of 10 indicates that the APNs server closed the connection (for example, to perform maintenance). The notification identifier in the error response indicates the last notification that was successfully sent. Any notifications you sent after it have been discarded and must be resent. When you receive this status code, stop using this connection and open a new connection.

Take note that the device token in the production environment and the device token in the development environment are not the same value.

5.遇到的坑

当程序kill以后,推送到之后,点击通知进入App,程序不走

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

这时此方法的 userInfo 不为空,且通过取他的 [launchOptions objectForKey:UIApplicationLaunchOptionsRemoteNotificationKey] 内容,即是推送的json内容,对userInfo根据您App情况加以处理,当然userInfo包含了其他处理字段,请根据具体情况区别处理

6.参考资料

感谢一下读者的博客,本文只是对这些博文串起来叙述了APNS从证书落地到推送消息处理业务逻辑,有大量的参考资料代码,文字,感谢,统计标记在这儿,希望读者能去拜读一下前人栽的书。

配置流程没有问题,pushMe.php有问题
http://blog.csdn.net/showhilllee/article/details/8631734
http://www.raywenderlich.com/32960/apple-push-notification-services-in-ios-6-tutorial-part-1
http://www.raywenderlich.com/32963/apple-push-notification-services-in-ios-6-tutorial-part-2

phpme.php推送用这个解决
http://www.cnblogs.com/hubj/archive/2012/06/14/2549816.html

http://www.intertech.com/Blog/push-notifications-tutorial-for-ios-9/
http://blog.csdn.net/yujianxiang666/article/details/35260135

iOS9 Text Input
http://fancypixel.github.io/blog/2015/06/11/ios9-notifications-and-text-input/

WWDC2015APNS video
https://developer.apple.com/videos/play/wwdc2015-720/

http://54im.com/ios/手把手教你配置苹果apns推送服务.html#8.5_

7.消息大批量发送方案

a.消息大批量发送问题

目前由于APNS(Apple Push Notification Service)机制原因,目前easy apns的消息发送机制为:

对每一条发送的消息,为所有需要推送的设备都在数据库中apns_messages创建一条消息,然后通过轮训数据库表来一条一条向苹果消息推送服务器发送消息

在需要推送的设备较多的情况下,由于存在大量的网络链接,导致存在较长时间的延迟。

解决方案:

1、做批量消息推送时候,保持与苹果消息推送服务器的长链接

2、使用批量发送机制

You should also retain connections with APNs across multiple notifications. APNs may consider connections that are rapidly and repeatedly established and torn down as a denial-of-service attack. Upon error, APNs closes the connection on which the error occurred.

As a provider, you are responsible for the following aspects of push notifications:

You must compose the notification payload (see “The Notification Payload”).

You are responsible for supplying the badge number to be displayed on the application icon.

You should regularly connect with the feedback web server and fetch the current list of those devices that have repeatedly reported failed-delivery attempts. Then you should cease sending notifications to the devices associated with those applications. See “The Feedback Service” for more information.

b、数据库轮询效率问题

由于目前easy apns是采用数据库轮询的方式来进行消息推送,效率并不高,后期可以修改为Redis这样的NOSQL

13年走过几遍推送之后,后来项目一直不参与这块,最近突然接手这一块,发现虽然知道一些iOS7之后的新特性,但产品只能把推送归到一个推送,缺不明白其中有很多精妙的处理,有很多新的特性能可以为用户解决一些痛点,而产品的不可能做到每个端的技术细节,所以我们技术能做的时,跟上技术发展的方向,让自己知识体系保持最新,能为用户提供最优的技术解决方案。
最近有点多愁善感,文章有疏漏的地方请留言或者
weibo:@lp_马建成

10人推荐
随时随地看视频
慕课网APP