对于移动APP来说,IM功能正变得越来越重要,它能够创建起人与人之间的连接。社交类产品中,用户与用户之间的沟通可以产生出更好的用户粘性。
在复杂的 Android 生态环境下,多种因素都会造成消息推送不能及时达到客户端。另外,不稳定的移动网络也给数据传输的速率和可靠性增加了障碍。
本文详解了 网易云信IM SDK 在应对弱网环境、移动端硬件限制以及Android复杂的生态现状时的探索与心得.如何实现不影响用户体验的后台保活,改善的长连接加推送组合方案,以及在弱网环境大数据传输的优化实践。
带着思考阅读:
-
什么是IM
-
IM SDK
如何实现不影响用户体验的后台保活
-
如何做长连接加推送组合方案
- 如何在弱网环境下优化大数据传输
IM的定义
IM由两个字组成:Instant,Messaging。
即时性要求有新消息时能够立即收到,如果程序在后台,则要能立即收到推送通知。
通信则要求稳定可靠,系统不宕机,程序不崩溃,安全,传递消息时不会被拦截监听,消息不丢,顺序不乱,不重复,如果包含音视频聊天,则要求延迟低,流畅不卡顿。
要真正做出一套稳定可靠的商用级 IM系统 ,挑战非常大。
第一个问题是消息推送。iOS有 APNS做推送,相当稳定。Android本身也有GCM可以用,但是在国内有“墙”,直接就把GCM等等google的服务全部挡在外面。为了实现即时稳定的消息推送,从易信时代开始,网易就开始研究,随着时间的推移,困难和方法也在不停的变化。
对于IM,当APP退到后台,是必须还能够收到新消息提醒的,没有GCM,怎么办?在最初,唯一能做的,就是后台运行了。这几乎是接收推送的唯一途径,就算是到现在,也是最主要的途径。Android从设计上,就是支持真后台运行的,后台运行的特性也是Android现在能如此成功的原因之一,但另一面,Android长久以来一直摆脱不了的卡顿,耗电等坏名声,后台运行也拖不了干系。因此,系统对于后台运行也不会放任自流。
APP在后台运行所面对的四大障碍
第一个障碍是Android的Low Memory Killer机制。手机的内存有限,当后台运行的进程越来越多,内存剩余量也就随之减少。当有一个新的APP想要启动,如果内存不够,LMK机制就会启动,从正在运行的进程中挑选一个清理掉,释放出空间,然后新的APP就可以运行了。
LMK有两个尺度去评判。一个是进程优先级,优先级越低,被清理的可能性越大,另一个是内存占用,占的内存越多,被清理的权重自然也越大。
因为LMK机制的存在,虽然APP允许在后台运行,但同样也面临随时被清理的风险。因此,网易需要在被清理后及时的重新启动。
第二个障碍是alarm,闹钟,有循环闹钟和一次性闹钟两种,在闹钟触发后启动对应的组件。
第三个障碍是在Manifest文件中静态注册的Receiver,通过监听各种系统事件,比如开机,网络变化,mount/unmounts等,在这些事件发生时启动组件,因为这种方式会造成在这些事件发生时系统容易卡顿,在7.0里面,Android增加了限制。
第四个障碍是JobScheduler,这是在5.0里面新增的,允许APP在特定事件发生时做一些动作,比如充电,切换到wifi等。
虽说无论怎么做,APP终究免不了一死,但通过对照LMK的评判准则,还是可以降低APP被清理的概率的。第一个就是降低进程的内存占用。如果采用单进程的模式,由于进程中包含了UI,Webview,各种图片缓存等内容,内存必然会居高不下,降不下来。IM软件一般都会采用双进程甚至多进程的策略,将push进程独立出来,在push进程里只处理网络连接和push业务,不参与任何其他业务逻辑,更不包含任何UI。
以下是网易云信Android SDK的架构,按照分层的结构模式设计。最底下青色的一层是push层,他就是作为一个独立进程运行的。他只负责处理网络长连接的相关工作,比如安全加密,心跳,鉴权,封包解包等工作,所有业务逻辑都交给UI进程的服务模块去做。来看一下云信demo的进程内存占用情况。上面一个是主进程,看第四列PSS的数据,内存占用是50M左右,下面一个是push进程,内存占用只有10M左右。当处于后台时,push进程被清理概率比UI主进程低很多。
降低被清理概率的第二个手段是提升进程优先级。先看这个例子,这是绿色守护的一个截图,最上面是“暂不自动休眠”,因为这里列出的两个APP的状态都是工作中,对应的进程优先级是“可视进程”。但这两个APP并没有提供桌面小部门在运行,也没有指示前台服务的常驻通知栏提醒,事实上,他们就只是在后台运而已。通常进程退到后台后,其进程优先级类型就变成了较低的后台进程,而不是这样的“可视进程”,他们是通过什么方法来提升优先级,降低被清理概率呢?
Android在设计前台服务上有一个漏洞,通过两个服务配合就能创建一个隐形的前台服务。这里有两个已经启动的service: A和B。先在A中调用startForeground,提供一个NOTIFY_ID, 然后A就变成前台服务了,同时有了一个ID为NOTIFY_ID的常驻通知栏提醒,然后网易在B中也调用startForeground,提供相同的NOTIFY_ID, B也变成了前台服务,因为两个通知ID相同,因此这一次就不会创建新的通知栏提醒了。然后再在A中调用stopForeground,A的前台属性被取消,同时,常驻通知栏提醒也会被移除,但是,service B并不会受到任何影响,还是前台服务,这是再把A停掉,进程就只剩下前台服务B了,进程也变成了前台进程,但用户不会有任何感知。