现在已经有很多家公司提供了免费的云推送服务,常见的包括百度云推送,极光推送等。很多朋友觉得用云推送省力省心,也有一些朋友觉得用别人的推送到时候延迟过大或者有特殊协议需求等问题时,没有源码没办法修改。
不管你想省力用现成的,还是喜欢什么东西都自己控制,我们都可以讨论一下一个推送模块怎么写,实际项目用不用自己写的再说嘛。
我觉得云推送涉及三部分内容,Socket长连接,Android Service管理长连接以及启动我们Service 的Broadcast。
Java中的Socket还是比较简单的,网上也有很多教程。不过实际使用中一个Socket长连接要注意两个问题,即断线重连和心跳包。
断线重连
断线重连概念比较简单,实现起来也很简单,只要Catch两个异常然后重连就可以。两个异常包括a)服务器如果未启动客户端就尝试连接,会抛出java.net.ConnectException: Connection refused: connect
异常。b)服务器宕机之后,客户端再写入时会报java.net.SocketException: Connection reset by peer: socket write error
异常。其实ConnectException和SocketException两个类型都是IOException的子类。只要Catch IOException就可以了,示例如下
private boolean connect() {
try {
clientSocket = new Socket("192.168.56.1", 6666);
// read time out
clientSocket.setSoTimeout(HEARTBEAT_INTERVAL);
reader = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
writer = new BufferedWriter(new OutputStreamWriter(clientSocket.getOutputStream()));
} catch (IOException e) {
Log.e(NETWORK_CONNECTION, e.getMessage());
return false;
}
return true;
}
private boolean reconnect() {
for (int i = 0; i < 10; i++) {
if (connect()) return true;
}
return false;
}
@Override
public void run() {
while (working) {
if (reconnect()) {
String line;
try {
while ((line = reader.readLine()) != null) {
if (line.toUpperCase().equals("HEARTBEAT")) {
Log.d(NETWORK_CONNECTION, "Receive Heartbeat");
} else {
if (messageHandler!=null) {
messageHandler.handle(line);
}
Log.d(BUSINESS, String.format("Receive text: %s", line));
}
}
} catch (IOException e) {
Log.d(NETWORK_CONNECTION, e.toString());
close();
}
} else {
try {
Thread.sleep(RECONNECTION_INTERVAL);
} catch (InterruptedException e) {
Log.e(NETWORK_CONNECTION, e.toString());
close();
working = false;
}
}
}
Log.d(NETWORK_CONNECTION, "User Interrupts the thread");
}
我们只是接受推送,所以我们只需要一个阻塞收的进程就可以了。以上代码也包括了心跳包的内容。
心跳
有些路由或者交换机会定时咔嚓掉一些不活跃的连接,所以我们就需要定时刷一下存在该。另外也可能存在网络环境异常导致连接中断,我们需要通过心跳包及时检测出来并进行重连,这样我们才能实时收到服务器端的推送。详细的心跳的概念可以参见百度百科心跳包。
此处我们实现逻辑很简单,首先设置Socket的度超时为HEABERT_INTERVAL
,服务端应该在HEABERT_INTERVAL
的时间内发送一个包给我们,如果我们的客户端抛出了读超时的异常,就证明服务端和客户端的链路短了。此时我们就会重连10次,重连十次都连不上的话可能是网络异常了,此时我们Sleep一段时间然后继续重试。实现的代码都在上面的run()方法中了。
另外除了自己发送心跳之外,其实Socket.setKeepAlive(boolean)方法,set true之后他会自己定时发送心跳包。但是这个函数有些问题,他的间隔时间是系统控制的,而且默认时间非常长,好像是两个小时。所以一般大家都喜欢实现自己的心跳逻辑,而且可以在心跳中加些料,免得白白发送一个网络请求。
服务端
因为不是讨论服务端的,所以此处省略了服务端的内容。这里提一句,我们的演示代码包括一个简单的服务端,实际开发中服务端可能会考虑Netty开发,而我们此处的代码为了简单直接使用io包的ServerSocket开发。
总结
以上一个简单的连接部分就做好了,不过在移动端还要考虑很多问题,比如耗电,流量等。这些问题都没有详细测试过,有些人觉得不是一个专门做推送的公司很难做好推送部分,估计也是因为这些问题吧。
完整的代码会在该系列完成之后放出,因为实际写的过程中可能还会修改代码。