市面上目前做免费推送服务的有很多,友盟、极光、百度、小米、华为等,由于android机型的多样性,在使用单独的一种推送时,往往会造成一些机型无法获取(当然,内部原因可能很复杂)。
Android 第三方 Push 推送方案使用调查
因此往往需要同时集成多个第三方推送,并且能做到:
目前,采用了百度、小米和华为的推送SDK,层次上以百度为主覆盖大量机型,小米和华为为辅覆盖专有机型。
三种SDK在基本的使用方法上大同小异,现简单加以说明:
在移动互联网时代,移动端的开发都必须涉及 客户端/服务端的概念,但是从字面上看,服务端和客户端是完全独立的两套逻辑
客户端可以主动的向服务端请求数据,譬如最常见的登录,用户输入账户密码,点击确定按钮—客户端主动把信息抛给给服务端,并等待服务端返回—-服务端返回校验状态–客户端拿到,并呈现相应的UI状态
注意,上述流程的起点是客户端主动发起请求那么问题来了,一旦服务端的数据发生了变更,如何主动告知客户端
那么问题来了,一旦服务端的数据发生了变更,如何主动告知客户端呢? 譬如,你的服务器收到了一个紧急邮件,但是你没在电脑前边,服务器如果通知到你呢?
这种服务器主动发起的信息交互,就是推送
关于推送的具体概念和不同实现方式,可以自行查看http://www.cnblogs.com/liangqihui/p/3539984.html
在进行一下概念之前,先声明一点,也存在一些app的端内推送和端外推送是混合在一起的,譬如完全使用第三方的推送构建自己的聊天或业务逻辑,再譬如完全不依赖第三方,把自己的端内推送做的牛逼到可以用作端外
其实这些概念的界限很模糊,我们就kdzl本身来谈一下:
端内推送和端外推送是一对性质相同的概念,这里的端其实指的就是我们的应用app本身
第三方推送则是一个独立的概念,主要描述了推送的一种实现方式,主要涉及推送整个流程中是否涉及第三方的服务器
好了,我们结合实际的kdzl分析下:
现在可以看出来,kdzl的推送分为了两种,而且基本是相互独立的,这点大家要先明白
由此,我们就可以解答这样一个疑问了:
“为什么会出现,通知栏我看到了X给我发的聊天信息,但是我打开应用却没看到,等了一段时间才看到?”
“为什么X给我发的信息,我在应用里的IM聊天界面已经看多了,过了一会,又收到了位于通知栏的新信息提醒?”
我们要知道端内推送的TCP通道是依赖于 应用进程而存在的,进程在活跃状态下(前台或者后台)下,那么TCP通道的推送是基本稳定的;但是我们知道,android会在内存不足的状况下,会回收进程,并且 大量android用户甚至会主动通过系统一键清除或者第三方的一键清除kill我们的进程,这自然就导致我们端内长连接关闭,在此时我们如何通知用户一些信息呢? 这就需要第三方推送了
第三方推送其实也是维持了一个与第三方服务器的长连接服务,同样它也面临同样的问题,但是这个长连接服务比较稳定:
手机厂商自定义的长连接,不会被kill
譬如小米手机运行的小米推送,小米推送所在进程为系统常驻进程,这个进程不会被系统回收,而且一键清除也不会关闭,所以稳定
厂商合作
微信作为超级应用,其实是手机厂主动和微信合作,这些我们不细说;厂商合作的结果就是 应用所在进程 被作为系统进程或者高权限进程存活,从而不能或者很难被 一键清除等操作kill, 应用不会被kill,那么自然长连接稳定。
系统进程衍生的进程还是系统进程,微信主进程和推送进程分离,即便主进程被偶尔kill,推送进程不见得被kill;
大家可以验证下,我们知道微信其实是有logo界面的,就是那个地球;如果你能看到地球,说明应用是从死亡状态重新激活;如果你一键清除后,再打开微信,还是么考到logo,那就说明应用根本没被kill
平台适配
做一款稳定的推送是十分耗费人力物力的,尤其是android这样机型千千万,各种自定义的,要在不同的机型上尽量保证存活;
全家桶的相互唤醒
百度推送作为非手机厂商的推送,在一键清除时候长连接也会很大几率被kill,但是当你启动集成百度推送的其他应用的时候,会把百度推送的长连接唤醒。
譬如kdzl 和 百度地图都集成了百度推送,当kdzl被kill的时候,长连接也会被系统kill,这时候自然无法收到第三方的推送;但是当你打开百度地图的时候,由于kdzl和百度地图实际上在一台手机上共享一个长连接通道,kdzl的推送就也可收到了;
这种全家桶由于集成用户众多,相互唤醒概率很大,从而保证长连接的自启动,进而实现稳定
“第三方推送”向开发者提供的消息推送服务;通过利用云端与客户端之间建立稳定、可靠的长连接来为开发者提供向客户端应用推送实时消息服务。
为了使用第三方推送,往往需要继承对应第三方平台的相关资源文件,这些文件一般被封装为一个SDK,借助SDK,应用的开发者可以通过少量的代码,就可以将推送服务接入的APP应用中,同时可选择使用(1)自己搭建服务器使用对应第三方平台服务API 或者(2)直接使用第三方平台服务控制台的方式进行通知信息的发送。
为了实现推送服务,实质上需要以下4个模块:
App应用(App Client)
自己开发的应用,需要接入push,只需要和pushService打交道提出请求即可
pushService(Push Client)
运行在手机上的推送服务,直接代理App应用与“第三方推送服务器”打交道
用于与服务器建立稳定push通道,上报标签、 LBS信息,通知推送(含富媒体)等
集成SDK后,一般由SDK维护
第三方推送服务器(推送控制台,Push Server)
pushService监听的服务器,该服务器用于发送通知或者信息,同时也会处理设置标签、用户记录等操作
自己的业务服务器(App Server)
实现自己的业务功能,往往需要接入“第三方推送服务器”所设定的一些API,从而可以自动的发送通知,而不再需要人工登录“推送控制台”进行信息推送
请求TMID—-注册请求,委托推送服务去开启推送服务
一般在app启动(activity或者application的onCreate等函数中,不确定,不同平台有不同设定)时,通过调用对应的SDK方法,请求PushService,为当前设备注册并开启推送服务
请求分配TMID—-接收到应用请求,向“第三方服务器”发起注册请求
该方法一般由SDK默认实现和控制,应用开发者只需要在步骤1中调用对应的接口方法后,pushservice会向服务器提出申请
该申请一般是异步申请
TMID应答
该pushService一直运行在手机中,该service在接收到“第三方服务器”给出的应答后,会发送广播,通知对应的接受者,并转发信息
该方法一般由SDK默认实现和控制,
TMID响应
App应用往往需要继承SDK提供“receiver接收器”,并进行注册。
pushService发出的广播,会被所继承的“接收器”中的对应方法接收,并调用
上报TMID
App应用获取到“第三方推送服务器”分配给自己的TMID时,需要将TMID上传给“自己的业务服务器”,从而可以长久保存该ID,并且告知业务服务器通过该TMID,可向对应的业务用户发送通知。
譬如,1:1的聊天信息,需要做定向推送,A发送给B的信息,需要得知B的TMID码
再譬如,非定向信息,覆盖全部用户的热定新闻通知,不需要TMID码,全部推送即可。然而实际上,虽然“业务服务器”没有给出TMID码,其实是第三方服务器对“所有该App的TMID码”进行了推送。实质上和上个例子一样
我们以“1:1的聊天信息”为例,A发送给B信息:
根据TMID推送信息
业务服务器调用“第三方平台”给出的对应API方法,向“推送服务器”发出推送信息的申请,定向推送需要给出B用户的TMID码
根据TMID找到手机并下发
第三方的推送服务器会通过TMID码找到对应的手机,并定向发出推送信息
PushService转发信息
该service一直运行在手机中,监听“ 第三方的推送服务器”的推送,获取到推送信息, 会发送广播,通知对应的接受者,并转发信息
信息响应
App应用往往需要继承SDK提供“receiver接收器”,并进行注册。
pushService发出的广播,会被所继承的“接收器”中的对应方法接收,并调用。
可以在该方法中实现自己的业务操作。
App应用中设定标签,设定别名等操作的过程,与注册流程基本一致
为什么百度排第一呢,后台和客服反映说百度推送一堆问题,为什么还是采用百度第一呢? 原因两点:
一直想找个机会,好好的分析下,这些很浅显的问题:
如果,我们非要从一个外在形式上来评判一个应用是Alive还是Deaded,那么我们可以使用当前进程的Application是否存在作为评判,那么唤醒一个应用其实就是意味着做了初始化Application的行为:
事实上,我们经常说“有些应用会后台自启动”,“后台偷跑流量”,这里所谓的自启动,其实并没有启动任何一个Activity,也就是根本没有构建Task,而只是启动了某个Service或者Receiver,从而势必引发至少一个进程和对应Applicaiton,我们视作Alive状态
好了,现在我们得知Application可以作为应用是唤醒状态Alive 或者 死亡deaded状态 的标准了:
那么,应用被唤醒了,就意味着 应用可能维持的通信长连接已经建立了么?
相信看到这里你基本也清楚了,应用被唤醒 与 应用具体的业务逻辑没有半毛钱关系(长连接的建立也属于业务逻辑分部),如果你想实现某个业务部分,那就必须在被唤醒的模块中 调用或者初始化
如何唤醒被杀死的android app
上篇文章里其实也有提及,唤醒应用的方式
在本处,我们主要关注下,广播这一块。我们知道,第三方推送SDK维护的PushService其实往往是运行在一个单独的进程中的(但是很大可能和app主进程同组,这点注意下,一会会提及) ,这个进程和我们应用app进程是分割开的
我们注册推送SDK的时候,所有的官方文档都会让我静态一个Receiver类,作为监听器。回顾下上文提到的“推送流程 3-4”,PushService维持一个长连接,如果收到了来自PushServer的信息,则跨进程的发送广播给我们的应用,试图唤醒我们的的应用
我们可以大胆猜测,这种inten方式应该如《如何唤醒被杀死的android app》一文中所讲:
我们一般发广播都是局限在app内部,所以通常都是这么发的:
Intent intent = new Intent();
intent.setAction("my.broadcast.test");
sendBroadcast(intent);
或者这么发:
Intent intent = new Intent(context, TestBroadcastReceiver.class);
sendBroadcast(intent);
静态的系统广播,例如:开机广播,用户开屏广播,USB插入和拔出广播等,在app运行期间可以用静态注册的接收器正常接收,但是在app被杀死后就无法收到了,Android系统做了屏蔽,把被杀死的app的系统静态广播都过滤了,所以想让app被杀死后仍然通过静态注册的接收器接收系统广播是做不到的
静态注册的自定义广播也会遇到类似问题,尤其是在定制版的系统中
采用下面这种方式发送广播即使app被杀死后,静态广播也能正常收到:
Intent intent = new Intent();
Context c = null;
try {
c = createPackageContext("com.example.broadcasttest", Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY);
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
// intent.setPackage(getPackageName());
// intent.setComponent(pkgName, className);
// intent.setComponent(pkgNameContext, className);
intent.setClassName(c, "com.example.broadcasttest.TestBroadcastReceiver");
// intent.setClassName("com.example.broadcasttest", "com.example.broadcasttest.TestBroadcastReceiver");
intent.setAction("my.broadcast.test");
intent.setFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
sendBroadcast(intent);
接收放广播的配置(app内的自定义推送Receiver)要把exported设置成true,否则就无法收到app以外的广播发送,只能收到app内部的广播发送
正如文中所说:
以上通过广播唤醒在一些手机上可以正常唤醒app,例如小米3;但是在魅族手机上就没办法唤醒了,需要到安全中心把app的自启动权限开启后才能正常唤醒,由此可见,一些手机厂商可能对于静态广播的接收做了一些优化导致静态广播还是没办法被接收,所以会唤醒失败
即便 intent.setFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES)
相信到这里大家也有所明悟了,为什么推送有时候收不到,我们让客户开启自启动权限,就可以收到了,原因如下:
App的自启动权限,会限制 PushService的自启动权限;
App的自启动权限,会影响自身被唤醒的限制,也就是receiver的正常接收
这也是为什么有时开启app的自启动权限,就能导致第三方推送恢复正常的原因了。 至于app被唤醒后,触发通知栏,但是通知栏信息未显示的原因,我们在后续文章中会讲解。
信息推送是一个动词,我们上边主要关注了“推送”这个动作的实现方式,我们知道了实现这个动作所需要的 四大天王,缺一不可
那么作为这个动作的受体,到底是什么呢?
我们可以简单地认为是一串文本信息,譬如一句话: “你好”
到这里,我们回归下最开始的意图,我们使用第三方推送,是期望能在手机的通知栏弹出一栏信息,根据 执行弹出动作的主体可以分为两种类型:
相对而言,透传信息的自由度更高,我们可以自行处理收到信息后的状态,譬如弹窗,开启引用,或者不弹窗
我们现在在百度和华为平台上使用了透传信息,而小米则使用了通知
其实这和我们之前说的“非典型交互流程”有关
我们知道, pushservice 在收到信息后,会发送广播,试图唤醒我们的应用,然后将信息传递过去
那么唤醒不成功呢? 自然无法将信息传递过去,应用本身不知道信息,处于dead状态,自然就不会弹窗提醒了
然而通知类型则不需要唤醒了,因为弹窗动作直接由pushservice完成啦,管你app是死是活,都能弹出来提示
而在小米手机上,pushservice是一种系统支持的服务,你杀都杀不死,所以我们在一般在小米手机上使用通知方式
很简单呀,一旦使用通知,相当于把全部责任给 pushservice, 如果pushservice越坚挺,我们就都使用通知
然而实际上,只有小米和华为这种手机厂商自拟的pushservice,在自己手机上 pushservice 才有这么牛逼
因为,是小米手机我们就用小米推送
华为的透传信息也是系统级别的,能保持 pushservice 坚挺就足够了,,,我们还是期望多些自由度
肯定不是了,弹窗只是一个动作,很有可能被“通知中心”之类的限制,你弹,它限制,你弹,它限制,自然出不来了
目前作为移动设备的两大巨头 Android 和 iOS 平台, 也分别在系统级别各自集成相应的推送模块(GCM, APNs), 然而由于 Google 服务器在国内所存在的流量限制和Android 系统存在的定制多样性, 使得信息推送在 Android 设备上成为一件不得不大费周折的事情。
保证信息推送的即时性( 信息发送后能在 Minutes 级别被响应收到) 和高到达率( 信息发送后能稳定的到达对应设备) 成为信息推送所需要解决的首要问题。
目前市面上已经有很多关于 Android、 iOS、 Windows Phone 的信息推送平台, 基本上我们可以分为三种类型, 不同的推送平台都拥有着不同的优势, 然而也存在着不同层面上的问题, 无法有效保证信息推送的即时性和高到达率:
由于是从原生 Android 的系统层次进行推送支持, 从而保证了移动端推送运转的畅通和稳定性, 可以有效且即时的响应信息, 但是由于国内对 Google 服务器的流量限制, 导致了服务端的 GCM推送服务的不确定性, 这也导致了国内的终端厂商大部分移除了 GCM 模块。 综合来说, GCM 对于主要面向国外市场的移动设备依然是一种优先考虑的选择, 对于国内设备只能作为信息推送的辅助手段, 无法承担实际信息推送的任务。
国内的终端厂商往往在原生 Android系统的基础上进行了进一步定制, 实现了各自不同的终端系统, 并在各自的系统级别实现了推送服务,但是各自的推送平台仅能在各自相应的系统上作为系统级别的推送服务,例如 MiPush 仅能在小米系统上作为系统级服务存在, 在其他终端系统上退化为普通推送服务。
作为应用级的信息推送平台, 支持跨系统平台的推送服务, 且往往依赖于集成同款应用之间的相互唤醒, 具有一定的稳定性, 例如集成百度推送的移动应用可以被另一款集成该推送的移动应用所唤醒, 但是移动端推送服务被系统回收和 kill 的概率相对较高, 往往需要赋予自启权限,且很难保证推送的稳定性。
如何合理且高效的运用这些推送平台, 以保证信息推送的即时性和高到达率?
为方便理解,我举一个例子,让大家感受下
场景预设:农民伯伯种地,同一土地的酸碱度和水分程度会随时间变化,同时不同的土地含矿物质不同。农民伯伯只能种下唯一一颗种子,种子一分钟就会开会结果,而后落地成种子继续开花结果。不同的土地适合不同的种子,种子在不适合的土地上生长会减产或者死亡。
场景预设的等价描述:不同的系统平台上启动推送服务,同一系统平台适用的推送服务可能会不同,不同的系统平台适用的推送服务也会不同。系统平台上同时运行的有且只能有一个推送服务。 推送服务的选择与开启在每次应用开启时都会触发。
涉及元素:土地 = 终端设备的系统平台、种子 = 推送平台
问题: 如何保证产量 = 如何提供信息的到达率?
土地会随着时间导致酸碱度和水分程度不同—-不同的酸碱度和水分程度适宜不同的种子—–>时间会导致 土地适用于不同的种子 —–> 农民伯伯只能种一个种子—->那么当然期望一个超级种子:它内部包含多个种子;能体现不同性质,而长成苹果 or 梨子等
不同的土地含矿物质不同 — 不同矿物质含量适宜不同的种子 —> 不同的土地 导致 适用于不同的种子 —–>农民伯伯只能种一个种子—->那么当然期望一个超级种子:它内部包含多个种子;第一次在某个土地上落地时,能先抽取下土地的土壤并分析,而后根据矿物质的不同,选择出包含的最优适配的种子作为自身的性质;记录该种子标识,并在下一分钟的再次 落地开花结果中能够直接转化为对应种子,而不是再次抽取土地土壤分析;
不同的手机系统 有 不同的最佳适用推送服务,小米系统适用小米推送,华为系统适用华为推送,vivo适用百度推送 —> 应用一旦被安装在手机上,只能同时运行一款推送服务——>那么当然期望一个超级推送:它内部包含多个推送平台;第一次在某个终端安装并运行应用时,智能的在不同手机平台上从“推送库”中选择不同的推送服务,作为首要选择并缓存记录;同时下次启动应用时,直接按照此时记录的推送平台进行启动;
土地会随着时间导致酸碱度和水分程度不同—-不同的酸碱度和水分程度适宜不同的种子—–>时间会导致 土地适用于不同的种子 —–> 农民伯伯只能种一个种子—->那么当然期望一个超级种子:它内部包含多个种子;能够及时响应自身 减产或死亡的变化而选择出来对应的最佳种子,如果在当前土地上减产或死亡多次,能自动切换为下一个种子类型;
应用在运行的过程中,根据相关运行时状态(例如无响应和失败响应),动态的对推送服务进行切换,同时缓存记录该切换后的平台标识,下次启动应用时,直接按照此时记录的推送平台进行启动;
环节4:农民伯伯觉得自动调整有些问题,他就想种苹果种子 == 命令行控制方式作为自动化切换的人工补偿。
其他:所有土地都适合中土豆,但是土豆在国内不值钱,国外值钱 = 所有android的GCM是最原生的,但是GCM在国内被限制,国外可用
土地在国外则直接中土豆,否则土豆作为催化剂
手机设备支持GCM且不受限制则使用GCM,否则GCM作为辅助通道
思考下“既然各个推送平台各个有缺,为什么我不把全部推送都集成进来”