客户端的主要包说明
org.androidpn.client包下的文件
- public class Constants { //包含静态数据
- public class InvalidFormatException extends RuntimeException { // 运行时所产生的错误处理
- public class LogUtil { //信息输出
- public class NotificationService extends Service { //后台运行并响应来自服务器的事件推送通知服务
- public final class ServiceManager { //管理的notificatin服务,加载配置
- public class XmppManager { // 管理客户端和服务器之间的连接
- public class ConnectivityReceiver extends BroadcastReceiver { // 网络变化广播接收器
- public class PersistentConnectionListener implements ConnectionListener { //连接关闭和重新连接事件的监听器 ,在XMPPManager中注册。
- public class PhoneStateChangeListener extends PhoneStateListener { //监听手机状态
- public class ReconnectionThread extends Thread { // 用于断线重连
- public class NotificationIQ extends IQ { // 来自服务器的推送消息
- public class NotificationPacketListener implements PacketListener {// NotificationIQ通知传入的数据包的接收,在XMPPManager中注册
- public class NotificationIQProvider implements IQProvider { //提过给XmppConnection的NotificationIQ 解析方式,在XMPPManager中注册
- public final class NotificationReceiver extends BroadcastReceiver { //推送通知消息的广播接收器
- public class Notifier { //通知用户,这里正式对消息进行处理,如发送本地通知等。
- public class NotificationSettingsActivity extends PreferenceActivity { // 设置信息
- public class NotificationDetailsActivity extends Activity { // 活动通知详细视图显示(未使用)
客户端使用的了smack.jar
做了一些修改,整个jar包解压了。smack.jar中 PacketReader解析消息,PacketWriter发消息,XMPPConnection负责连接管理。
发送消息:
XmppManager -> XmppConnection-> PacketWriter
接收消息:
XmppConnection -> packetReader -> NotificationPacketListener -> NotificationReceiver -> Notifier
服务器端的主要包说明
原项目有4+5+15,共计24个包,另有一个default包,里面仅有一段控制台测试代码。
3.3.3.1 web相关的4个包
1. org.androidpn.server.console.api 接口
2. org.androidpn.server.console.controller 控制器
3. org.androidpn.server.console.vo viewModel
4. org.androidpn.server.container 未用到
3.3.3.2 数据库访问相关的5个包
1.org.androidpn.server.dao
2.org.androidpn.server.dao.hibernate
3.org.androidpn.server.model
4.org.androidpn.server.service
5.org.androidpn.server.service.impl
3.3.3.3 推送相关的15个包
1. org.androidpn.server.util
包中的类用来加载resources中的配置文件,在配置文件中可指定监听端口和ssl证书目录等属性。
- public class Config { //读取配置信息
- public class ConfigManager { //应用程序配置信息管理
2. org.androidpn.server.xmpp
主要是包含有入口类XmppServer,这个类用来启动和停止server程序,
包里面定义了一些异常类型。
- public class XmppServer { //服务启动
3. org.androidpn.server.xmpp.auth
认证相关的一些类。
- public class AuthToken { //封装用户认证token 名字、域名
- public class AuthManager { //用户认证管理
4.org.androidpn.server.xmpp.codec
XMPP协议的XML文件解析包,通过这个包来进行xmpp协议数据传输的编码和解码。
- public class XmppCodecFactory implements ProtocolCodecFactory { //编码/解码器,用于解析XMPP消息。
- public class XmppDecoder extends CumulativeProtocolDecoder { //协议解码 (这里增加了心跳回送)
- public class XmppEncoder implements ProtocolEncoder { //协议编码 ,androidpn把加密放在了Connection 类中
5.org.androidpn.server.xmpp.handler
对消息的处理,我们可以针对不同的消息类型定义自己的handler。
- public class IQAuthHandler extends IQHandler {//APP连接时认证身份
- public abstract class IQHandler { //抽象类,处理客户端发来的IQ消息
- public class IQRegisterHandler extends IQHandler { //APP连接时处理用户注册
- public class IQRosterHandler extends IQHandler { //未使用,拉取好友列表
- public class PresenceUpdateHandler { //处理操作(APP连接时登陆成功后更新在线状态)
6.org.androidpn.server.xmpp.net
负责维护与client之间的持久连接,并实现了一些传输方式供发送xmpp消息时使用。
- public class Connection { //一个XMPP连接服务器的实例 所有与客户端的消息连接都通过本类来处理
- public interface ConnectionCloseListener { // 连接关闭监听
- public class IoBufferWriter extends Writer { //输入输出处理
- public class StanzaHandler { //处理传入的XML (增加了一个方法getClientSession)
- public class XmppIoHandler implements IoHandler { //创建新的会话,销毁会话,并提供接收XML
7.org.androidpn.server.xmpp.presence
只包含PresenceManager类,用来维护client的在线状态。
- public class PresenceManager { //它会调用SessionManager,用于判断某用户是否在线或获取用户当前的Presence。
8.org.androidpn.server.xmpp.push
只NotificationManager类包含有向client发送消息的接口。
- public class NotificationManager { // 消息推送入口
9.org.androidpn.server.xmpp.router
负责将收到的信息包发送到相应的handler进行处理,是一个路由包。
- public class IQRouter { //路由IQ消息到其相应的处理
- public class MessageRouter { //路由Message消息到其相应的处理程序
- public class PacketDeliverer { //将数据包发送到连接会话
- public class PacketRouter { //处理传入的数据包,并将它们路由到其相应的处理程序
- public class PresenceRouter { //路由Presence消息到其相应的处理程序
10.org.androidpn.server.xmpp.session
定义了用来表示持久链接的session,每个session包含一条连接的状态信息。
- public class ClientSession extends Session { //会话
- public abstract class Session { //会话抽象类
- public class SessionManager { //会话管理
11.org.androidpn.server.xmpp.ssl
对连接进行ssl认证的工具包。
- public class SSLConfig { //ssl配置
- public class SSLKeyManagerFactory { //负责产生ssl管理 实例
- public class SSLTrustManagerFactory { //SSL信任管理器工厂类
12.org.dom4j.io
输出输入流工具包
- public class XMPPPacketReader { // xml解析器
13.org.jivesoftware.openfire.net
- public class MXParser extends org.xmlpull.mxp1.MXParser { //解析器验证文件
14.org.jivesoftware.openfire.nio
- public class XMLLightweightParser { //一个轻量级的XML解析器
15.org.jivesoftware.util
- public class PropertyEventDispatcher { //事件调配
- public interface PropertyEventListener { //事件监听
- public class XMLWriter extends XMLFilterImpl implements LexicalHandler { //xml编辑
推送实现
连接的建立和维持
服务器端
webRoot/WEB-INF/dispatcher-servlet.xml 和resources/spring-config.xml 内的bean在应用启动时会自动实例化,而它们中的许多在实例化时都会直接或间接地调用XmppServer.getInstance()方法。从而启动服务。
resources/spring-config.xml 中有相应的mina框架配置信息:
服务器和客户端之间的消息在org.androidpn.server.xmpp.net.XmppIoHandler中处理:
sessionCreated -> sessionOpened -> messageReceived - > sessionIdle -> sessionClosed
客户端
- 提供了三个监听器对手机网络状态或连接状态进行监听,在状态变化时启动断线或重连操作。
- 重连的时间间隔(秒)
private int waiting() {
//waiting 在每次重连会自增1
if (waiting > 20) {
return 600;
}
if (waiting > 13) {
return 300;
}
return waiting <= 7 ? 10 : 60;
} - 在app操作中的任何一次页面切换都会判断服务的状态。
//如果服务未启动,则启动服务
private void startService(){
if(NotificationService.getNotificationService() == null){
// Start the service
ServiceManager serviceManager = new ServiceManager(this);
serviceManager.setNotificationIcon(R.drawable.ic_launcher);
serviceManager.startService();
}
} - ServiceManager实例化时会读取配置信息。
- ServiceManager的startService =>XmppManager的connect方法=>XMPPConnection的connect方法
- 连接时,执行登陆操作,在登陆前先判别是否需要注册,注册前先判别是否有连接,详细连接过程见XmppManager类。这个登陆类似于QQ一样,它先通过认证建立连接后,然后会要求使用用户名和密码来登陆,这组用户名和密码是随机生成的,每台设备对应唯一一组。
用户会话状态的维持
- mina框架提供了IoSession对象,IoSession维护着连接状态,并提供读写流,可以将信息发送给用户。
- IoSession中会创建Connection、Stanzahandler两个对象的实例,并持有它们。
- Stanzahandler中又创建了androidPN新增的ClientSession对象的实例,ClientSession对象又持有Connection对象。
- Connection对象中持有这两个Session成员: IoSession 和ClientSession 。
- SessionManager对象维护着一个Map,它存有设备标识(apn_user表的username)与用户会话ClientSession的映射关系,并提供了一个单例。
消息推送
安卓消息服务器端的推送流程
- NotificationManager的方法被调用,依据参数得到一组设备标识。
- 通过设备标识在SessionManager中检索可以匹配到ClientSession。
- 定义自己的xmpp消息格式并拼接出xml消息体。
- 通过ClientSession中的deliver方法,调用Connection中的deliver方法向用户发送消息。
要定义和组装自己的xmpp消息,将适当的信息发送给客户端并便于客户端解析,需要修改的就是第3步,实例化一个IQ对象,放入特定格式的消息体,然后直接发送就可以了。
发送
ClientSession session = sessionManager.getSession(username);
if (session != null&&session.getPresence().isAvailable()) {
iq.setTo(session.getAddress());
session.deliver(iq);
}
创建element的时候,传入的namespace要和客户端解析使用的namespace相匹配。
安卓消息服务器端的消息接收和处理
- 收到用户传输的数据时,首先经过IoFilter,完成消息的接收、编/解码等操作后。
- 解码操作由org.androidpn.server.xmpp.codec.XmppDecode来完成,解析完成后为每一个解码后的消息对象调用ProtocolDecoderOutput接口的write(Object)方法,将消息输出。
- 之后就开始消息的处理工作,以XmppIoHandler 的messageReceived方法为起点,后续StanzaHandler -> PacketRouter,然后依据消息体的类型选择对应的路由器,如处理IQ的IQRouter,Router再根据packet的namespace,选择对应的handler。
- handler进行处理。
router和handler类在androidpn中都有例子可以参考。开发中只要根据client发送消息的格式,定义自己的router和handler类,然后在PacketRouter中注册router,在IQRouter中注册handler即可。
客户端消息的接收
- org.jivesoftware.smack.PacketReader负责消息的接收。
- NotificationIQ、NotificationIQProvider、NotificationPacketListener三个类负责对收到的Notification格式的消息进行解析和处理。
NotificationIQ中定义消息实体,
NotificationIQProvider提供NotificationIQ的解析方法,
NotificationPacketListener中对执行具体处理操作。
它们需要在XmppManager中进行注册,代码如下:
1. 连接成功后注册消息解析器:
2. 登陆成功后添加监听器:
要解析服务器推送的某IQ,需要实现*IQ和*IQProvider两个类,然后要在XmppManager中注册,并在NotificationPacketListener提供相应的处理操作。
客户端消息的发送
发送消息:xmppManager.getConnection().sendPacket(*IQ);
具体执行数据传输的是org.jivesoftware.smack.PacketWriter。
要发送某IQ到服务器,只要实现*IQ一个类就可以了,然后调用上面的方法发送到服务器。
主要的一些改动
APP中在NotificationService中增加对自身的引用
方便更加便捷地在任意位置获取xmppManager对象,同时可以方便判别当前服务器是否已启动。
增加心跳
APP端
在PacketWriter增加定时任务,默认每隔30秒向服务器发送一个空格,维持活跃状态,同时服务器会每隔300秒自动回复1个EchoIQ,如果app连续1000秒没有收到这个回应,则抛出连接中断异常,启动重连:XmppManager.startReconnectionThread()。心跳在登陆成功后启动,仅在超时后才会因异常而中断。
服务器端
设置readerIdleTime,主动检查用户是否掉线,默认超时时间120秒。
spring-config.xml 文件底部添加:
<bean id="getSessionConfig" factory-bean="ioAcceptor" factory-method="getSessionConfig">
<property name="readerIdleTime" value="60"></property>
</bean>
在org.androidpn.server.xmpp.codec.XmppDecode增加对客户端发送的“心跳”的处理(因为任何消息无论是否合法都会经过这里)。同时每隔300秒主动回一个“心跳”(一个EchoIQ)给安卓APP,在ClientSession中增加字段echoTime;在服务器发送消息给客户端时记录本次“心跳”的时间(在connection.deliver(packet)中)。
APP中修改重连功能
虽然存在3个监听器来监听手机网络和服务的状态,但这里存在BUG。
在XmppConnection中有3个连接状态:connected、authenticated、wasAuthenticated。
在不是主动下线(连接服务器时发生异常或者收到服务器的shotdown的要求)的情况下,被动断网后XmppConnection里的连接状态始终是connected,ConnectivityReceiver并没有被触发,重连操作无法正常执行,只是一路XMPP contected already,Account registered already下来,最后无法成功登陆。
PersistentConnectionListener类 在XmppManager中加入监听集合,
XmppConnection在连接关闭时或读写异常时会调用:
XmppConnection.notifyConnectionError(e)->
PersistentConnectionListener.notifyConnectionError(e)->
xmppManager.startReconnectionThread();
PacketReader,PacketWriter的shutDown中如果有操作未完成,会调用
PersistentConnectionListener.connectionClosed();
在重连成功会调用
XmppConnection.notifyReconnection()->
PersistentConnectionListener.reconnectionSuccessful(e)
修改:
1. XmppConnection中新增了noConnected(),在开启重连线程时先将connected 改为false。
2. XmppManager.startReconnectionThread()可能会经常被触发,所以做了修改,避免开启多个重连线程,或一个重连线程启动多次。
3. 在PersistentConnectionListener内的几个事件中选择性地添加重连的调用。
增加离线消息
添加notification表,发送消息前先将消息缓存到数据库中,在用户上线时主动读取这些消息。客户端上线的消息类型属于Presence(在org.androidpn.server.xmpp.handler.PresenceUpdateHandler中执行):
StanzaHandler->PacketRouter->PresenceRouter->PresenceUpdateHandler
客户端增加了DeliverConfirmIQ,在收到消息后,主动通知服务器删除缓存。
服务器处理IQ的相关文件:DeliverConfirmIQ,IQDeliverConfirmIQHandler。
如果该消息在2周后依然无法送达则会主动删除(在配置文件中可以修改这个有效期)。
其他主要的一些修改
1. 服务器端通过引入javaPNS2.2.jar来增加对IOS推送的支持。
2. 增加一个新的用户表,它和apn_user中的用户存在一对一的映射关系。apn_user表是消息推送的直接用户,因为消息推送是直接推送给手机设备的。新增的用户表的则是为了连接业务操作,实际操作中推送消息是通过app的业务用户映射到apn_user表中记录的用户设备来完成。因为用户的登陆设备是可更换,所以用户每次登陆(验证短信)都要重新绑定用户名与设备标识(apn_user表中的username),就是移除旧的映射关系,建立新的映射关系,同时会记录用户的新设备类型(安卓还是苹果)。依据用户的设备类型,从而决定最终的消息发送方式。所有的发送模式都是最终获取一组apn_user表的用户数据,然后遍历它们完成消息推送操作的。
3. 关于apn_user表中的数据,安卓会在socket连接成功后,依据设备的IMEI到服务器上查询是否已有记录,没有则会生成新的随机用户名和密码,并记录设备IMEI。另一个用户表中的数据则是在APP账号首次登陆APP时通过访问服务器的API接口来创建,并建立与apn_user表中数据的映射关系,IOS设备可能会先自动创建一份apn_user表的数据再绑定。IOS设备的机器标识直接使用苹果APNS服务器所生成的token,android设备则使用随机字符串,但会记录设备的唯一标识IMEI,通过IMEI可以保障同一设备不会重复注册。