在上兩篇文章中,我們依次介紹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