最近推送在IOS设备上出现无法关闭的情况,分析原因发现应用内开关按钮在本次App会话周期是有效的。但是IOS设备重启就会导致关闭失效,而在Android设备上不存在这个问题。借这个机会正好整理一下移动推送方面的研究结果。
实时推送原理
基础知识
要获取服务器上不定时更新的信息,一般来说有两种方法:第一种是客户端使用Pull(拉)的方式,就是隔一段时间就去服务器上获取一下信息,看是否有更新的信息出现。第二种就是 服务器使用Push(推送)的方式,当服务器端有新信息了,则把最新的信息Push到客户端上。这样,客户端就能自动的接收到消息。
虽然Pull和Push两种方式都能实现获取服务器端更新信息的功能,但是明显来说Push方式比Pull方式更优越。因为Pull方式更费客户端的网络流量,更主要的是费电量,还需要我们的程序不停地去监测服务端的变化。
IOS因为有APNS
服务并且其特殊的后台机制,所以市面上的推送服务在IOS端都是依靠封装APNS的API来实现推送。 而Android因为墙的存在导致GCM
不能使用。So,国内推送产品大致的做法是在后台保持一个心跳服务不断维持Client与Server的TCP长连接来实现实时推送
。(具体可以了解MQTT协议)
实际开发中,android一般都是直接用第三方推送的。ios可以通过官方的解决方案。
实现原理
轮训(poll)方式
应用程序应当阶段性的与服务器进行连接并查询是否有新的消息到达,你必须自己实现与服务器之间的通信,例如消息排队等。而且你还要考虑轮询的频率,如果太慢可能导致某些消息的延迟,如果太快,则会大量消耗网络带宽和电池。
SMS(push方式)
在Android平台上,你可以通过拦截SMS消息并且解析消息内容来了解服务器的意图,并获取其显示内容进行处理。这是一个不错的想法,我就见过采用这个方案的应用程序。这个方案的好处是,可以实现完全的实时操作。但是问题是这个方案的成本相对比较高,我们需要向移动公司缴纳相应的费用。我们目前很难找到免费的短消息发送网关来实现这种方案。
持久连接(Push)方式
这个方案可以解决由轮询带来的性能问题,但是还是会消耗手机的电池。IOS平台的推送服务之所以工作的很好,是因为每一台手机仅仅保持一个与服务器之间的连接,事实上C2DM也是这么工作的。不过刚才也讲了,这个方案存在着很多的不足之处,就是我们很难在手机上实现一个可靠的服务,目前也无法与IOS平台的推送功能相比。
解决方案
C2DM云端推送功能
在Android手机平台上,Google提供了C2DM(Cloudto Device Messaging)服务。Android Cloud to Device Messaging (C2DM)是一个用来帮助开发者从服务器向Android应用程序发送数据的服务。该服务提供了一个简单的、轻量级的机制,允许服务器可以通知移动应用程序直接与服务器进行通信,以便于从服务器获取应用程序更新和用户数据。C2DM服务负责处理诸如消息排队等事务并向运行于目标设备上的应用程序分发这些消息。
问题
- C2DM需要依赖于Google官方提供的C2DM服务器,由于国内的网络环境,这个服务经常不可用。
- 国内定制ROM很可能把Google这种原生的服务去掉
MQTT协议实现Android推送功能
MQTT是一个轻量级的消息发布/订阅协议,它是实现基于手机客户端的消息推送服务器的理想解决方案。
wmqtt.jar 是IBM提供的MQTT协议的实现。我们可以从这里下载该项目的实例代码,并且可以找到一个采用PHP书写的服务器端实现。
RSMB实现推送功能
Really Small Message Broker (RSMB) ,他是一个简单的MQTT代理,同样由IBM提供,具体点击查看地址。缺省打开1883端口,应用程序当中,它负责接收来自服务器的消息并将其转发给指定的移动设备。
SAM是一个针对MQTT写的PHP库。我们可以从这个地址下载它.
XMPP协议实现Android推送功能
目前它是开源的,对于其简单的推送功能它还是能够实现的。我们可以修改其源代码来适应我们的应用程序。
事实上Google官方的C2DM服务器底层也是采用XMPP协议进行的封装。XMPP(可扩展通讯和表示协议)是基于可扩展标记语言(XML)的协议,它用于即时消息(IM)以及在线探测。这个协议可能最终允许因特网用户向因特网上的其他任何人发送即时消息。关于XMPP协议的介绍,大家可以参考下大神的文章XMPP协议。
androidpn是一个基于XMPP协议的java开源Android push notification实现。 androidpn 客户端需要用到一个基于java的开源XMPP协议包asmack,这个包同样也是基于openfire下的另外一个开源项目smack,不过我们不需要自己编译,可以直接把androidpn客户端里面的asmack.jar拿来使用。客户端利用asmack中提供的XMPPConnection类与服 务器建立持久连接,并通过该连接进行用户注册和登录认证,同样也是通过这条连接,接收服务器发送的通知。
第三方平台
- 手机厂商类:小米推送、华为推送
- 第三方平台类:友盟推送、极光推送、云巴(基于MQTT)、个推
- BAT大厂的平台推送:阿里云移动推送、腾讯信鸽推送、百度云推送
- 网友推荐:个推、云巴
长连接
什么是长连接? 定时发送心跳, 这和轮询有什么区别? 心跳是干什么的? 同样是定期和服务器沟通, 为什么长连接就比轮询更加优秀? 手机休眠了TCP连接不会断掉吗?
什么是长连接
长连接就是大家建立连接之后, 不主动断开. 双方互相发送数据, 发完了也不主动断开连接, 之后有需要发送的数据就继续通过这个连接发送.
TCP连接在默认的情况下就是所谓的长连接, 也就是说连接双方都不主动关闭连接, 这个连接就应该一直存在.
但是网络中的情况是复杂的, 这个连接可能会被切断. 比如客户端到服务器的链路因为故障断了, 或者服务器宕机了, 或者是你家网线被人剪了, 这些都是一些莫名其妙的导致连接被切断的因素, 还有几种比较特殊的:
- NAT超时:国内移动无线网络运营商在链路上一段时间内没有数据通讯后, 会淘汰NAT表中的对应项, 造成链路中断.
- 网络状态切换:手机网络和WIFI网络切换, 网络断开和连上等情况, 也会使长连接断开. 这里原因可能比较多, 但结果无非就是IP变了, 或者被系统通知连接断了.
- DHCP的租期:DHCP租期到了不会主动续约并且会继续使用过期IP, 这个问题会造成TCP长连接偶然的断连.
心跳包的作用
其实主要是为了防止上面提到的NAT超时, 既然一些NAT设备判断是否淘汰NAT映射的依据是一定时间没有数据, 那么客户端就主动发一个数据.
当然, 如果仅仅是为了防止NAT超时, 可以让服务器来发送心跳包给客户端, 不过这样做有个弊病就是, 万一连接断了, 服务器就再也联系不上客户端了. 所以心跳包必须由客户端发送, 客户端发现连接断了, 还可以尝试重连服务器.
所以心跳包的主要作用是防止NAT超时, 其次是探测连接是否断开
.
心跳包时间间隔
发送心跳包势必要先唤醒设备, 然后才能发送, 如果唤醒设备过于频繁, 或者直接导致设备无法休眠, 会大量消耗电量, 而且移动网络下进行网络通信, 比在wifi下耗电得多. 所以这个心跳包的时间间隔应该尽量的长, 最理想的情况就是根本没有NAT超时, 这也就是网上常说的长连接, 慢心跳.
现实是残酷的, 根据网上的一些说法, 中移动2/3G下, NAT超时时间为5分钟, 中国电信3G则大于28分钟, 理想的情况下, 客户端应当以略小于NAT超时时间的间隔来发送心跳包.
wifi下, NAT超时时间都会比较长, 据说宽带的网关一般没有空闲释放机制, GCM有些时候在wifi下的心跳比在移动网络下的心跳要快, 可能是因为wifi下联网通信耗费的电量比移动网络下小.
关于如何让心跳间隔逼近NAT超时的间隔, 同时自动适应NAT超时间隔的变化, 可以参看Android微信智能心跳方案.
心跳包和轮询的区别
- 轮询是为了获取数据, 而心跳是为了保活TCP连接.
- 轮询得越频繁, 获取数据就越及时, 心跳的频繁与否和数据是否及时没有直接关系
- 轮询比心跳能耗更高, 因为一次轮询需要经过TCP三次握手, 四次挥手, 单次心跳不需要建立和拆除TCP连接.
资料参考
极光基础
系统区别
在IOS系统中,极光推送插件默认在App完成启动之后,立即注册苹果通知服务+启动JPush SDK。
- 注册苹果通知服务会弹出提示窗口向用户请求权限
- 启动JPush SDK是使用JPush各项API的必要条件
用户可以根据自己的需求,延迟注册和启动,需要一下操作:
- 查找并配置PushConfig.plist文件中的Delay字段为YES,表明会延迟使用,此时插件不在自动注册、启动通知
- 需要手动注册并在启动通知的地方调用API-startJPushSDK
基本API
init初始化
调用此API,用来开启JPush SDK提供的推送服务。
stopPush关闭推送
IOS中不推荐使用,原理让DeviceToken失效。
android平台
- 开发者 App 可以通过调用停止推送服务 API 来停止极光推送服务,当又需要使用极光推送服务时,则必须要调用恢复推送服务 API。
- 调用了本 API 后,JPush 推送服务完全被停止,具体表现为:
- JPush Service 不在后台运行。
- 收不到推送消息。
- 不能通过 JPushInterface.init 恢复,需要调用 resumePush 恢复。
- 极光推送所有的其他 API 调用都无效。
resumePush恢复推送
- Android 平台:极光推送完全恢复正常工作。
- iOS 平台:重新去 APNS 注册。
推送方式
ID
- getRegistrationID
JPushPlugin.prototype.getRegistrationID(callback)
别名与标签
别名Alias:为安装了应用程序的用户,取个别名来标识。以后给该用户 Push 消息时,就可以用此别名来指定。每个用户只能指定一个别名。同一个应用程序内,对不同的用户,建议取不同的别名。这样,尽可能根据别名来唯一确定用户。
标签Tag:为安装了应用程序的用户,打上标签。其目的主要是方便开发者根据标签,来批量下发 Push 消息。可为每个用户打多个标签。可以多重身份。
接口定义
JPushPlugin.prototype.setTagsWithAlias(tags, alias, successCallback, errorCallback)
//方便开发者根据标签,批量下发消息,一个用户可打多个标签,不同用户可以打同样的标签
JPushPlugin.prototype.setTags(tags, successCallback, errorCallback)
//每个用户只能指定一个别名,且建议一个别名只能指定一个用户
JPushPlugin.prototype.setAlias(alias, successCallback, errorCallback)
/*
* 旧版本中,新版本貌似有如上描述回调函数!
* JPushPlugin.prototype.setTagsWithAlias(tags, alias) tags:参数类型为数组。alias:参数类型为字符串。
* 返回值说明:函数本身无返回值,但需要注册 jpush.setTagsWithAlias 事件来监听设置结果:document.addEventListener("jpush.setTagsWithAlias", function(event) {});
*/
参数说明
- tags
- 参数类型为数组。
- 空集合表示取消之前的设置。
- 有效的标签组成:字母(区分大小写)、数字、下划线、汉字。
- 限制:每个 tag 命名长度限制为 40 字节,最多支持设置 100 个 tag,但总长度不得超过1K字节(判断长度需采用UTF-8编码)。
- 单个设备最多支持设置 100 个 tag,App 全局 tag 数量无限制。
- alias
- 参数类型为字符串。
- 空字符串 ("")表示取消之前的设置。
- 有效的别名组成:字母(区分大小写)、数字、下划线、汉字。
- 限制:alias 命名长度限制为 40 字节(判断长度需采用 UTF-8 编码)。
事件
- 点击推送通知:event - jpush.openNotification
- 前台收到推送:event - jpush.receiveNotification
- 后台收到推送:event - jpush.backgroundNotification
- 获取自定义消息内容:event - jpush.receiveMessage
徽章
- setBadge, resetBadge:上传 badge 值至 JPush 服务器
- setApplicationIconBadgeNumber:修改本地 badege 值
- getApplicationIconBadgeNumber:获取 iOS 的角标值
本地通知
- clearAllLocalNotifications:清除所有本地推送对象
- clearNotificationById
获取用户推送设置
window.plugins.jPushPlugin.prototype.getUserNotificationSettings(callback)
IOS分析,分为:iOS 10 before
和iOS 10 after
,感觉好麻烦的样子
设置推送时间
方法如下:
//设置允许推送时间
window.plugins.jPushPlugin.setPushTime(days, startHour, endHour)
//设置通知静默时间
window.plugins.jPushPlugin.setSilenceTime(startHour, startMinute, endHour, endMinute)
通知关闭
android关闭通知
- 调用stopPush
- 关闭所有类型消息的接受
- 其他API调用无效
- 必须调用resumePush才能恢复
- 恢复后如果消息还在time_to_live内,客户端会收到消息
- 置空别名/标签
- 一般需求:退出登录后不要收到通知,对应的操作是:退出登录-置空别名-点击登录-重设别名。
- 注意点:别名置空至重设别名期间,推送的消息,在重设别名后,设备不会收到。
IOS关闭通知
JPush无法控制APNs通知的展示与否,实现关闭APNs通知,有如下变通的方法:
- 文字说明,引导用户去设置中关闭
- 置空别名/标签
具体应用
实现定向推
你可以使用给该项目的用户设置别名、标签、或者直接获取设备的registrationID用ID来推送,来达到你的需要。
每个设备存5条离线消息的,你可以根据你的需要设置离线保存的时长~推送的参数是time_to_live
设置alias问题
- 设置tagalias的接口调用频率是5s以上,低于5s就会报6002超时
- 极光后台在短时间内不允许对同个设备重复设置别名和标签
- 2小时内,不支持对同个regid设置相同tag、alias值;
- 5s内也不支持对同个regid设置不同tag、alias值
- 如果是设置相同的,会直接返回成功,如果超频(5s内)会直接丢弃,客户端就是收不到响应
客服解答
- 重新进入app,如果退出前init成功,无须再次init,但多次init没有问题
- 先设置别名成功、后stoppush,不会影响别名,不需重新设置。
- 调用stopPush之后,必须resumePush之后在init,否则init无效。直接将resumePush封装在init中,没有问题!
- 设置别名可能出现6002的返回码,错误原因可能是设置频率过快和网速问题,因为设置别名会上传到极光的服务器,同时建议上传到自己的服务器保存起来!
基础APNS
- 申请App IDS,勾选Push Notification
- 创建证书请求文件,下一步骤需要
- 创建push证书,分为开发证书和发布证书,勾选Apple push notification service SSL
- 双击打开证书,钥匙串访问中选择“My Certificates” 和"login"
- 导出 .p12 证书文件,注意要选“login”和“My Certificates” 导出证书时要选中证书文件,不要展开private key。
- 上传证书,在JPush控制台上传
- 创建PP证书,双击下载下来的Provisioning Profile,添加到xcode。
- XCode证书配置,设置bundleID和JPush注册的一致,设置开发者证书的PP文件
- 开启推送,Application Target的Capabilities->Push Notifications选项,确认step状态都是勾
- ios不同于android,在初始init极光之前,需要注册苹果通知服务,API - startJPushSDK
- JPushConfig.plist字段值说明
- AppKey:JPush提供的应用key
- Channel:指明应用程序包的下载渠道,为方便分渠道统计,具体值由你自行定义,如:App Store。
- isProduction:生产环境还是开发环境
- isIDFA:是否使用 IDFA 启动 SDK
- Delay:Delay字段为YES,表明会延迟使用,此时插件不再自动注册、启动通知。
- 其他
- capablilties->background modes->remote notifications
- 控制台建议:请将JPush的初始化方法,添加到[UIApplication application: didFinishLaunchingWithOptions:]方法中,否则JPush将不能准确的统计到通知的点击数量。参考文档
问题分析
IOS设置别名出错6003
主要是由于setAlias方法的参数必须是字符串,应该传参时使用toString()方法进行转换,确保是String类型。
IOS生产环境无法收到通知,开发环境却可以
主要是因为IOS APP有生产和开发环境的区别,Android是没有的,IOS在发布时打包的证书是生产环境证书,直接使用xcode安装的应用使用的是开发环境证书,总结起来就是app是什么环境,由你打包的证书决定,同时建议PUSHConfig.list的IsProducation建议和证书保持一致。
至于为什么会出现上述问题,是因为IOS在推送通知时必须指定是什么环境,通过Option类apns_production指定,true表示生产环境,false表示开发环境,默认为FALSE。
极光总结
由于安卓机型的复杂多样,同时由于并不是极光的VIP用户,在华为机型上的到达率不是很好,在小米、魅族等机型在按照文案设置的基础上还能接受,但是设置起来着实麻烦,一般用户还真找不着地方。比如笔者亲测在OPPO R9上设置步骤如下:
- 打开权限;设置-通知与状态栏-通知管理-找到对应应用,全部打开。
- 开启后台运行权限;手机管家app-权限隐私-自启动管理,将对应应用修改成允许自启动。
- 关闭冻结应用功能;设置-电池(寻找对应应用,没有则在其他中)-找到应用,关闭后台冻结和自动优化。
- 锁定应用,打开多任务,下拉锁定应用,应用左上角会有个小锁,避免应用被清理掉。
在机型统计上,华为机型占比居第二,因此针对华为机型,建议使用华为官方推送。