在上两篇文章中,我们依次介绍openfire部署以及smack常用api的使用,这一节中我们着力介绍如何基于asmack开发一个android的客户端,本篇的重点在实践,讲解和原理环节,大家可以参考前两篇的文章
基于xmpp openfire smack开发之openfire介绍和部署[1]
基于xmpp openfire smack开发之smack类库介绍和使用[2]
activity包下存放一些android页面交互相关的控制程序,还有一个些公共帮助类
db包为sqlite的工具类封装,这里做了一些自定义的改造,稍微仿spring的jdbctemplate结构,使用起来更加方便一点
manager包留下主要是一些管理组件,包括联系人管理,消息管理,提醒管理,离线消息管理,用户管理,xmpp连接管理
model包中都是一些对象模型,传输介质
service中存放一些android后台的核心服务,主要包括聊天服务,联系人服务,系统消息服务,重连接服务
task包中存放一些耗时的异步操作
util中存放一些常用的工具类
view中一些和android的ui相关的显示控件
anim中存放一些动画元素的配置
layout是布局页面
menu是地步菜单布局页面
values中存放一些字符,颜色,样式,参数的配置信息
其中strings.xml中,保存的缺省配置为gtalk的服务器信息,大家如果有谷歌gtalk的账号可以直接登录,否则需要更改这里的配置才可以使用其他的xmpp服务器
[html] view
plaincopy
<!-- 缺省的服务器配置 -->
<integer name="xmpp_port">5222</integer>
<string name="xmpp_host">talk.google.com</string>
<string name="xmpp_service_name">gmail.com</string>
<bool name="is_remember">true</bool>
<bool name="is_autologin">false</bool>
<bool name="is_novisible">false</bool>
androidmanifest.xml为android功能清单的配置文件,我们这里开放的权限并不多
<!-- 访问internet -->
<uses-permission android:name="android.permission.internet" />
<!--- 访问网络状态 -->
<uses-permission android:name="android.permission.access_network_state" />
<!-- 往sdcard写入数据权限 -->
<uses-permission android:name="android.permission.write_external_storage"/>
<span style="white-space: pre"> </span><!-- 在sdcard中创建与删除文件权限 -->
<span style="white-space: pre"> </span><uses-permission android:name="android.permission.mount_unmount_filesystems"/>
<span style="white-space: pre"> </span><!-- 往sdcard写入数据权限 -->
<span style="white-space: pre"> </span><uses-permission android:name="android.permission.write_external_storage"/>
1.activitysupport类
[java] view
package csdn.shimiso.eim.activity;
import android.app.activity;
import android.app.alertdialog;
import android.app.notification;
import android.app.notificationmanager;
import android.app.pendingintent;
import android.app.progressdialog;
import android.content.context;
import android.content.dialoginterface;
import android.content.intent;
import android.content.sharedpreferences;
import android.location.locationmanager;
import android.net.connectivitymanager;
import android.net.networkinfo;
import android.os.bundle;
import android.os.environment;
import android.provider.settings;
import android.view.inputmethod.inputmethodmanager;
import android.widget.toast;
import csdn.shimiso.eim.r;
import csdn.shimiso.eim.comm.constant;
import csdn.shimiso.eim.model.loginconfig;
import csdn.shimiso.eim.service.imchatservice;
import csdn.shimiso.eim.service.imcontactservice;
import csdn.shimiso.eim.service.imsystemmsgservice;
import csdn.shimiso.eim.service.reconnectservice;
/**
* actity 工具支持类
*
* @author shimiso
*/
public class activitysupport extends activity implements iactivitysupport {
protected context context = null;
protected sharedpreferences preferences;
protected eimapplication eimapplication;
protected progressdialog pg = null;
protected notificationmanager notificationmanager;
@override
protected void oncreate(bundle savedinstancestate) {
super.oncreate(savedinstancestate);
context = this;
preferences = getsharedpreferences(constant.login_set, 0);
notificationmanager = (notificationmanager) getsystemservice(notification_service);
pg = new progressdialog(context);
eimapplication = (eimapplication) getapplication();
eimapplication.addactivity(this);
}
protected void onstart() {
super.onstart();
protected void onresume() {
super.onresume();
protected void onpause() {
super.onpause();
protected void onstop() {
super.onstop();
public void ondestroy() {
super.ondestroy();
public progressdialog getprogressdialog() {
return pg;
public void startservice() {
// 好友联系人服务
intent server = new intent(context, imcontactservice.class);
context.startservice(server);
// 聊天服务
intent chatserver = new intent(context, imchatservice.class);
context.startservice(chatserver);
// 自动恢复连接服务
intent reconnectservice = new intent(context, reconnectservice.class);
context.startservice(reconnectservice);
// 系统消息连接服务
intent imsystemmsgservice = new intent(context,
imsystemmsgservice.class);
context.startservice(imsystemmsgservice);
/**
*
* 销毁服务.
* @author shimiso
* @update 2012-5-16 下午12:16:08
*/
public void stopservice() {
context.stopservice(server);
context.stopservice(chatserver);
context.stopservice(reconnectservice);
context.stopservice(imsystemmsgservice);
public void isexit() {
new alertdialog.builder(context).settitle("确定退出吗?")
.setneutralbutton("确定", new dialoginterface.onclicklistener() {
@override
public void onclick(dialoginterface dialog, int which) {
stopservice();
eimapplication.exit();
}
})
.setnegativebutton("取消", new dialoginterface.onclicklistener() {
dialog.cancel();
}).show();
public boolean hasinternetconnected() {
connectivitymanager manager = (connectivitymanager) context
.getsystemservice(context.connectivity_service);
if (manager != null) {
networkinfo network = manager.getactivenetworkinfo();
if (network != null && network.isconnectedorconnecting()) {
return true;
}
}
return false;
public boolean validateinternet() {
if (manager == null) {
openwirelessset();
return false;
} else {
networkinfo[] info = manager.getallnetworkinfo();
if (info != null) {
for (int i = 0; i < info.length; i++) {
if (info[i].getstate() == networkinfo.state.connected) {
return true;
}
openwirelessset();
public boolean haslocationgps() {
locationmanager manager = (locationmanager) context
.getsystemservice(context.location_service);
if (manager
.isproviderenabled(android.location.locationmanager.gps_provider)) {
return true;
public boolean haslocationnetwork() {
.isproviderenabled(android.location.locationmanager.network_provider)) {
public void checkmemorycard() {
if (!environment.media_mounted.equals(environment
.getexternalstoragestate())) {
new alertdialog.builder(context)
.settitle(r.string.prompt)
.setmessage("请检查内存卡")
.setpositivebutton(r.string.menu_settings,
new dialoginterface.onclicklistener() {
@override
public void onclick(dialoginterface dialog,
int which) {
dialog.cancel();
intent intent = new intent(
settings.action_settings);
context.startactivity(intent);
}
})
.setnegativebutton("退出",
eimapplication.exit();
}).create().show();
public void openwirelessset() {
alertdialog.builder dialogbuilder = new alertdialog.builder(context);
dialogbuilder
.settitle(r.string.prompt)
.setmessage(context.getstring(r.string.check_connection))
.setpositivebutton(r.string.menu_settings,
new dialoginterface.onclicklistener() {
@override
public void onclick(dialoginterface dialog,
int which) {
dialog.cancel();
intent intent = new intent(
settings.action_wireless_settings);
context.startactivity(intent);
}
})
.setnegativebutton(r.string.close,
int whichbutton) {
});
dialogbuilder.show();
* 显示toast
* @param text
* @param longint
* @update 2012-6-28 下午3:46:18
public void showtoast(string text, int longint) {
toast.maketext(context, text, longint).show();
public void showtoast(string text) {
toast.maketext(context, text, toast.length_short).show();
* 关闭键盘事件
* @update 2012-7-4 下午2:34:34
public void closeinput() {
inputmethodmanager inputmethodmanager = (inputmethodmanager) getsystemservice(context.input_method_service);
if (inputmethodmanager != null && this.getcurrentfocus() != null) {
inputmethodmanager.hidesoftinputfromwindow(this.getcurrentfocus()
.getwindowtoken(), inputmethodmanager.hide_not_always);
* 发出notification的method.
* @param iconid
* 图标
* @param contenttitle
* 标题
* @param contenttext
* 你内容
* @param activity
* @update 2012-5-14 下午12:01:55
public void setnotitype(int iconid, string contenttitle,
string contenttext, class activity, string from) {
/*
* 创建新的intent,作为点击notification留言条时, 会运行的activity
*/
intent notifyintent = new intent(this, activity);
notifyintent.putextra("to", from);
// notifyintent.setflags(intent.flag_activity_new_task);
/* 创建pendingintent作为设置递延运行的activity */
pendingintent appintent = pendingintent.getactivity(this, 0,
notifyintent, 0);
/* 创建notication,并设置相关参数 */
notification mynoti = new notification();
// 点击自动消失
mynoti.flags = notification.flag_auto_cancel;
/* 设置statusbar显示的icon */
mynoti.icon = iconid;
/* 设置statusbar显示的文字信息 */
mynoti.tickertext = contenttitle;
/* 设置notification发生时同时发出默认声音 */
mynoti.defaults = notification.default_sound;
/* 设置notification留言条的参数 */
mynoti.setlatesteventinfo(this, contenttitle, contenttext, appintent);
/* 送出notification */
notificationmanager.notify(0, mynoti);
public context getcontext() {
return context;
public sharedpreferences getloginusersharedpre() {
return preferences;
public void saveloginconfig(loginconfig loginconfig) {
preferences.edit()
.putstring(constant.xmpp_host, loginconfig.getxmpphost())
.commit();
.putint(constant.xmpp_port, loginconfig.getxmppport()).commit();
preferences
.edit()
.putstring(constant.xmpp_seivice_name,
loginconfig.getxmppservicename()).commit();
.putstring(constant.username, loginconfig.getusername())
.putstring(constant.password, loginconfig.getpassword())
.putboolean(constant.is_autologin, loginconfig.isautologin())
.putboolean(constant.is_novisible, loginconfig.isnovisible())
.putboolean(constant.is_remember, loginconfig.isremember())
.putboolean(constant.is_online, loginconfig.isonline())
.putboolean(constant.is_firststart, loginconfig.isfirststart())
public loginconfig getloginconfig() {
loginconfig loginconfig = new loginconfig();
string a = preferences.getstring(constant.xmpp_host, null);
string b = getresources().getstring(r.string.xmpp_host);
loginconfig.setxmpphost(preferences.getstring(constant.xmpp_host,
getresources().getstring(r.string.xmpp_host)));
loginconfig.setxmppport(preferences.getint(constant.xmpp_port,
getresources().getinteger(r.integer.xmpp_port)));
loginconfig.setusername(preferences.getstring(constant.username, null));
loginconfig.setpassword(preferences.getstring(constant.password, null));
loginconfig.setxmppservicename(preferences.getstring(
constant.xmpp_seivice_name,
getresources().getstring(r.string.xmpp_service_name)));
loginconfig.setautologin(preferences.getboolean(constant.is_autologin,
getresources().getboolean(r.bool.is_autologin)));
loginconfig.setnovisible(preferences.getboolean(constant.is_novisible,
getresources().getboolean(r.bool.is_novisible)));
loginconfig.setremember(preferences.getboolean(constant.is_remember,
getresources().getboolean(r.bool.is_remember)));
loginconfig.setfirststart(preferences.getboolean(
constant.is_firststart, true));
return loginconfig;
public boolean getuseronlinestate() {
// preferences = getsharedpreferences(constant.login_set,0);
return preferences.getboolean(constant.is_online, true);
public void setuseronlinestate(boolean isonline) {
preferences.edit().putboolean(constant.is_online, isonline).commit();
public eimapplication geteimapplication() {
return eimapplication;
}
大家写android程序会发现,不同的activity之间经常需要调用一些公共的资源,这里的资源不仅包括android自身的,还有我们自己的管理服务类,甚至相互之间传递一些参数,这里我仿照struts2的设计,提炼出一个activitysupport类,同时抽取一个接口,让所有的activity都集成这个类,因为有了接口,我们便可以采用回调模式,非常方便的传递数据和使用公共的资源,这种好处相信大家使用之后都能有深刻的体会,通过接口回调传递参数和相互调用的方式无疑是最优雅的,spring和hibernate源码中曾经大量使用这种结构。
2.sqlitetemplate类
package csdn.shimiso.eim.db;
import java.util.arraylist;
import java.util.list;
import android.content.contentvalues;
import android.database.cursor;
import android.database.sqlite.sqlitedatabase;
* sqlite数据库模板工具类
* 该类提供了数据库操作常用的增删改查,以及各种复杂条件匹配,分页,排序等操作
* @see sqlitedatabase
public class sqlitetemplate {
* default primary key
protected string mprimarykey = "_id";
* dbmanager
private dbmanager dbmanager;
* 是否为一个事务
private boolean istransaction = false;
* 数据库连接
private sqlitedatabase database = null;
private sqlitetemplate() {
private sqlitetemplate(dbmanager dbmanager, boolean istransaction) {
this.dbmanager = dbmanager;
this.istransaction = istransaction;
* istransaction 是否属于一个事务 注:一旦istransaction设为true
* 所有的sqlitetemplate方法都不会自动关闭资源,需在事务成功后手动关闭
* @return
public static sqlitetemplate getinstance(dbmanager dbmanager,
boolean istransaction) {
return new sqlitetemplate(dbmanager, istransaction);
* 执行一条sql语句
* @param name
* @param tel
public void execsql(string sql) {
try {
database = dbmanager.opendatabase();
database.execsql(sql);
} catch (exception e) {
e.printstacktrace();
} finally {
if (!istransaction) {
closedatabase(null);
public void execsql(string sql, object[] bindargs) {
database.execsql(sql, bindargs);
* 向数据库表中插入一条数据
* @param table
* 表名
* @param content
* 字段值
public long insert(string table, contentvalues content) {
// insert方法第一参数:数据库表名,第二个参数如果content为空时则向表中插入一个null,第三个参数为插入的内容
return database.insert(table, null, content);
return 0;
* 批量删除指定主键数据
* @param ids
public void deletebyids(string table, object... primarykeys) {
if (primarykeys.length > 0) {
stringbuilder sb = new stringbuilder();
for (@suppresswarnings("unused")
object id : primarykeys) {
sb.append("?").append(",");
sb.deletecharat(sb.length() - 1);
database = dbmanager.opendatabase();
database.execsql("delete from " + table + " where "
+ mprimarykey + " in(" + sb + ")",
(object[]) primarykeys);
* 根据某一个字段和值删除一行数据, 如 name="jack"
* @param field
* @param value
* @return 返回值大于0表示删除成功
public int deletebyfield(string table, string field, string value) {
return database.delete(table, field + "=?", new string[] { value });
* 根据条件删除数据
* @param whereclause
* 查询语句 参数采用?
* @param whereargs
* 参数值
public int deletebycondition(string table, string whereclause,
string[] whereargs) {
return database.delete(table, whereclause, whereargs);
* 根据主键删除一行数据
* @param id
public int deletebyid(string table, string id) {
return deletebyfield(table, mprimarykey, id);
* 根据主键更新一行数据
* @param values
* @return 返回值大于0表示更新成功
public int updatebyid(string table, string id, contentvalues values) {
return database.update(table, values, mprimarykey + "=?",
new string[] { id });
* 更新数据
public int update(string table, contentvalues values, string whereclause,
return database.update(table, values, whereclause, whereargs);
* 根据主键查看某条数据是否存在
public boolean isexistsbyid(string table, string id) {
return isexistsbyfield(table, mprimarykey, id);
return null;
* 根据某字段/值查看某条数据是否存在
* @param status
public boolean isexistsbyfield(string table, string field, string value) {
stringbuilder sql = new stringbuilder();
sql.append("select count(*) from ").append(table).append(" where ")
.append(field).append(" =?");
return isexistsbysql(sql.tostring(), new string[] { value });
* 使用sql语句查看某条数据是否存在
* @param sql
* @param selectionargs
public boolean isexistsbysql(string sql, string[] selectionargs) {
cursor cursor = null;
cursor = database.rawquery(sql, selectionargs);
if (cursor.movetofirst()) {
return (cursor.getint(0) > 0);
} else {
return false;
closedatabase(cursor);
* 查询一条数据
* @param rowmapper
* @param args
public <t> t queryforobject(rowmapper<t> rowmapper, string sql,
string[] args) {
t object = null;
cursor = database.rawquery(sql, args);
object = rowmapper.maprow(cursor, cursor.getcount());
return object;
* 查询
* @param startresult
* 开始索引 注:第一条记录索引为0
* @param maxresult
* 步长
public <t> list<t> queryforlist(rowmapper<t> rowmapper, string sql,
string[] selectionargs) {
list<t> list = null;
list = new arraylist<t>();
while (cursor.movetonext()) {
list.add(rowmapper.maprow(cursor, cursor.getposition()));
return list;
* 分页查询
int startresult, int maxresult) {
cursor = database.rawquery(sql + " limit ?,?", new string[] {
string.valueof(startresult), string.valueof(maxresult) });
* 获取记录数
public integer getcount(string sql, string[] args) {
cursor = database.rawquery("select count(*) from (" + sql + ")",
args);
if (cursor.movetonext()) {
return cursor.getint(0);
* 检索的表
* @param columns
* 由需要返回列的列名所组成的字符串数组,传入null会返回所有的列。
* @param selection
* 查询条件子句,相当于select语句where关键字后面的部分,在条件子句允许使用占位符"?"
* 对应于selection语句中占位符的值,值在数组中的位置与占位符在语句中的位置必须一致,否则就会有异常
* @param groupby
* 对结果集进行分组的group by语句(不包括group by关键字)。传入null将不对结果集进行分组
* @param having
* 对查询后的结果集进行过滤,传入null则不过滤
* @param orderby
* 对结果集进行排序的order by语句(不包括order by关键字)。传入null将对结果集使用默认的排序
* @param limit
* 指定偏移量和获取的记录数,相当于select语句limit关键字后面的部分,如果为null则返回所有行
public <t> list<t> queryforlist(rowmapper<t> rowmapper, string table,
string[] columns, string selection, string[] selectionargs,
string groupby, string having, string orderby, string limit) {
cursor = database.query(table, columns, selection, selectionargs,
groupby, having, orderby, limit);
* get primary key
public string getprimarykey() {
return mprimarykey;
* set primary key
* @param primarykey
public void setprimarykey(string primarykey) {
this.mprimarykey = primarykey;
* @param <t>
public interface rowmapper<t> {
/**
*
* @param cursor
* 游标
* @param index
* 下标索引
* @return
public t maprow(cursor cursor, int index);
* 关闭数据库
public void closedatabase(cursor cursor) {
if (null != database) {
database.close();
if (null != cursor) {
cursor.close();
我们希望在android操作数据库是优雅的一种方式,这里不必关注事务,也不用担心分页,更不用为了封装传递对象烦恼,总之一切就像面向对象那样,简单,模板类的出现正是解决这个问题,虽然它看上去可能不是那么完美有待提高,这里我封装了很多sqlite常用的工具,大家可以借鉴使用。
3.xmppconnectionmanager管理类
package csdn.shimiso.eim.manager;
import org.jivesoftware.smack.connection;
import org.jivesoftware.smack.connectionconfiguration;
import org.jivesoftware.smack.roster;
import org.jivesoftware.smack.xmppconnection;
import org.jivesoftware.smack.provider.providermanager;
import org.jivesoftware.smackx.groupchatinvitation;
import org.jivesoftware.smackx.privatedatamanager;
import org.jivesoftware.smackx.packet.chatstateextension;
import org.jivesoftware.smackx.packet.lastactivity;
import org.jivesoftware.smackx.packet.offlinemessageinfo;
import org.jivesoftware.smackx.packet.offlinemessagerequest;
import org.jivesoftware.smackx.packet.sharedgroupsinfo;
import org.jivesoftware.smackx.provider.dataformprovider;
import org.jivesoftware.smackx.provider.delayinformationprovider;
import org.jivesoftware.smackx.provider.discoverinfoprovider;
import org.jivesoftware.smackx.provider.discoveritemsprovider;
import org.jivesoftware.smackx.provider.mucadminprovider;
import org.jivesoftware.smackx.provider.mucownerprovider;
import org.jivesoftware.smackx.provider.mucuserprovider;
import org.jivesoftware.smackx.provider.messageeventprovider;
import org.jivesoftware.smackx.provider.multipleaddressesprovider;
import org.jivesoftware.smackx.provider.rosterexchangeprovider;
import org.jivesoftware.smackx.provider.streaminitiationprovider;
import org.jivesoftware.smackx.provider.vcardprovider;
import org.jivesoftware.smackx.provider.xhtmlextensionprovider;
import org.jivesoftware.smackx.search.usersearch;
* xmpp服务器连接工具类.
public class xmppconnectionmanager {
private xmppconnection connection;
private static connectionconfiguration connectionconfig;
private static xmppconnectionmanager xmppconnectionmanager;
private xmppconnectionmanager() {
public static xmppconnectionmanager getinstance() {
if (xmppconnectionmanager == null) {
xmppconnectionmanager = new xmppconnectionmanager();
return xmppconnectionmanager;
// init
public xmppconnection init(loginconfig loginconfig) {
connection.debug_enabled = false;
providermanager pm = providermanager.getinstance();
configure(pm);
connectionconfig = new connectionconfiguration(
loginconfig.getxmpphost(), loginconfig.getxmppport(),
loginconfig.getxmppservicename());
connectionconfig.setsaslauthenticationenabled(false);// 不使用sasl验证,设置为false
connectionconfig
.setsecuritymode(connectionconfiguration.securitymode.enabled);
// 允许自动连接
connectionconfig.setreconnectionallowed(false);
// 允许登陆成功后更新在线状态
connectionconfig.setsendpresence(true);
// 收到好友邀请后manual表示需要经过同意,accept_all表示不经同意自动为好友
roster.setdefaultsubscriptionmode(roster.subscriptionmode.manual);
connection = new xmppconnection(connectionconfig);
return connection;
* 返回一个有效的xmpp连接,如果无效则返回空.
* @update 2012-7-4 下午6:54:31
public xmppconnection getconnection() {
if (connection == null) {
throw new runtimeexception("请先初始化xmppconnection连接");
* 销毁xmpp连接.
* @update 2012-7-4 下午6:55:03
public void disconnect() {
if (connection != null) {
connection.disconnect();
public void configure(providermanager pm) {
// private data storage
pm.addiqprovider("query", "jabber:iq:private",
new privatedatamanager.privatedataiqprovider());
// time
pm.addiqprovider("query", "jabber:iq:time",
class.forname("org.jivesoftware.smackx.packet.time"));
} catch (classnotfoundexception e) {
// xhtml
pm.addextensionprovider("html", "http://jabber.org/protocol/xhtml-im",
new xhtmlextensionprovider());
// roster exchange
pm.addextensionprovider("x", "jabber:x:roster",
new rosterexchangeprovider());
// message events
pm.addextensionprovider("x", "jabber:x:event",
new messageeventprovider());
// chat state
pm.addextensionprovider("active",
"http://jabber.org/protocol/chatstates",
new chatstateextension.provider());
pm.addextensionprovider("composing",
pm.addextensionprovider("paused",
pm.addextensionprovider("inactive",
pm.addextensionprovider("gone",
// filetransfer
pm.addiqprovider("si", "http://jabber.org/protocol/si",
new streaminitiationprovider());
// group chat invitations
pm.addextensionprovider("x", "jabber:x:conference",
new groupchatinvitation.provider());
// service discovery # items
pm.addiqprovider("query", "http://jabber.org/protocol/disco#items",
new discoveritemsprovider());
// service discovery # info
pm.addiqprovider("query", "http://jabber.org/protocol/disco#info",
new discoverinfoprovider());
// data forms
pm.addextensionprovider("x", "jabber:x:data", new dataformprovider());
// muc user
pm.addextensionprovider("x", "http://jabber.org/protocol/muc#user",
new mucuserprovider());
// muc admin
pm.addiqprovider("query", "http://jabber.org/protocol/muc#admin",
new mucadminprovider());
// muc owner
pm.addiqprovider("query", "http://jabber.org/protocol/muc#owner",
new mucownerprovider());
// delayed delivery
pm.addextensionprovider("x", "jabber:x:delay",
new delayinformationprovider());
// version
pm.addiqprovider("query", "jabber:iq:version",
class.forname("org.jivesoftware.smackx.packet.version"));
// vcard
pm.addiqprovider("vcard", "vcard-temp", new vcardprovider());
// offline message requests
pm.addiqprovider("offline", "http://jabber.org/protocol/offline",
new offlinemessagerequest.provider());
// offline message indicator
pm.addextensionprovider("offline",
"http://jabber.org/protocol/offline",
new offlinemessageinfo.provider());
// last activity
pm.addiqprovider("query", "jabber:iq:last", new lastactivity.provider());
// user search
pm.addiqprovider("query", "jabber:iq:search", new usersearch.provider());
// sharedgroupsinfo
pm.addiqprovider("sharedgroup",
"http://www.jivesoftware.org/protocol/sharedgroup",
new sharedgroupsinfo.provider());
// jep-33: extended stanza addressing
pm.addextensionprovider("addresses",
"http://jabber.org/protocol/address",
new multipleaddressesprovider());
这个类是xmpp连接的管理类,如果大家使用smack的api对这个应该不会陌生,asmack对xmpp连接的管理,与smack的差别不大,但是部分细微区别也有,我们在使用中如果遇到问题,还要多加注意,我们这里将其设计成单例,毕竟重复创建连接是个非常消耗的过程。
很像qq吧,没错,这是2012年版本qq的安卓界面,只是界面元素一样,实现方式大不相同,下面简单列一下这个客户端实现的功能:
1.聊天
2.离线消息
3.添加,删除好友
4.添加,移动好友分组
5.设置昵称
6.监控好友状态
7.网络断开系统自动重连接
8.收到添加好友请求消息处理
9.收到系统广播消息处理
10.查看历史聊天记录
11.消息弹出提醒,和小气泡
....
因为时间关系不是很完美,主要用于学习研究,欢迎大家给我提bug和改进意见。
分数比较大,不是为了坑大家,是怕有伸手党出现,拿了源码出去招摇撞骗,请尊重作者原创!
http://download.csdn.net/detail/shimiso/6224163
参阅文献
openfirehttp://www.igniterealtime.org/
push-notificationhttp://www.push-notification.org/
claros chathttp://www.claros.org/
androidpnsourceforgehttp://sourceforge.net/projects/androidpn/
android消息推送解决方案http://www.cnblogs.com/hanyonglu/archive/2012/03/04/2378971.html
xmpp协议实现原理介绍 http://www.cnblogs.com/hanyonglu/archive/2012/03/04/2378956.html