推送简介
在开发Android和iPhone应用程序时,往往需要从服务器不定时的向手机客户端即时推送各种通知消息。
所谓的消息推送指的是在互联网上通过定期传送用户需要的信息来减少信息过载的一项新技术。消息推送是从服务器端向移动终端发送连接,传输一定的信息。比如一些新闻客户端,每隔一段时间收到一条或者多条通知,这就是从服务器端传来的推送消息;还比如常用的一些IM软件如微信、GTalk等,都具有服务器推送功能。 推送技术通过自动传送信息给用户,来减少用于网络上搜索的时间。它根据用户的兴趣来搜索、过滤信息,并将其定期推给用户,帮助用户高效率地发掘有价值的信息。 当开发需要和服务器交互的移动应用时,基本上都需要和服务器进行交互,包括上传数据到服务器,同时从服务器上获取数据。一般情况下,客户端与服务器之间通讯客户端是主动的,但这就存在一个问题就是一旦服务器数据有更新或者服务器要下发通知给客户端只能等客户端连接的时候才能实现。这种方式使消息失去了实时性。 使客户端能够实时的收到服务器的消息和通知,总体来说有两种方式:
- 客户端使用Pull(拉)的方式:每隔一段时间就去服务器上获取一下信息,看是否有更新的信息出现。
- 服务器使用Push(推送)的方式:当服务器端有新信息了,则把最新的信息Push到客户端上。这样,客户端就能自动的接收到消息。 虽然Pull和Push两种方式都能实现获取服务器端更新信息的功能,但是明显来说Push方式比Pull方式更优越。因为Pull方式更费客户端的网络流量,更主要的是费电量,它需要客户端程序不停地去监测服务端的变化。
关于服务器端向客户端的推送,主要有三种方式:
- SMS(Push)方式 通过发送短信并拦截解析服务器返回的短信内容来获取服务器端的指令。在Android平台上,可以通过拦截SMS消息并且解析消息内容来了解服务器的意图,可以实现完全的实时操作。但是问题是这个方案的成本相对比较高,且依赖于运营商。
- 轮循(Pull)方式 这种方法需要客户端(主动)定时或者周期性的访问服务器端接口,与服务器进行连接并查询是否有新的消息到达,以获得最新的消息;客户端必须自己实现与服务器之间的通信,例如消息排队等。而且客户端还要考虑轮询的频率,如果太慢可能导致某些消息的延迟,如果太快,则会大量消耗网络带宽和电池电量。
- 持久连接(Push)方式 这个方案可以解决由轮询带来的性能问题,但是还是会造成手机电池的过快消耗。这种情况下,客户端需要开一个服务来保持和服务器端的长久连接(苹果就和谷歌的C2DM是这种机制)。但是对于Android系统,当系统可用资源较低,系统会强制关闭我们的服务或者是应用,这种情况下连接会强制中断。(Apple的推送服务之所以工作的很好,是因为每一台手机仅仅保持一个与服务器之间的连接,事实上C2DM也是这么工作的。即所有的推送服务都是经由一个代理服务器完成的,这种情况下只需要和一台服务器保持持久连接即可)。 C2DM是一个用来帮助开发者从服务器向客户端应用程序发送数据的服务。该服务提供了一个简单的、轻量级的机制,允许服务器可以通知移动应用程序直接与服务器进行通信,以便于从服务器获取应用程序更新和用户数据。C2DM服务负责处理诸如消息排队等事务并向运行于目标设备上的应用程序分发这些消息。
相比之下第三种还是最可行的。为软件编写系统服务或开机启动功能;或者如果系统资源较低,服务被关闭后可以在onDestroy ()方法里面再重启该服务,进而实现持久连接的方式。 在Android手机平台上,Google提供了C2DM(Cloudto Device Messaging)服务。C2DM是一个用来帮助开发者从服务器向Android应用程序发送数据的服务。该服务提供了一个简单的、轻量级的机制,允许服务器可以通知移动应用程序直接与服务器进行通信,以便于从服务器获取应用程序更新和用户数据。 C2DM内置于Android的2.2系统上,无法兼容老的1.6到2.1系统,更重要的是它依赖于Google官方提供的C2DM服务器,由于国内的网络环境,这个服务经常不可用。
XMPP协议
建立在TCP协议之上的XMPP协议,不仅可提供可持久连接的功能,实现服务器和客户机的双向通信,还能不依赖与系统版本和google服务器的限制,提供了比较好的解决方案。,XMPP是一种基于XML的传递协议,具有很强的灵活性和可扩展性。它的特点是将复杂性从客户端转移到了服务器端。 XMPP全称Extensible Messaging and Presence Protocol(可扩展通讯和表示协议),是一种以XML为基础的开放式即时通讯协议。Google官方的C2DM服务器底层也是采用XMPP协议进行的封装。XMPP因为被Google Talk和网易泡泡应用而被广大网民所接触。XMPP的关键特色是,分散式的即时通讯系统,以及使用XML串流。XMPP目前被IETF国际标准组织完成了标准化工作。 XMPP主要显著的优点主要有以下几个方面:
- 分布式:任何人都可以运行自己的XMPP服务器,它没有主服务器
- 安全性很高:使用SASL及TLS等技术的可靠安全性
- 开发性:它是开源的,易于进行学习和了解
- 跨平台:毋庸置疑,使用的XML进行传输的
androidpn
基于XMPP协议的java开发有一个开源框架,那就是smack,它主要封装了一些XMPP的实现。而如果把它直接用在Android上是不行的,因为android缺少了一些java的类库,于是一个改进版的asmack诞生了,它是专门为android而改进的android smack。而另外一个开源框架的诞生,则是对在引用smack的基础上实现和服务器端的持久连接,以实现服务器对客户端的推送,那就是android push notification,简称androidpn。 Androidpn在客户端集成了asmack。这样就可以很容易的简立一个和服务器端的基于xmpp协议的socket连接。Androidpn的客户端中,进行管理连接的类是XmppManager,它主要用来管理连接的信息,比如XMPP的端口、IP、登录的用户名密码,以及对连接的维护。之所以需要用户名和密码,是因为整个服务器端和客户端的通信是基于一个session(会话)过程:会话开始,首先会指定服务器的端口号,然后把上述提到的信息发送到服务器端,怎么发送消息的呢?以<stream>根节点的方式开始传递,只有在服务器和客户端关闭的时候才会发送它的结束标记</stream>。客户端通过XMPP协议只用做的就是接收消息,而所有其它的操作都交给服务器,比如管理连接、消息保存等等,这样就很大程度的减轻了客户端的负担。那么客户端和服务器端的消息回应是如何实现的?如要通过一个ID来标识,具体细节可以去查看XMPP协议。 androidpn内部使用asmack来实现xmpp协议的解析和拓展,使用MINA框架来进行多线程的socket管理。 一旦注册绑定后,服务器端就和客户端建立了连接,客户端只用负责去接收消息。所以当我们应用Androidpn的时候,客户端会非常的简单。在服务器端Androidpn实现的功能主要是展示用户状态和发送信息。 androidpn(android push notification)是一个基于XMPP协议的java开源框架,它包含了完整的客户端和服务器端:
androidpn客户端需要用到一个基于java的开源包asmack(XMMP协议的一个实现)。客户端利用asmack中提供的XMPPConnection类与服务器建立持久连接,并通过该连接进行用户注册和登录认证,同样也是通过这条连接,接收服务器发送的通知。 androidpn服务器端是java语言实现的,它的Web部分采用的是Spring和Hibernate框架,主要是用来显示和服务器建立连接的用户,以及消息的推送。Androidpn服务器包含两个部分,一个是侦听在5222端口上的XMPP服务,负责与客户端的XMPPConnection类进行通信,作用是用户注册和身份认证,并发送推送通知消息。另外一部分是Web服务器,采用一个轻量级的HTTP服务器,负责接收用户的Web请求。服务器的这两方式,意义非凡:当相应的TCP端口被防火墙封闭,可以使用轮询的方式进行访问,因此又有助于通过防火墙。
服务器架构 最上层包含四个组成部分,分别是SessionManager、Auth Manager、PresenceManager以及Notification Manager。其中:
- SessionManager负责管理客户端与服务器之间的会话
- Auth Manager负责客户端用户认证管理
- Presence Manager负责管理客户端用户的登录状态
- NotificationManager负责实现服务器向客户端推送消息功能
服务器端界面如下,分别对应了上述的几个功能模块:
采用上面的XMPP方案,目前只能发送文字消息,不过对于推送来说一般足够了,因为我们不能指望通过推送让客商得到所有的数据,一般情况下,利用推送只是告诉手机端服务器发生了某些改变,当客户端收到通知以后,应该主动到服务器获取最新的数据,这样才是推送服务的完整实现。
下面开始讲解下androidpn中的几个关键类。 在服务器端的源码中一个org.androidpn.server.xmpp.net.Connection类,该类代表一个服务器上的XMPP连接(注意只是一个),它可以确保在服务器关闭的时候,发送一个</stream>标记到客户端,告知连接断开,需重新连接。在Connection类中有以下几个重要字段:
private static final Log log = LogFactory.getLog(Connection.class);
private IoSession ioSession;
private Session session;
private ConnectionCloseListener closeListener;
private int majorVersion = 1;
private int minorVersion = 0;
private String language = null;
private TLSPolicy tlsPolicy = TLSPolicy.optional;
@SuppressWarnings("unchecked")
private static ThreadLocal encoder = new ThreadLocalEncoder();
private boolean closed;
其中:
- iosesion引用的MINA框架中的一个类,它是MINA中所有session类的*接口,它是线程安全的,其中定义了一些客户端的端口地址等属性信息,主要是用来保存服务器和客户端之间建立连接的一些信息。MINA是一个socket框架,主要是为了多线程管理socket的出现的。
- Session是一个抽象类,也是自定义的,主要是描述一些服务器和客户端的信息。Majorversion和minorversion指的是主版本和副版本。
- Tlspolicy指的是传输协议的策略,是一个枚举类型,分为三种required,optional,disabled.
- Encoder主要是来定义编码的,这里用的是utf8。
- 在这个类里,还有一个ClientAuth,它是枚举类型,主要是用来表示客户端是否需要验证。它主要的功能是用来建立连接,定义了一下发送数据到客户端的方法,还有就关闭连接的时候绑定监听器进行处理。
org.androidpn.server.xmpp.session.SessionManager主要用户管理所有会话,比如连接断开,删除session以及建立连接,添加session等等。 在管理Socket连接的时候,androidpn采用了MINA框架来进行管理,MINA的优点就是改变了我们传统的管理socket的方式,比如每建立一个socket开一个线程,而MINA可以实现多个线程管理N多个用户。在处理高并发的推送上无疑是有巨大的好处的。 合理的利用监听器来管理session,也是androidpn的优点。在安全性方面,制定了TLS(安全传输层)策略,并却采用了安全认证,这些方面都做的不错。 总之,用Androidpn好处有以下方面:采用完全开放的XMPP协议进行数据传输(QQ,MSN,GTalk等都是采用的这种协议);良好的框架支持(专门为android 而产生的推送框架asmack,以及很好的管理socket的框架MINA,都是很成熟的产品);完全开放的源代码(我们可以在androidpn的基础上进行修改,来满足我们的任何需求变更);大大的减少了客户端的代码,降低了android的开发难度。缺点不言而喻,使用了太多的框架,如果想要改一些具体的实现,可能会迁移发动全身。
源代码使用
- 打开MySQL服务,在其中创建一个名为androidpn的数据库
- 导入服务器端androidpnServer,可在resources目录下的jdbc.properties中修改数据库名及连接的用户名和密码
- 导入客户端androidpnClient,可在res/raw下的androidpn.properties中修改服务器端的ip地址。
客户端具体使用: 1、 右键androidpnClient,将其做为一个类库使用,如下图所示:
2、 第二步新建一个项目,右键该项目,将androidpnClient以类库的形式引入到当前项目中,如下图所示:
第三步:将androidpnClient的DemoAppActivity的onCreate()方法下面的三行代码拷贝到新建项目的onCreate()方法中。
// Start the service
ServiceManager serviceManager = new ServiceManager(this);
serviceManager.setNotificationIcon(R.drawable.notification);
serviceManager.startService();
第四步:将androidpnClient项目中功能清单文件的如下代码拷贝到新建的Android项目中:
<activity
android:name="org.androidpn.client.NotificationDetailsActivity"
android:label="Notification Details" >
</activity>
<activity
android:name="org.androidpn.client.NotificationSettingsActivity"
android:label="Notification Settings" >
</activity>
<service
android:name="org.androidpn.client.NotificationService"
android:enabled="true"
android:label="NotificationService" >
<intent-filter>
<action android:name="org.androidpn.client.NotificationService" />
</intent-filter>
</service>
<!--
<meta-data android:name="ANDROIDPN_API_KEY"
android:value="1234567890" />
-->
</application>
<uses-sdk android:minSdkVersion="5" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.VIBRATE" />