首页 > 代码库 > 推送 从入门到放弃

推送 从入门到放弃

推送

推送简直就是一种轻量级的骚扰方式

自从有了推送,各个公司基本上都在使用推送,这确实是一个比较好的提醒方式,Android较iOS强的一个部分,也就是在于Android的Notification。Google教育我们利用好Android的通知模块,做更多友好的交互,可这句话,翻译成中文,不知不觉,就变成了在Notification中推送各种广告,而且仅仅就是一些广告,Notification各种牛逼的功能,完全不需要,这也违背了Google设计Notification的初衷。

更关键的是,现在随便找一款App,没有推送的真是凤毛麟角,更可恶的是,做外卖的App给我推送奥运新闻,一条新闻十几个App推送,以至于现在很多用户都非常反感各种推送广告,就我本人而言,基本上会禁用所有广告类的App的推送。

本人非常反感推送,借用王思聪的一句话,XXX App天天给我推送各种广告,还TM是自己做的推送,真是绝了。

推送方案

轮询

轮询是最简单的与服务器保持通信的方式,即循环向服务器通信。这个方案的特点就是通信由客户端主动发起,你需要自己实现轮询消息队列、频率等等参数,在功耗和效果间做权衡,类似于TCP的短连接。

SMS

这个其实就是借助短信来实现信息的展示,只不过把短信内容展示到了Notification中,这个方案,到达率确实高,毕竟短信是比较可靠、稳定的,但劣势也很明显,就是成本很高,而且在Android平台上,短信的权限比较开放,容易被劫持。

长连接

长连接和前面提到的短连接,都是基于Socket连接的方式,他们的区别在与,短连接是每次数据传输完毕后就断开连接,而长连接不会。所以,基于轮询的方式,每次都要进行链路的连接,性能消耗更大,基于长连接的方式,就是对这点的改进。应用一旦与服务器连接成功,并不会主动断开连接,后面的通信都基于这个通道。目前大部分的推送服务都是基于长连接的推送,在后台维护一个Service,维持应用与服务端之间的TCP长连接。

推送方案

iOS

iOS这边使用系统统一的APNs,所有推送消息都由苹果的服务器进行下发,同时,也由系统进行统一展示和处理。

GCM

与iOS一样,Android同样有一套内置的推送方案,但很可惜的是,Google的服务在中国大陆无法使用,草了个蛋。

第三方推送服务

专业的第三方推送

  • 极光
  • 个推
  • 友盟推送

手机ROM厂商推送

  • 华为推送
  • 小米推送

BAT级别的全家桶

  • 阿里推送
  • 信鸽推送
  • 百度推送

关于第三方推送服务在各个App中的使用率,大家可以参考贾吉鑫的这篇文章:

https://mp.weixin.qq.com/s?__biz=MzA5OTMxMjQzMw==&mid=2648112527&idx=1&sn=b23c1b5f3e32e343ad96d705bd4d63ff

第三方推送注意

这些推送服务大同小异,基本上一家使用了一个新功能,另外几家,也会很快推出这个功能,就例如之前比较火的,『共享推送通道进行App唤醒』这个技术,友盟、个推推出后,很快其它推送服务商就支持了,所以开发者并不需要担心哪一家推送功能比较强。

这里还需要说下现在的『推送唤醒』这样一个功能,简单的说,就是所有安装了A推送的App,只要有一个还活着,就可以把其它安装了A推送的App拉起来,从而提高推送的到达率。有些阿里系、百度系的App,被大家称作『全家桶』,实际上就是因为这个原因,这个方式,确实能在一定程度上提高推送到达率,但另一方面,也破坏了Android生态,增加了功耗,打乱了系统的清理策略。

另外,小米推送、华为推送,大家接入的原因可能很简单,就是他们的手机市场占有率比较高,接入他们自家的推送,可以在一定程度上提高到达率,但需要注意的是,推送分为透传和非透传两种方式,透传即我们自己App处理推送消息,而非透传,则是交给相应的PushSDK处理,对于小米推送、华为推送来说,只有采用非透传消息,到达率采用保证,而透传消息,与其它推送并没有什么区别,换句话说,小米手机、华为手机,只对非透传的推送消息做了可靠性保证,但非透传消息的展示格式非常固定、简单,且不能自定义,这是一个很大的问题,这点应该是很多开发者的误区。

最后,很多推送服务都需要在Application中进行初始化,同时,各种被唤醒策略,又会拉起Application,导致推送进程的重复,所以,这里经常需要对进程名进行过滤,非主进程,不进行初始化。

自建推送服务

基本都是基于AndroidPN、MQTT、XMPP、长连接这些方式去实现的,自己搭建Push平台服务,一个最大的问题就是服务端的架构设计,不仅成本高,而且效果不一定好,建议中小企业不要轻易尝试。

推送名词解释

RegistrationID\ClientID

一般来说,类似这类ID都是用于唯一标识应用\用户的,每个App在每台手机上都会生成一个唯一ID。

RegistrationID\ClientID生成规则

Android平台上因为国内存在大量山寨设备,所以很多设备的IMEI、Mac地址、AndroidID 都有可能为空或者错误,所以不能单独作为唯一标识,需要将这些进行组合起来使用。

对于应用卸载后RegistrationID的问题,很多PushSDK的策略是,生成一个DeviceID保存到本地存储,应用被卸载后如果被重新安装,如果检测到存储里的DeviceID还在的话,就判定是同一个设备,不重新生成RegistrationID。

AppKey\AppID

这些Key基本都是用于验证App的,每个包名对应一个加密的Key。

透传\非透传

非透传消息是指推送消息被PushSDK获取并处理,透传消息是指推送消息被PushSDK交给宿主应用处理,非透传消息通常只能设置一些固定的样式,比较简单,而透传消息,可以由App自定义处理,比较灵活。

推送数据基础

累计注册

通过应用使用的appid统计用户注册总量。

日在线用户

通过应用使用的appid统计当天的在线用户数。

活跃用户

通过应用使用的appid统计当天在推送平台激活过的用户总数。

在线下发率

在线消息下发数/总下发数。

回执率

消息回执数(去重)/消息在线下发数。

到达率

到达数/实际下发数。

百日内联网用户数(可推送用户数)

是指最近三个月内有登录过(设备与推送服务端建立长链接)的设备总数,即有效可下发的用户数。一般的推送服务端认为,设备在100天内没有登录请求,认为该设备已经失效,所以无需再次发送。

实际下发数

实际可推送设备数(在消息有效期内,有联网并推送进程正常的设备,即消息有效期内的在线下发数。消息有效期就是设置的离线时间)。

到达数

客户端SDK接收到消息的设备数(通过统计客户端SDK接收到消息后的回执获得)。

展示数

用自定义非透传消息在用户手机展示过的设备数。

点击数

点击通知栏消息的设备数。

推送数据分析

那么关于推送,大家实际上最关系的,就是『到达率』。那么这个到达率究竟怎么计算呢?

首先我们举个例子来说明上面的这些数据背后的实际意义,例如,我们有一款App,有100w的下载量,每个App启动后,都将上报给服务器一个唯一ID,所以,累计注册量就是100w,也称发送总量。

那么在服务端准备发送推送的时候,当前手机端推送进程还活着的,也就是说推送的长连接还健在的,就是在线设备,如果按天算,那么就叫日在线设备数,我们假设这个数字是60w。

OK,推送发出去后,客户端收到推送消息,并产生回执,代表完成了一次推送,假设这些完成推送的设备是55w,这个就是送达设备数。一般来说,只要设备在线,基本都能送达,所以这个数字和在线设备数非常接近,不接近的话,这个推送基本就有问题了,其中可能送不达的原因就在于网络切换等导致长连接断掉这类因素。

那么到这里,一般的推送服务商会使用送达设备数/在线设备数的方式来计算到达率,当然,前面我们也说了,这个比例一定是很高的,如果保持长连接的设备都不能收到推送,那一定是有问题了。

而一般的到达率,应该是送达设备数/可送达设备数,也就是百日内活跃的设备数,这样一除,这个比例一下子就小了很多,因为谁也不知道,这一百天内曾经活跃的用户,第二天是不是就已经把你卸载了。所以说,Android下统计推送的到达率一般都很低,而推送服务商宣传的到达率都很高,这其实就是一个偷换概念的问题,我们说的是一般的到达率,而服务商宣传的是在线到达率。

而且,这个到达率与iOS完全没有可比性,因为iOS统一通过APNs来进行推送,且无法获取到达回执,所以,iOS基本不存在到达率这一说法,如果有,几乎也是100%,完全没有意义,所以,如果哪一天有产品或者运营跟你说,为什么Android的到达率比iOS的到达率差这么多,请毫不客气的砸它一斤苹果。

Tag\Alias

Tag

Tag,或者叫标签,是用户的一种属性,在给某些用户设置某类标签后就可以针对推送。比如给喜欢『编程』的人打上『编程』的标签,就可以只给他们精准推送。

通常情况下,一个设备(在一个App里)可以设置多个标签。标签与别名类似,其对应关系也是保存在推送服务器侧的。

Alias

Alias,或者叫别名,是对已经安装某应用的用户取个别名进行标识,在对该用户消息推送时,就可以用此别名来进行推送。设置了别名后,推送时服务器端指定别名即可。推送服务器端来把别名转化到设备ID来找到设备。

Tag和Alias他们的共同点在于,提供对用户的精确推送。

技术分享

推送原理

目前大部分的第三方推送服务,都是基于长连接的推送方案,下面将对这个方式进行详细讲解。

NAT

首先,我们需要了解下一个网络基本知识——NAT,即网络地址转换(Network Address Translation,NAT),这是因为IP地址是有限的,手机无论是通过路由器还是数据网络,都有一个内网IP地址,同时,路由器上会维护一个外网IP地址,从而形成一个NAT路由表,即内网IP地址:端口,以及对应的外网IP地址:端口。这样通过一层层封装与解封装,就达到了内网与外网交换通信的方式。

NAT超时

由于NAT路由表的大小有效,所以一般路由都有NAT有效期,WIFI下,这个NAT有效期可能会比较长,而在数据流量下,运营商一般都会尽快更新NAT路由表,淘汰无效的设备,所以,在使用数据流量时,长连接经常容易断。

那么除了NAT路由表主动淘汰过期的设备之外,切换网络环境和DHCP服务器租期到期,这些情况都有可能导致NAT路由表改变,从而造成长连接中断。

心跳包

前面我们说了,现在的推送服务一般采用的是长连接的通信方式,而长连接会因为NAT路由表的更新而中断,所以,客户端会定时向服务端发送一个心跳包,来定期告知NAT路由表,我还活着,别杀我!这就是心跳包的作用——防止NAT路由表超时,同时检测连接是否被断开。

心跳包的心跳时间

既然心跳包的作用是防止NAT超时,那么就需要将心跳包的发送频率设置为小余NAT超时的检测频率,而WIFI和数据流量下,对于NAT路由表的超时时间又是不一样的,而且不同的网络运营商的超时时间,甚至都不一样,所以,一个比较好的方法就是根据设备当前网络环境,来动态的设置心跳时间。

注意,心跳包与轮询是不一样的,心跳包建立在长连接上,只要发送数据即可,而轮询每次都是一个完整的TCP连接。

心跳包谁来发

既然需要定时任务,那么就需要使用AlarmManager来作定时唤醒了,原因我之前的文章有讲过,是关于处理器唤醒的原因,这里就不赘述了,大家可以参考我之前的文章:

http://mp.weixin.qq.com/s?__biz=MzAxNzMxNzk5OQ==&mid=2649484680&idx=1&sn=bd9086a95b769af8d8644cf681ce66ec#rd

进程保活

所谓进程保活,是指App希望尽可能的保证自己的App的推送进程能够存活在后台,以保证可以收到服务端的推送消息,因此,才出现了一大批关于进程保活的方式,例如NDK层的文件锁,fork子进程、前台服务、进程优先级等等方式,然而,这些东西,实际上,都不能完全保证手机的进程管理策略放过你,特别是Android 5.0以后的系统,Android对进程的管理更加严格,还有国内的这些ROM层的修改,ROM想要杀你这个进程,你怎么做也没有办法,哦,除了白名单。所以,不要再花心思去找什么进程保活的黑科技了,好好做好应用,提供用户的使用黏性,才是最佳的保活,而对于一些产品、运营所谓的『为什么微信、QQ都可以保活』这样的问题,我建议你回答它:『如果你能把产品做到微信、QQ那样的数量级,我也能让你活!』

推送整合方案

介于各种第三方推送与ROM推送的特点,我们目前采用的推送方案,名为『UniversalPushSDK』,即整合了多个不同的推送渠道,通过模板设计模式来进行整合,并向外暴露统一的接口,这种方式的好处在于UniversalPushSDK利用的各个不同推送特点,提高推送到达率,但是坏处在于,包的体积会大一些。例如,我们现在整合了『小米推送、极光推送、华为推送』,在系统启动的时候,判断当前系统,如果是小米系统,则启用『小米推送』,如果是华为手机,则启用『华为推送』,其它的Android设备,则启用『极光推送』,通过这种方式来设计我们自己的推送SDK,可以在一定程度上,利用好各个推送平台的特性。

那么如果利用这种方式来设计SDK给到不同的App接入,就需要能够将应用的推送Key做到动态配置,这也是我们遇到的最大的一个问题,解决方法大家可以参考我之前写的一篇文章:

http://blog.csdn.net/eclipsexys/article/details/51283232

虽然我极力反对这种方案,我坚持认为,做好App,提升用户使用黏性,才是提升推送到达率的关键

<script type="text/javascript"> $(function () { $(‘pre.prettyprint code‘).each(function () { var lines = $(this).text().split(‘\n‘).length; var $numbering = $(‘
    ‘).addClass(‘pre-numbering‘).hide(); $(this).addClass(‘has-numbering‘).parent().append($numbering); for (i = 1; i <= lines; i++) { $numbering.append($(‘
  • ‘).text(i)); }; $numbering.fadeIn(1700); }); }); </script>

    推送 从入门到放弃