前言
Smack官方地址
即時通訊的項目用到Smack 4.1.9版本簿晓,由于項目需求Smack某些API不符合要求,有些需求采用自己后臺接口實現(xiàn)千埃,例如:文件上傳憔儿、文件下載、注冊放可、添加好友谒臼,刪除好友,好友列表吴侦、用戶詳情屋休、群列表、群成員备韧、查詢離線消息劫樟、查詢歷史消息、離開群织堂,這樣做的好處可以做到業(yè)務(wù)與IM分離叠艳,用戶關(guān)系可以從其他系統(tǒng)導(dǎo)入直接使用,并且后面也還存在Pjsip的用戶體系易阳。
項目坑
項目里面最坑就是斷線之后聊天室要重新加入才能收到消息附较,Smack 4.2.0修復(fù)了這個bug,斷線之后不需要重新加入聊天室(群)潦俺, [SMACK-572] - Rejoin MUC rooms after reconnect拒课。
服務(wù)器做時間校驗存在后臺返回報文格式不對,無法正常進(jìn)行時間校驗(浪費兩天找這個問題)事示,升級成4.1.9解決這個問題早像,本來想用最新版本但是里面有些API的用法發(fā)生改變也就沒用了,建議用Smack最新版本肖爵,可以避免很多麻煩卢鹦。
[SMACK-716] - EntityTimeManager.getTime() does not set the recipients JID
代碼
Android Studio build.gradle依賴:
//smack依賴包
compile 'org.igniterealtime.smack:smack-android-extensions:4.1.9'
compile 'org.igniterealtime.smack:smack-android:4.1.9'
compile 'org.igniterealtime.smack:smack-tcp:4.1.9'
compile 'org.igniterealtime.smack:smack-im:4.1.9'
- 初始化
這里只做一個簡單的Smack初始化,數(shù)據(jù)加密可能還涉及到證書生成劝堪、添加校驗冀自。具體證書的生成可以參看這篇博客TLS 雙向認(rèn)證揉稚,即時通訊項目涉及到用戶切換,所以設(shè)計XMPP流每個用戶都重新初始化熬粗,否則用戶退出后搀玖,切換到新用戶存在各種問題。
try {
KeyStore trustStore = KeyStore.getInstance("JKS");
trustStore.load(getClass().getResourceAsStream("/truststore"), storePass.toCharArray());
TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
tmf.init(trustStore);
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, tmf.getTrustManagers(), null);
} catch (KeyStoreException e) {
e.printStackTrace();
} catch (CertificateException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (KeyManagementException e) {
e.printStackTrace();
}
//初始化xmpp流
XMPPTCPConnectionConfiguration config = XMPPTCPConnectionConfiguration.builder()
.setServiceName(URConstant.xmppIp)
.setHost(URConstant.xmppIp)
.setPort(URConstant.xmppPort)
// 添加服務(wù)器域名荐糜、IP巷怜、端口
// 域名可以跟IP一樣
//.setServiceName("applexmpp.com")
//.setHost("172.19.26.12")
//.setPort(5222)
.setSecurityMode(ConnectionConfiguration.SecurityMode.ifpossible)
//設(shè)置為true,利于開發(fā)調(diào)試
.setDebuggerEnabled(options.getDebuggerEnabled())
.setConnectTimeout(options.getConnectTimeout())
//添加證書
// .setCustomSSLContext(sslContext)
// .setHostnameVerifier()
.setCompressionEnabled(true)
.build();
connection = new XMPPTCPConnection(config);
- 注冊
/**
* 注冊
*
* @param account 注冊帳號
* @param password 注冊密碼
* @return true 注冊成功 false 注冊失敗
*/
public boolean register(String account, String password) {
try {
if (!connection.isConnected()) {
connection.connect();
}
} catch (Exception e) {
e.printStackTrace();
return false;
}
try {
AccountManager.getInstance(connection).createAccount(account, password);
} catch (XMPPException | SmackException e) {
e.printStackTrace();
return false;
}
return true;
}
- 登陸
其中出現(xiàn)的一些異潮┦希可以做一接口回調(diào)反饋給UI層
/**
* 登錄tigase服務(wù)器
*
* @param userName 用戶名
* @param pwd 密碼
*/
public void loginTigase(String userName, String pwd) {
try {
if (!connection.isConnected()) {
connection.connect();
}
} catch (Exception e) {
e.printStackTrace();
return;
}
if (connection.isConnected()) {
try {
connection.login(userName, pwd, "android");
if (connection.isAuthenticated()) {
// 允許自動連接
ReconnectionManager reconnectionManager = ReconnectionManager.getInstanceFor(connection);
// 重聯(lián)間隔5秒
reconnectionManager.setFixedDelay(5);
reconnectionManager.enableAutomaticReconnection();//開啟重聯(lián)機(jī)制
// 維持ping
PingManager.setDefaultPingInterval(10);
PingManager pingManager = PingManager.getInstanceFor(connection);
// 監(jiān)聽連接狀態(tài)
pingManager.registerPingFailedListener(connectListener);
//獲取、校驗服務(wù)器時間
TimeInfo.checkServerIntervalTime(connection);
} else {
}
} catch (XMPPException | IOException e) {
e.printStackTrace();
} catch (SmackException e) {
e.printStackTrace();
}
} else {
}
}
時間校驗工具類
public class TimeInfo {
private static long serverIntervalTime;
/**
* 獲取本地時間與服務(wù)器的時間差绣张,用于消息時間驗證
*/
public static long checkServerIntervalTime(XMPPTCPConnection connection) {
final EntityTimeManager timeManager = EntityTimeManager.getInstanceFor(connection);
EntityTimeManager.setAutoEnable(true);
try {
String utcTime = timeManager.getTime(URConstant.xmppIp).getUtc();
//返回時區(qū)
String tzo = timeManager.getTime(URConstant.xmppIp).getTzo();
long serverTime = utc2Local(utcTime, tzo);
return serverTime - System.currentTimeMillis();
} catch (SmackException.NoResponseException | XMPPException.XMPPErrorException | SmackException.NotConnectedException e) {
e.printStackTrace();
return 0;
}
}
/**
* 函數(shù)功能描述:UTC時間轉(zhuǎn)本地時間格式
*
* @param utcTime UTC時間格式
* @param pysj 時區(qū)
* @return 本地時間格式的時間
* eg:utc2Local("2017-06-14 09:37:50.788+08:00", "yyyy-MM-dd HH:mm:ss.SSSXXX", "yyyy-MM-dd HH:mm:ss.SSS")
*/
public static long utc2Local(String utcTime, String pysj) {
@SuppressLint("SimpleDateFormat")
SimpleDateFormat utcFormater = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");//UTC時間格式
utcFormater.setTimeZone(TimeZone.getTimeZone("UTC"));
Date gpsUTCDate = null;
try {
gpsUTCDate = utcFormater.parse(utcTime);
} catch (ParseException e) {
e.printStackTrace();
return System.currentTimeMillis();
}
@SuppressLint("SimpleDateFormat")
SimpleDateFormat localFormater = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//當(dāng)?shù)貢r間格式
localFormater.setTimeZone(TimeZone.getTimeZone("GMT" + pysj));
String localTime = localFormater.format(gpsUTCDate.getTime());
Date date;
try {
date = localFormater.parse(localTime);
} catch (ParseException e) {
e.printStackTrace();
return System.currentTimeMillis();
}
return date.getTime();
}
}
- 登出
/**
* XMPP登出 將流置為空答渔,新用戶重新初始化
*/
public void logOut() {
URLog.d("XMPPConnectionManager 退出登陸");
//這里需要先將登陸狀態(tài)改變?yōu)椤半x線”,再斷開連接侥涵,不然在后臺還是上線的狀態(tài)
Presence presence = new Presence(Presence.Type.unavailable);
try {
connection.sendPacket(presence);
if (connection != null) {
connection.disconnect();
connection = null;
}
} catch (SmackException.NotConnectedException e) {
e.printStackTrace();
}
}
*注銷
/**
* 注銷前用戶登錄
*
* @return
*/
private boolean deleteUser() {
try {
try {
if (!connection.isConnected()) {
connection.connect();
}
} catch (Exception e) {
e.printStackTrace();
return false;
}
AccountManager.getInstance(connection).deleteAccount();
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}
感悟
這個項目讓我知道日志的重要性沼撕,熟悉XMPP協(xié)議報文是非常必要的,要不然出現(xiàn)問題了就是一臉懵逼不知道從哪排查起芜飘。