IM推送之客户端SDK开发(一)

什么是IM

IM就是即时通讯(instant messenger)的简称,市面上比较流行的即时通讯APP有微信,易信,阿里旺旺,陌陌等,在Android平台上,即时通讯主要都是通过TCP来实现的

TCP的鲜明特点

  • TCP连接是可靠连接,保证消息必达
  • TCP链路是双工通道,所谓双工就是链路两端都可以同时进行数据发送与接收并不受影响,也就是说TCP链路在写数据的同时可以进行读数据
  • TCP数据传输都是按照顺序到达的,假如有三个数据包A,B,C按顺序发送出去,在接收端也一定是先收到A,再收到B,最后收到C,如果B没有收到,TCP协议底层实现会进行重试机制来保证数据包必达,于此同时C数据包也不会收到,知道收到了B之后才会继续收到C
  • TCP数据传输存在粘包问题,粘包问题就是假设数据包A,B,C各有10B的大小,发送端按顺序发送A,B,C,在接收端就有可能是第一次收到15B的数据,第二次再收到15B的数据,这时候就出现粘包现象了,B数据包的前5B数据紧跟着A数据包一起被接收,B数据包的后5B数据紧跟着C数据包一起被接收,为了解决粘包问题,TCP数据传输一般都要执行数据传输协议
  • 相对于UDP来说,TCP连接是比较耗费资源的,因为TCP连接必须保证一直连着,所以并且为了保证连接的有效性要间隔时间去发送心跳包,所以无论从内存占用还是手机耗电,开发代码来看都比UDP开发要复杂

TCP保活(维护一条有效的TCP)

  • TCP保活分为两方面,一是TCP连接保活,另一个是Android平台上Service的保活

TCP连接保活

  • TCP连接的保活就是维持稳定心跳,各大运营商网络环境都有一个NAT超时,NAT就是网络地址转换,在外网路由器里面有一张把本地IP转换成外网IP的对应表,如果一条TCP连接长时间没有数据传输,那么这条TCP的IP对应会从表中移除,这就导致从本地到外网路由器的链路是通的,但是从外网路由器到这条TCP要连接的目的地的链路不通了,从而就导致这条TCP连接是无效连接,无法成功进行数据传输和接收,但是这时候程序的TCP连接是感知不到的,对于程序来说可以正常的写数据,一切正常,不会抛出任何异常,但是实际上数据是无法传输到目的地的,所以为了保证这条TCP连接的有效性,就要间隔一定的时间给这条TCP发送一些数据包,保证TCP连接一直有效,这些数据包就叫做心跳包,心跳包的特点是数据包的大小尽量做到很小,这样省流量,而且在网络环境不好的条件下,小的数据包传输的成功率也比较高,心=同时心跳包也要有回馈,如果在指定时间内收到回馈就说明这条TCP连接正常可用,如果没收到回馈那就说明这条TCP连接可能是不可用的,这时候就要断开当前的TCP连接重新创建一条新的TCP连接
  • 对于Android来说,维护一条TCP连接无疑是有一个Service来维护,由于Android手机上,程序退到后台后有可能被系统杀死,所以要保证TCP连接不断首先要保证Service常驻后台运行,关于Service不被杀死,可以参考我这篇文章如何降低Service被杀死的概率,但是其实,经过长期的测试发现,在5.0以下的android系统上,可以通过JNI层来做到Service常驻,具体原理就是从主进程中fork一个子进程,这个子进程并不是java创建出来的,而是native层创建出来的,所以5.0一下手机就算强杀也只会杀死java进程而不会杀死native的进程,这时候可以通过在native进程中做轮询通过AM命令来定时启动Android的Service,但是在5.0以上的Android系统就失效了,因为5.0以上的Android系统强杀会杀死当前APP进程组,也就是跟APP进程有关的进程都会被杀死,因此native层的进程也就无法存活,现在市面上主流的Android系统大部分是5.0以上的,所以我们就直接抛弃了native层保活的做法,另一方面之所以抛弃这个所发是native层的jni实现在各大厂商自定制的Android系统上有时候会存在兼容问题导致无法正常运行,或者导致耗电增加,其实关于Service保活已经在文章中讲的比较清楚了,但是还有一些要补充的,所以下面就再说一下当前市面上比较主流的保活手段

Android平台上的Service保活

  • 把维护TCP连接的进程抽离成一个单独的push进程,这个进程只负责维护TCP连接的有效性,保证这个进程内存占用很小,降低push进程在Android内存不足的时候被回收的概率
  • 提升进程的优先级,具体方法可以参考如何降低Service被杀死的概率
  • 集成指定的官方推送,国内的手机厂商自定制的Android系统如果要杀死你的进程,还真的无能为力,所以针对不同的手机厂商做适配,我们当前是这么做的:针对华为手机,集成华为官方提供了华为推送;针对小米手机,集成小米官方提供的小米推送;因为官方推送在自家手机上,维护TCP的Service所在的push进程属于系统集成,是不会被手机回收的,所以我们后台检测当我们推送的连接不存在的时候,如果有华为推送就通过华为推送来传输数据,有小米推送就通过小米推送来传输数据,这样做可以大大的提升推送消息在华为,小米手机平台上的消息送达率,实际上根据外面用户的反馈,我们的推送消息送达率在华为手机平台上最低,主要原因就是手机锁屏后push进程就被杀死了
  • TCP多路复用技术,简单来说,一台手机上安装的多款APP都采用友盟推送,那么这么多APP会有一个宿主APP来专门维护唯一一条TCP长连接,其他的APP消息就由宿主APP来进行路由消息,APP之间的信息传递可以通过广播,service,aidl等方式;换句话说就是APP互相唤醒,互相唤醒的方式就是通过Service,Broadcast,Activity,一般是通过Service和Broadcast;这其实是一个不错的方式,多个APP共享一条TCP连接,降低耗电,降低内存占用,谷歌的GCM估计就是这么干的,但是互相唤醒需要有一定亮的用户来支持,如果一些APP基本没什么用户使用,那么也就不会去点开APP,当然无法触发唤醒,反之,如UC浏览器,基本每个人的Android手机都有安装,每天可能隔一会儿就会点开UC浏览器APP使用,那么频繁的点开APP就会频繁的触发唤醒,即便是宿主APP被杀死了,只要集成友盟推送中的任何一个APP有被点开,就会把宿主APP拉起,这样当然大大提升了push进程的后台存活率。遗憾的是:互相唤醒的效果在一些国产定制的android系统上运行效果并不理想,例如华为手机就是默认禁止APP互相唤醒(禁止广播发送,禁用AIDL),小米,魅族也相继推出在休眠状态下禁止APP互相唤醒,但是这种做法是可取的,市面上的知名第三方推送,如友盟推送,极光推送个推等大部分都支持TCP多路复用,这种做法也是最有效的,在一定程度上还是可以增加Service后台存活的几率,针对互相唤醒被禁用的还可以再做进一步优化,这个等下在后面来具体说明
  • 后台播放音乐,请原谅我曾经用这么流氓的做法确实做到了进程在锁屏后不被杀死,在华为6.0,魅族5.0上面亲测,其实就是参照了一些音乐播放软件在锁屏后照样可以播放,但是这样会增加耗电,而且其实只是做到了进程保活,并没有做到TCP连接保活,有些手机在锁屏后就会对指定的app采取断网操作,遇到这种情况,进程或者有个屁用,唯一有改善的是在手机屏幕解锁后,app重新获取联网权限,这时候因为进程没死所以TCP连接就是重新连接,但是如果进程死了,手机解锁后TCP连接是无法重新连接的,可参考这篇文章后台播放音乐保证进程不被杀死

互相唤醒被禁用优化

  • 经过测试发现,华为,魅族手机的禁止互相唤醒是有条件的,如果APP进程已经被杀死了,那么无论手机是否休眠都不能唤醒APP进程,如果APP进程没有被杀死,依然在运行,但是此时广播和Service也被禁用导致宿主APP无法把收到的消息路由给其他没被杀死的APP,那么针对APP进程还没有被杀死的情况可以采用本地UDP或者本地TCP来实现数据的传递,这样就能解决当APP进程没死,但是广播和Service却被禁用倒是消息无法路由的情况,这里可以做双层保证,因为发送广播没有返回值,不知道广播发送是否成功,因此才会service通信和本地UDP通信的方式来配合,当start service失败的时候就采用UDP来传输数据,那么问题来了,UDP有可能会丢包,这个我觉得可以忽略了,本地UDP数据传递就只是在本地自己传输,根本不用ping到外网去,所以丢包的概率极小,UDP比TCP省资源所以采用UDP而不用TCP

Android平台上Service保活的误区

  • Native层的守护进程,刚上面提到了,在5.0系统以上都无效
  • 双进程守护,也称作双Service守护,就是在Java层开两个Service,分别运行在不同的进程中,其中一个是push进程负责维护TCP连接,另一个是守护进程,专门负责轮询检测push进程的service是否存在,如果发现不存在就把它拉起,这里如果采用轮询可能会有点耗电,而且在手机休眠后所有java实现的定时器都会被挂起,如果仍然要在休眠后继续轮询只能采用闹钟唤醒,一提到闹钟唤醒,随之而来的就是耗电增加,所以可以考虑采用在两个进程的Service之间建立一条通道,例如建立一条本地的TCP连接,如果其中一端被杀掉,另一端的TCP连接就能立即感知到异常报错,这时候就可以把被杀死的Service启动起来,这条本地TCP连接不需要任何心跳,只要建立然后就一直连着就行了,并不会消耗太多的电量;然并卵,都说了在5.0系统上面杀死的是APP的进程组了,所以这两个进程会一起被系统杀死,只有存在这种情况:系统先杀死push进程,在还没杀死守护进程的时候守护进程就检测到push进程被杀死了,这时候在守护进程被杀死的前一霎那把push进程又拉起来了,但是这种概率实在是太小了,我测了很多次,依然没有发现push进程能顺利保活
  • 监听静态广播启动程序,例如监听开机广播,网络切换广播,USB插拔广播等等,也是扯淡,要明白,5.0的Android是不允许唤醒已经被杀死的APP进程,也就是处于STOPPED状态的APP是收不到广播的
  • 最新的JobScheduler,在android原生的系统上面确实有用,在app被杀死后,AlarmManager定时任务被清除但是JobScheduler的定时任务不会被清除,还会唤醒程序,然后我试了下,在魅族手机上依然没有用,华为手机就不试了,估计也一样,国内自定制的Android系统估计在这方面做了修改
  • 谷歌服务GCM,别提了,国内手机的Android系统基本都是把GCM模块直接从手机上移除,根本用不了GCM,不过在国外手机上并且是国外的网络环境上,是可以采用GCM来实现推送的
  • 其实明白一点,从系统层把进程给你干掉,除了一些黑科技,利用系统漏洞等来实现进程保活,基本不太可能通过Android SDK提供的常规接口,广播等方式来实现保活,在下不才,目前为止没有发现有效的保活黑科技,也不曾找到网上的有效黑科技,网上搜索的,经过验证没有一个是可以的!

IM数据协议

  • XMPP是比较完善的XML格式的数据协议,其优点是可拓展性强,但是协议冗余,经过转换后的数据亮很大,并不适合移动端的数据传输
  • MQTT协议是一种轻量级的二进制协议,而且已经有一些针对MQTT协议实现的java版本的jar包,用起来很方便,MQTT协议的数据量很小,也已经有运用在一些移动端的APP中了,是可选择的IM数据协议方案
  • 自定制的私有二进制协议,很多第三方推送公司都是实现了自己定制的二进制数据协议,二进制协议的特点就是数据量小,适合移动端的数据传输,数据编解码快,基本可以忽略数据编解码的性能瓶颈,省电,但是有一点不好的是可拓展性较差,如果增加新的协议包需要修改协议实现代码
  • 数据协议的编码方式,LV编码,TLV编码是一个比较流行的编码方式,非常适合用于二进制协议的编码,具体详解:TLV编码
  • 我自己用java实现了TLV数据编码的方式,但是和网上的TLV数据编码有一点微小的不同,代码都在github上:TLV编解码Java实现,具体不同有两点:1.网上的是第6~7位:表示TLV的类型,00表示TLV描述的是基本数据类型(Primitive Frame, int,string,long…),01表示用户自定义类型(Private Frame,常用于描述协议中的消息);而我们自己设计的是第6~7位,00,01和网上的保持一致,网上的TLV的第七位一直是0,没有用起来,而我们就规定第七位为1就表示有后续字节,为0就没有后续字节,所谓的后续字节的意思是TLV最简单的最小的时候是1个字节8位,后续字节就是当TLV中V长度较大的时候一个字节表示不下的时候,这时候整层TLV长度就会超出一个字节的大小,超出的部分就是属于后续字节

猜你喜欢

转载自blog.csdn.net/lhd201006/article/details/52818068