推送定义:在任何时间地点服务端向客户端推送一条消息,如果客户端在线或者下次上线,就能接收到该消息。
通常想到的实现方式是:轮询、tcp长连,其目的都是让服务端和客户端之间时刻保持在线状态
对于客户端而言,
轮询:无非是写个线程按某种配置的时间间隔无限循环去请求服务端是否有新的消息,当有新的消息,就提醒给用户
tcp长连:与服务端建立tcp长连,这样服务端就可以直接给客户端发送消息了,当前市面上基本上是以此种方式居多
上面两种方式都是比较消耗资源的,而这里我们使用的是另外的一种方式来实现的,如MobPush
udp无连接:其实现基本原理为,客户端创建socket并向服务端发送udp包,服务端接收到请求连接的udp包之后,将客户端id与ip和端口号进行绑定,当要向某个客户端id发送消息时,找到其对应的ip和端口号,然后将消息组装成udp包发送即可,其大致流程如下:
而对于客户端需要解决的如下几个问题:
如何维护客户端id与路由之间的绑定关系;
如何延长客户端的在线状态(app保活)
客户端性能考虑
下面将针对这几点进行逐步介绍
维护客户端与路由的绑定关系
这里我们需要了解一下NAT,所谓NAT就是,在局域网内部网络中使用内部地址,而当内部节点要与外部网络进行通讯时,就在网关处,将内部地址替换成公用地址,从而在外部公网上正常使用。
所以当发送udp包到服务器时,服务器拿到的ip和端口其实是客户端在路由上映射的ip和端口,所以我们需要维护路由上的映射表,这时就需要定期发送心跳包,以保证路由上的映射关系不会被清除掉。
1. 维护心跳包
主要作用是防止NAT超时, 和探测连接是否断开,并根据实际情况进行重连操作,其流程如下:
网络监测
当网络切换和变化时,会导致映射关系失效,所以我们需要做相应的监测和重连监听网络变化,当网络类型变化或者断开后重新连接上时,进行重连
定期监测ip地址变化,如果监测到ip地址有变化时,则进行重连
APP保活
app保活是一个老生常谈的话题,经过广大开发者多年累积与筛选,互联网上相关文章层出不穷,目前看来不算什么硬梗,大多都按套路出行,这里也套路套路
当应用退到后台时,为了确保推送通道能够正常使用而不被系统回收,通常会做一些进程保活和拉活的策略,大体分为以下几类:
1. 利用系统Service机制拉活
将 Service 设置为 START_STICKY,利用系统机制在 Service 挂掉后自动拉活
如下两种情况无法拉活:
1.Service 第一次被异常杀死后会在5秒内重启,第二次被杀死会在10秒内重启,第三次会在20秒内重启,一旦在短时间内 Service 被杀死达到5次,则系统不再拉起。
2.进程被取得 Root 权限的管理工具或系统工具通过 forestop 停止掉,无法重启。
经测试,在绝大多数手机任务进程中,手动杀掉进程后,是不会自动重启的(符合情况2)
2. 设置进程优先级
当进程退到后台后,系统在回收资源时,会根据进程优先级,进行资源回收,优先级越高越晚被回收,所以尽可能地提高service进程的优先级,可以在一定程度上保障其在后台时不被系统回收
进程按照重要性分为如下5类:
1.前台进程(Foreground process)
2.可见进程(Visible process)
3.服务进程(Service process)
4.后台进程(Background process)
5.空进程(Empty process)
一般的后台进程进程属于第4类,我们可以通过setForeground将service提升到2,但是这种方案必须与一条可见的通知绑定在一起,而这种体验显然不能被用户接受
当然我们可以通过new Notification()的方式设置一个空的通知,与之绑定,但只在4.3以下版本才有效,如下:
if (Build.VERSION.SDK_INT < 18) { service.startForeground(1001, new Notification());//API < 18 ,此方法能有效隐藏Notification上的图标}
神奇的开发者们发现了一种通过多实现一个TmpService,在MainService和TmpService两个service中同时发送具有相同 ID 的 Notification,然后干掉TmpService,这时Notification会自动消失,而MainService的优先级已经被提升到2了,从而达到了目的。缺陷是:需要开发者在manifest多配置一个service,可能在不久的将来也会被官方更新掉,不建议采纳。
3. 利用系统广播拉活
该方式是通过AndroidManifest.xml 注册一些特定的系统广播方式,来拉活进程,但是这种方式在高版本中已经被官方封掉了,所以也成了一堆没必要的配置
当然我们可以在代码中添加了相关的系统广播注册、同时在监听进程死掉时发送的广播,测试在某些低版本的手机上有效
IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(Intent.ACTION_USER_PRESENT); intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); intentFilter.addAction(Intent.ACTION_BOOT_COMPLETED); intentFilter.addAction(Intent.ACTION_MEDIA_MOUNTED); intentFilter.addAction(Intent.ACTION_POWER_CONNECTED); intentFilter.addAction(Intent.ACTION_POWER_DISCONNECTED);
4. 使用AlarmManager
使用AlarmManager定时发送心跳、定时检查ip变化
但是经测试当系统休眠时,AlarmManager也停止了工作,且在不同sdk版本上需要采用不同的set方式,如下:
AlarmManager am = (AlarmManager) getSystemService(Context.ALARM_SERVICE);if (pingPendingIntent != null) { am.cancel(pingPendingIntent); } pingPendingIntent = PendingIntent.getBroadcast(MobSDK.getContext(), 0, new Intent(ALARM_ACTION_PING), PendingIntent.FLAG_UPDATE_CURRENT);final long nextTime = SystemClock.elapsedRealtime() + interval * 1000L;if (Build.VERSION.SDK_INT >= 23) { am.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP, nextTime, pingPendingIntent); } else if (Build.VERSION.SDK_INT >= 19) { am.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, nextTime, pingPendingIntent); } else { am.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, nextTime, pingPendingIntent); }
进程间相互拉活
当某台手机上有多个应用都在使用sdk时,可根据用户在后台配置授权后,选择性的进行相互之间的拉活利用native进程拉活
网络中流传的一种利用 Linux 中的 fork 机制创建 Native 进程,在 Native 进程中监控主进程的存活,当主进程挂掉后,在 Native 进程中立即对主进程进行拉活。
在 Android 中所有进程和系统组件的生命周期受 ActivityManagerService 的统一管理。而且,通过 Linux 的 fork 机制创建的进程为纯 Linux 进程,其生命周期不受 Android 的管理。
这种方案在网上流传已久,听说在5.0以上版本也不成立,且需要额外添加本地代码编译so,无形的添加了app体积,不采纳
JobScheduler和账号同步机制拉活
这种两种方式同样需要在AndroidManifest.xml中注册相关配置和权限,版本限制,效果一般
8. 将应用加入厂商或管理软件白名单
第三方push通道接入:
GSM:国内不支持
小米推送、华为推送
性能考虑
APP性能也是老生常谈的话题,总结其出发点和最终的目的都是为了减少用户流量、内存占用、电量消耗等等方面的优化,以达到省电省流量且界面流畅的终极目标。
在开发时,大致可从如下几个方面思考和稍加注意:
减少网络请求次数,缩小网络中传输数据的体积,像推送这种主动的操作,可通过自定义数据传输协议来控制流量的消耗
控制唤醒屏幕,避免开启没必要的线程,合理释放资源,减少IO操作,避免使用广播机制,减少cup占用时间等等方面来控制内存和电量的消耗
链接描述