androidpn推送

通信机制
Androidpn是韩国Sehwan No写的开源消息推送项目,很多大公司都用这个消息推送方式构建自己的消息推送服务,缺点是导致客户端比较耗电。通信机制分别由客户端和服务器完成。

客户端采用基于javaXMPP协议包asmack(该包依赖于openfire下的开源项目smack)。通过该协议包提供的XMPPConnetcion类与服务器建立持久连接,并通过该连接进行用户注册和登录认证,以及接受服务器消息。

   服务器是基于开源的openfire工程,采用java语言实现和Spring框架提供Web服务。主要有两个部分,一个是侦听在5222端口上的XMPP服务,负责与客户端的XMPPConnection类进行通信,作用是用户注册和身份认证,并推送消息。另外是Web服务器,采用一个轻量级的HTTP服务器,负责接收用户的Web请求。

   整个基于XMPP的通信中,服务器端和客户端的通信是基于一个session(会话)过程,会话开始,首先客户端会指定端口号,然后把准备的连接信息发送到服务器端,客户端通过XMPP协议做的只有接收消息,其他的管理连接和保存消息等都由服务器负责。消息的传递是以根节点<stream>为起始,以</stream>为结束。

   服务器和客户端建立Tcp连接过程如下。首先,建立会话协商,Client 准备connecting to server (127.0.0.1:5222)。其次,服务器询问Client所支持的安全认证。再者,Client发送给服务器要注册的用户。然后,Client根据服务器的要求提交用户信息。最后,Client关闭注册的STREAM元素。

服务器
服务端架构依赖springmina框架,spring完成java Ben管理,mina完成网络通信。另外,服务端还通过jetty潜入了Admin Console Web界面功能。对于spring的依赖导致要把androidpn-server整合潜入项目中时,要考虑和自己项目本身spring环境的整合问题,可能要改造androidpn-server源代码,具体要看项目中spring的环境。对于mina的依赖不存在这个问题,因为mina是类库型框架,而spring是容器型框架。androidpn-serverjetty的使用不是可选的,而是和jetty绑定的,所以要不使用jetty就必须对androidpn-server的源代码进行改造。

另外,对数据的存储使用hibernate框架,androidpn-server在使用hibernate时配置通过ehcache实现二级缓存。官方默认使用的数据库是HSQLDB数据库,你可以改用其他数据库。HSQLDB是用java语言实现的数据库,对其使用请自己学习。Androidpn-server通过In-Process模式(作为应用程序的一部分潜入应用程序)使用HSQLDB的。




  服务器主要由以下几个包组成:
  org.androidpn.server.xmpp异常类型定义,包含程序入口类XmppServer

  org.androidpn.server.util加载配置文件,获取主机和端口等信息。

  org.androidpn.server.xmpp.codecXMPP协议的XML文件解析包,server  

收到和发送的消息都要通过这个包来进行xmpp协议编码和解码。

  org.androidpn.server.xmpp.handler负责对不同类型的消息进行处理

  org.androidpn.server.xmpp.net负责维护与client之间的持久连接,并实现了一些传输方式供发送xmpp消息时使用。

  org.androidpn.server.xmpp.ssl是对连接进行ssl认证的工具包。

  org.androidpn.server.xmpp.router包负责将收到的信息包发送到相应的handler进行处理,是一个路由包。

  org.androidpn.server.xmpp.presence里面只包含PresenceManager类,用来维护client的在线状态。

  org.androidpn.server.xmpp.push包里面的NotificationManager类包含有向client发送消息的接口。

    服务器中使用ServerStarter类启动服务,服务启动后使用XmppServer来管理连接、加载配置等。

服务器接收处理消息流程
 connection接收到来着Clientpacket,使用包codec解码。

② 路由包router根据packet的namespace等信息,将packet路由到相应的Handler。

③ Handler开始处理。

开发中只要根据client发送消息的格式,定义自己的router和handler类,然后在PacketRouter中注册router,在IQRouter中注册handler即可。

服务器发送消息的流程

 使用NotificationManager接口的push方法。

 使用SessionManagerSession集合查找相应的client连接。

 定义和组装XMPP消息,通过sessionclient发送。

在这个流程中我们需要修改的是步骤3,也就是需要定义和组装自己的xmpp消息,以便于将适当的信息传到客户端并便于客户端解析。一个简单的消息组装例子如下:

private IQ  createMessageIQ(String title, String message, String userId,String json) {
Element notification =  DocumentHelper.createElement(QName.get(
"message",  INQURIE_NAMESPACE));
notification.addElement("title").setText(title);
notification.addElement("text").setText(message);
notification.addElement("userId").setText(userId);
notification.addElement("json").setText(json);
IQ iq = new IQ();
iq.setType(IQ.Type.set);
iq.setChildElement(notification);
return iq;
}
要注意的是在创建element的时候,传入的namespace要和client解析使用的namespace相匹配。

独立部署使用



独立部署使用不需要对androidpn-server源代码进行任何改造,目录结构见上图,只需要修改好配置文件即可,配置文件在conf文件夹下,下面对需要注意的配置项进行说明,其他配置项目使用默认配置就可。

对于config.properties的配置说明如下:

apiKey=1234567890 #暂时发现没什么用处

admin.console.host=127.0.0.1 #web管理控制界面 jetty服务监听的地址

admin.console.port=7070 #web管理控制界面 jetty服务监听的端口

对于spring-config.xml的配置说明:

<bean id="ioAcceptor" class="org.apache.mina.transport.socket.nio.NioSocketAcceptor"init-method="bind" destroy-method="unbind">

<property name="defaultLocalAddress" value=":5222" /> <!--mina Socekt服务端监听端口,客户端配置和此配置一致 -->

<property name="handler" ref="xmppHandler" />

<property name="filterChainBuilder" ref="filterChainBuilder" />

<property name="reuseAddress" value="true" />

</bean>

启动脚本在bin下,windownslinux的脚本都有,需要配置系统环境变量JAVA_HOME,或者修改启动脚本将JAVA_HOME指定为你本机的具体地址,运行run脚本androidpn启动成功,通过浏览器访问 http://127.0.0.1:7070/ 就可看到console界面。

其他项目通过http方式将需要发送的消息提交给androidpn-server服务,由androidpn-server完成客户端消息推送。

具体应用可以通过程序代码编写方式访问 http://192.168.1.24:7070/notification.do 地址,将需要的参数以post方式提交。

嵌入项目使用
若要将androidpn作为应用程序的一部分潜入项目中使用,需要对androidpn-server源代码进行改造。

情况一:若你的项目中没有用到spring
这种情况不用考虑androidpnspring和你项目中spring配置的融合问题,直接因为spring就可以了,配置文件使用androidpn默认配置文件名称spring-config.xml。配置文件都放在classpath下。

需要在config.properties配置文件中增加配置server.home.dir选项,值指向一个目录,如:server.home.dir=F:\\android\\download\\ androidpn-server-0.5.0,该目录下要有子目录conf\security,目录下要放两个文件keystore和truststore,用来进行SSL安全传输用的密钥和证书,利用androdpn-server自带的就可以。

接下来就是修改org.androidpn.server.xmpp. XmppServer类,将启动admin console的代码注释掉,如下截图红框框住的部分。如果你需要启动admin console的话,需要将原始androidpn中console文件夹放到合适的路径下,这个还没有试。




Androidpn-server主启动类 org.androidpn.server.xmpp.XmppServer 修改完后,你就可以在你的应用程序这样调用:

XmppServerserver = XmppServer.getInstance();

这样XmppServer通过加载spring启动了mina监听服务,这时手机客户端应用已经可以连接服务端了。

你的应用程序需要推送消息时,实例化

org.androidpn.server.xmpp.push.NotificationManager调用其提供的 sendBroadcast sendNotifcationToUser 方法,如下代码调用:
NotificationManager notificationManager = new NotificationManager();
notificationManager.sendBroadcast("1234567890", "Hello","Hello BalanceJia!", "uri");
notificationManager.sendNotifcationToUser(apiKey,username, title, message, uri);

情况二:若你的项目中用到spring
和你的项目中spring配置融合,需要注意spring容器不能重复启动的问题。所以要将androidpn-serverspring配置增加到你的spring配置中,需要修改XmppServer启动spring的地方,另外androidpn-server spring中配置的ben名字和你项目中的本名字不能重复,因为androidpn-server中用到的ben  id有: dataSource、dataSource、transactionManager、userDao、userService。 还有数据源冲突的问题。

情况三:基于tomcat部署
1.依据ServerStart类写一个servlet,在servlet中启动时创建XmppServer实例。然后在web.xml中配置为自启动。

2.androidpn自带的配置目录conf拷贝到WEB-INF下面,并且把spring-config.xml文件从conf目录移动到WEB-INF目录。

3.修改默认的spring-config.xml文件,在文件schema的头部<beans>下面加入如下内容<import resource="applicationContext.xml"/>,其中,加载顺序会默认先加载applicationContext文件,然后会加载spring-config文件后面的内容。

原因是spring-config文件中有个<bean id="xmppHandler" .../>组件会在web服务启动的时候加载,该组件也会初始化XmppServer,导致与servlet的加载发生冲突。

由于未使用使用hibernate-cfg.xml文件,需要在spring-config文件加入如下内容:

<bean id="sessionFactory"
                   class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
                   <property name="dataSource" ref="dataSource" />
                   <property name="mappingResources" >
          <list>
            <value>org/androidpn/server/model/User.hbm.xml</value>
          </list>
        </property>
        <property name="hibernateProperties">
           <props>
             <prop key="hibernate.dialect">
              org.hibernate.dialect.SQLServerDialect
             </prop>
             <prop key="hibernate.show_sql">true</prop>
             <prop key="hibernate.hbm2ddl.auto">update</prop>
          </props>
         </property>
         </bean>

spring-config.xml文件中,配置组件<bean: id="ioAcceptor">listenrPort属性
<bean id="listenrPort" class="java.net.InetSocketAddress">
             <constructor-arg value="localhost"/>
             <constructor-arg value="5222"/>
</bean>

4. 此外,Androidpn默认采用spring-mvc方式配置,所以需要修改action指向。修改org.androidpn.server.console.controller包下面的三个类,改为继承自struct2ActionSupport方式,并修改默认的调用方法,然后配置struct.xml文件。

客服端
Client这边包含有消息的收发,解析以及持久连接的发起,重连等功能呢,十分强大,我们开发时完全不用管底层的连接,也不用担心断线,可以专注于业务部分的开发。

控制器
XmppManagerClient的主控制器,主要用来管理连接信息,包括XMPP端口、IP地址、登录的用户名和密码,以及对连接的维护。

消息解析处理
NotificationIQNotificationPackerListener以及NotificationIQProvider三个类负责对收到的Notification格式的消息进行解析和处理。

手机状态监听
PersistentConnetcionListenerPhoneStateChangeListener以及ReconnectionThread三个类负责监听手机状态和断线重连。

同时,代码结构也很简单。去除androidServiceBroadCast类以及一些工具类和常量类不谈:

1.NotificationIQ,NotificationIQProvider,NotificationPacketListener三个类负责对收到的Notification格式的消息进行解析和处理,

2.XmppManager是主控制器,NotificationService通过这个类,在后台维护androidpn连接。

3.PersistentConnectionListenerPhoneStateChangeListenerReconnectionThread.java三个类则负责监听手机的状态并进行断线重连。

我们自定义消息时需要定义3个类:在NotificationIQ中定义消息的实体,在NotificationIQProvider中将消息转化为NotificationIQ实体,在NotificationPacketListener中对实体进行处理,具体的实现可参考NotificationIQ,NotificationIQProvider,NotificationPacketListener三个类。在定义这些类之后,还需要在XmppManager中将这3个类中注册到connection中,代码如下:

connection.connect();
Log.i(LOGTAG, "XMPP connected successfully");
// packet provider
ProviderManager.getInstance().addIQProvider("message",
Constants.NOTIFICATION_NAMESPACE,
new NotificationIQProvider());
//packet filter
PacketFilter packetFilter = new PacketTypeFilter(
NotificationIQ.class);
// packet listener
PacketListener packetListener = xmppManager.getNotificationPacketListener();
connection.addPacketListener(packetListener, packetFilter);

需要注意的是,注册Notification IQProvider时,传入的namespace需要和服务端组装消息时使用的namespace一致,才能正确的收到。
注册
   Client在与服务器建立 connection后,首先会提交注册任务,在 Androidpn项目中,用户注册是采用 UUID(通用唯一标识码)来实现的,其每次登录都会利用这个 UUID.randomUUID()来产生一个用户名和密码,而且不会重复。
    注册方法根据 Server的格式要求组装注册消息,发送到服务器,同时使用在 connection中添加监听,来获取服务器返回的消息 packetClient会根据消息的类型来判断是否注册成功。
    服务端收到 Client的注册请求,会通过路由类 router来转发到相应的 Handler处理, router首先会读取 Client发来包 PacketNamespace部分,根据 XMPP RFC协议注册 packetnamespacejabber:iq:register,授权 packetnamespacejabber:iq:auth,由此转发到 IQRegisterHandler处理注册请求。
    如果注册成功, Client会把相应的用户名、密码通过 SharePerence保存在 Client的共享文件 AndroidpnClient中。此外, AndroidpnClient还保存了当前应用的包名、类名、服务器地址、端口、客户端版本、设备 ID等。
登录
   Client的登录是在注册之后进行的,在 ClientXmppManager类中,建立连接的时候首先会提交登录任务,然而在提交登录任务的时候都有一个 submitRegisterTask()方法,可知,每次登录都会首先提交一个注册任务。
    登录的时候,要先通过 isAuthenticated()方法判断是否授权,授权处理同样会通过 connection发送到服务端,服务端会根据 packetnamespace部分进行解析,然后路由类 router会根据 namespace转发到 IQAuthHandler处理。
   Client收到服务端返回的消息 packet,会使用 connection的监听器异步接收消息,并通过 XmppManagercontextsendBroadcast(Intentintent)以广播发送出去。

部署到自己的项目中
在自己项目中使用http协议将数据POST到server端这个 org.androidpn.server.console.controller.NotificationController类的send方法中。
具体参数名称及获取参数的代码,可以修改server端。
例如:
String url = "http://xxxxx./notification.do?action=send"

NameValuePair[] data = {new NameValuePair("username", UUID), 
new NameValuePair("broadcast", "N"),
new NameValuePair("title", userName),
new NameValuePair("message",msgText),
new NameValuePair("uri",uri),
new NameValuePair("remindType",remindType)};
new PostHttp().methodPost(url, data);

具体项目在我的资源里面有,大家自己去下吧

你可能感兴趣的:(androidpn推送)