- 在XMPP消息推送这个问题上,网上已经有很多资料了,本人觉得很好的一篇资料是: http://www.iteye.com/topic/1117043
- 提供了一个连接下载源码: http://115.com/file/bhkfse3i#%20Androidpn.rar
- 很感谢前辈们的研究结果。
- 在源码的使用过程中要注意的地方有两点,网上的那篇资料好像忽略了一个重要的地方,就是要改resources文件夹下面的jdbc.properties,将里面关于数据库的配置改为自己的,另一个需要注意的地方就是改android端的ip了。
在项目部署到tomcat下之后,发现了不少的bug,其中一个就是当tomcat重新启动,客户端的连接将断开,不能进行自动重连。
对于这个BUG,我们可以在Androidpn-clieng下的XmppManager这个类中做简要的处理即可修改。源码如下:
1 /*
2 * Copyright (C) 2010 Moduad Co., Ltd.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16 package org.androidpn.client;
17
18 import java.util.ArrayList;
19 import java.util.List;
20 import java.util.UUID;
21 import java.util.concurrent.Future;
22
23 import org.jivesoftware.smack.ConnectionConfiguration;
24 import org.jivesoftware.smack.ConnectionListener;
25 import org.jivesoftware.smack.PacketListener;
26 import org.jivesoftware.smack.XMPPConnection;
27 import org.jivesoftware.smack.XMPPException;
28 import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode;
29 import org.jivesoftware.smack.filter.AndFilter;
30 import org.jivesoftware.smack.filter.PacketFilter;
31 import org.jivesoftware.smack.filter.PacketIDFilter;
32 import org.jivesoftware.smack.filter.PacketTypeFilter;
33 import org.jivesoftware.smack.packet.IQ;
34 import org.jivesoftware.smack.packet.Packet;
35 import org.jivesoftware.smack.packet.Registration;
36 import org.jivesoftware.smack.provider.ProviderManager;
37
38 import android.content.Context;
39 import android.content.SharedPreferences;
40 import android.content.SharedPreferences.Editor;
41 import android.os.Handler;
42 import android.util.Log;
43
44
45 /**
46 * This class is to manage the XMPP connection between client and server.
47 *
48 * @author Sehwan Noh ([email protected])
49 */
50 public class XmppManager {
51
52 private static final String LOGTAG = LogUtil.makeLogTag(XmppManager.class);
53
54 private static final String XMPP_RESOURCE_NAME = "AndroidpnClient";
55
56 private Context context;
57
58 private NotificationService.TaskSubmitter taskSubmitter;
59
60 private NotificationService.TaskTracker taskTracker;
61
62 private SharedPreferences sharedPrefs;
63
64 private String xmppHost;
65
66 private int xmppPort;
67
68 private XMPPConnection connection;
69
70 private String username;
71
72 private String password;
73
74 private ConnectionListener connectionListener;
75
76 private PacketListener notificationPacketListener;
77
78 private Handler handler;
79
80 private List<Runnable> taskList;
81
82 private boolean running = false;
83
84 private Future<?> futureTask;
85
86 private Thread reconnection;
87
88 public XmppManager(NotificationService notificationService) {
89 context = notificationService;
90 taskSubmitter = notificationService.getTaskSubmitter();
91 taskTracker = notificationService.getTaskTracker();
92 sharedPrefs = notificationService.getSharedPreferences();
93
94 xmppHost = sharedPrefs.getString(Constants.XMPP_HOST, "localhost");
95 xmppPort = sharedPrefs.getInt(Constants.XMPP_PORT, 5222);
96 username = sharedPrefs.getString(Constants.XMPP_USERNAME, "");
97 password = sharedPrefs.getString(Constants.XMPP_PASSWORD, "");
98
99 connectionListener = new PersistentConnectionListener(this);
100 notificationPacketListener = new NotificationPacketListener(this);
101
102 handler = new Handler();
103 taskList = new ArrayList<Runnable>();
104 reconnection = new ReconnectionThread(this);
105 }
106
107 public Context getContext() {
108 return context;
109 }
110
111 public void connect() {
112 Log.d(LOGTAG, "connect()...");
113 submitLoginTask();
114 }
115
116 public void disconnect() {
117 Log.d(LOGTAG, "disconnect()...");
118 terminatePersistentConnection();
119 }
120
121 public void terminatePersistentConnection() {
122 Log.d(LOGTAG, "terminatePersistentConnection()...");
123 Runnable runnable = new Runnable() {
124
125 final XmppManager xmppManager = XmppManager.this;
126
127 public void run() {
128 if (xmppManager.isConnected()) {
129 Log.d(LOGTAG, "terminatePersistentConnection()... run()");
130 xmppManager.getConnection().removePacketListener(
131 xmppManager.getNotificationPacketListener());
132 xmppManager.getConnection().disconnect();
133 }
134 xmppManager.runTask();
135 }
136
137 };
138 addTask(runnable);
139 }
140
141 public XMPPConnection getConnection() {
142 return connection;
143 }
144
145 public void setConnection(XMPPConnection connection) {
146 this.connection = connection;
147 }
148
149 public String getUsername() {
150 return username;
151 }
152
153 public void setUsername(String username) {
154 this.username = username;
155 }
156
157 public String getPassword() {
158 return password;
159 }
160
161 public void setPassword(String password) {
162 this.password = password;
163 }
164
165 public ConnectionListener getConnectionListener() {
166 return connectionListener;
167 }
168
169 public PacketListener getNotificationPacketListener() {
170 return notificationPacketListener;
171 }
172
173 public void startReconnectionThread() {
174 synchronized (reconnection) {
175 if (!reconnection.isAlive()) {
176 reconnection.setName("Xmpp Reconnection Thread");
177 reconnection.start();
178 }
179 }
180 }
181
182 public Handler getHandler() {
183 return handler;
184 }
185
186 public void reregisterAccount() {
187 removeAccount();
188 submitLoginTask();
189 runTask();
190 }
191
192 public List<Runnable> getTaskList() {
193 return taskList;
194 }
195
196 public Future<?> getFutureTask() {
197 return futureTask;
198 }
199
200 public void runTask() {
201 Log.d(LOGTAG, "runTask()...");
202 synchronized (taskList) {
203 running = false;
204 futureTask = null;
205 if (!taskList.isEmpty()) {
206 Runnable runnable = (Runnable) taskList.get(0);
207 taskList.remove(0);
208 running = true;
209 futureTask = taskSubmitter.submit(runnable);
210 if (futureTask == null) {
211 taskTracker.decrease();
212 }
213 }
214 }
215 taskTracker.decrease();
216 Log.d(LOGTAG, "runTask()...done");
217 }
218
219 private String newRandomUUID() {
220 String uuidRaw = UUID.randomUUID().toString();
221 return uuidRaw.replaceAll("-", "");
222 }
223
224 private boolean isConnected() {
225 return connection != null && connection.isConnected();
226 }
227
228 private boolean isAuthenticated() {
229 return connection != null && connection.isConnected()
230 && connection.isAuthenticated();
231 }
232
233 private boolean isRegistered() {
234 return sharedPrefs.contains(Constants.XMPP_USERNAME)
235 && sharedPrefs.contains(Constants.XMPP_PASSWORD);
236 }
237
238 private void submitConnectTask() {
239 Log.d(LOGTAG, "submitConnectTask()...");
240 addTask(new ConnectTask());
241 }
242
243 private void submitRegisterTask() {
244 Log.d(LOGTAG, "submitRegisterTask()...");
245 submitConnectTask();
246 addTask(new RegisterTask());
247 }
248
249 private void submitLoginTask() {
250 Log.d(LOGTAG, "submitLoginTask()...");
251 submitRegisterTask();
252 addTask(new LoginTask());
253 }
254
255 private void addTask(Runnable runnable) {
256 Log.d(LOGTAG, "addTask(runnable)...");
257 taskTracker.increase();
258 synchronized (taskList) {
259 if (taskList.isEmpty() && !running) {
260 running = true;
261 futureTask = taskSubmitter.submit(runnable);
262 if (futureTask == null) {
263 taskTracker.decrease();
264 }
265 } else {
266 taskList.add(runnable);
267 }
268 }
269 Log.d(LOGTAG, "addTask(runnable)... done");
270 }
271
272 private void removeAccount() {
273 Editor editor = sharedPrefs.edit();
274 editor.remove(Constants.XMPP_USERNAME);
275 editor.remove(Constants.XMPP_PASSWORD);
276 editor.commit();
277 }
278
279 /**
280 * A runnable task to connect the server.
281 */
282 private class ConnectTask implements Runnable {
283
284 final XmppManager xmppManager;
285
286 private ConnectTask() {
287 this.xmppManager = XmppManager.this;
288 }
289
290 public void run() {
291 Log.i(LOGTAG, "ConnectTask.run()...");
292
293 if (!xmppManager.isConnected()) {
294 // Create the configuration for this new connection
295 ConnectionConfiguration connConfig = new ConnectionConfiguration(
296 xmppHost, xmppPort);
297 // connConfig.setSecurityMode(SecurityMode.disabled);
298 connConfig.setSecurityMode(SecurityMode.required);
299 connConfig.setSASLAuthenticationEnabled(false);
300 connConfig.setCompressionEnabled(false);
301
302 XMPPConnection connection = new XMPPConnection(connConfig);
303 xmppManager.setConnection(connection);
304
305 try {
306 // Connect to the server
307 connection.connect();
308 Log.i(LOGTAG, "XMPP connected successfully");
309
310 // packet provider
311 ProviderManager.getInstance().addIQProvider("notification",
312 "androidpn:iq:notification",
313 new NotificationIQProvider());
314
315 } catch (XMPPException e) {
316 Log.e(LOGTAG, "XMPP connection failed", e);
317 }
318
319 xmppManager.runTask();
320
321 } else {
322 Log.i(LOGTAG, "XMPP connected already");
323 xmppManager.runTask();
324 }
325 }
326 }
327
328 /**
329 * A runnable task to register a new user onto the server.
330 */
331 private class RegisterTask implements Runnable {
332
333 final XmppManager xmppManager;
334
335 private RegisterTask() {
336 xmppManager = XmppManager.this;
337 }
338
339 public void run() {
340 Log.i(LOGTAG, "RegisterTask.run()...");
341
342 //如果账号不存在的话,随机生成一个uuid的用户名和mima
343 if (!xmppManager.isRegistered()) {
344 final String newUsername = newRandomUUID();
345 final String newPassword = newRandomUUID();
346 // final String newUsername = "af100042487d4b06a49adda8c3a82d41";
347 // final String newPassword = "af100042487d4b06a49adda8c3a82d41";
348
349 Registration registration = new Registration();
350
351 PacketFilter packetFilter = new AndFilter(new PacketIDFilter(
352 registration.getPacketID()), new PacketTypeFilter(
353 IQ.class));
354
355 PacketListener packetListener = new PacketListener() {
356
357 public void processPacket(Packet packet) {
358 Log.d("RegisterTask.PacketListener",
359 "processPacket().....");
360 Log.d("RegisterTask.PacketListener", "packet="
361 + packet.toXML());
362
363 if (packet instanceof IQ) {
364 IQ response = (IQ) packet;
365 if (response.getType() == IQ.Type.ERROR) {
366 if (!response.getError().toString().contains(
367 "409")) {
368 Log.e(LOGTAG,
369 "Unknown error while registering XMPP account! "
370 + response.getError()
371 .getCondition());
372 }
373 } else if (response.getType() == IQ.Type.RESULT) {
374 xmppManager.setUsername(newUsername);
375 xmppManager.setPassword(newPassword);
376 Log.d(LOGTAG, "username=" + newUsername);
377 Log.d(LOGTAG, "password=" + newPassword);
378
379 Editor editor = sharedPrefs.edit();
380 editor.putString(Constants.XMPP_USERNAME,
381 newUsername);
382 editor.putString(Constants.XMPP_PASSWORD,
383 newPassword);
384 editor.commit();
385 Log
386 .i(LOGTAG,
387 "Account registered successfully");
388 xmppManager.runTask();
389 }
390 }
391 }
392 };
393
394 connection.addPacketListener(packetListener, packetFilter);
395
396 registration.setType(IQ.Type.SET);
397 // registration.setTo(xmppHost);
398 // Map<String, String> attributes = new HashMap<String, String>();
399 // attributes.put("username", rUsername);
400 // attributes.put("password", rPassword);
401 // registration.setAttributes(attributes);
402 registration.addAttribute("username", newUsername);
403 registration.addAttribute("password", newPassword);
404 connection.sendPacket(registration);
405
406 } else {
407 Log.i(LOGTAG, "Account registered already");
408 xmppManager.runTask();
409 }
410 }
411 }
412
413 /**
414 * A runnable task to log into the server.
415 */
416 private class LoginTask implements Runnable {
417
418 final XmppManager xmppManager;
419
420 private LoginTask() {
421 this.xmppManager = XmppManager.this;
422 }
423
424 public void run() {
425 Log.i(LOGTAG, "LoginTask.run()...");
426
427 if (!xmppManager.isAuthenticated()) {
428 Log.d(LOGTAG, "username=" + username);
429 Log.d(LOGTAG, "password=" + password);
430
431 try {
432 xmppManager.getConnection().login(
433 xmppManager.getUsername(),
434 xmppManager.getPassword(), XMPP_RESOURCE_NAME);
435 Log.d(LOGTAG, "Loggedn in successfully");
436
437 // connection listener
438 if (xmppManager.getConnectionListener() != null) {
439 xmppManager.getConnection().addConnectionListener(
440 xmppManager.getConnectionListener());
441 }
442
443 // packet filter
444 PacketFilter packetFilter = new PacketTypeFilter(
445 NotificationIQ.class);
446 // packet listener
447 PacketListener packetListener = xmppManager
448 .getNotificationPacketListener();
449 connection.addPacketListener(packetListener, packetFilter);
450 //判断是否处于连接状态(添加)
451 if(!getConnection().isConnected())
452 {
453 xmppManager.runTask();
454 }
455 xmppManager.runTask();
456 } catch (XMPPException e) {
457 Log.e(LOGTAG, "LoginTask.run()... xmpp error");
458 Log.e(LOGTAG, "Failed to login to xmpp server. Caused by: "
459 + e.getMessage());
460 String INVALID_CREDENTIALS_ERROR_CODE = "401";
461 String errorMessage = e.getMessage();
462 if (errorMessage != null
463 && errorMessage
464 .contains(INVALID_CREDENTIALS_ERROR_CODE)) {
465 xmppManager.reregisterAccount();
466 return;
467 }
468 xmppManager.startReconnectionThread();
469
470 } catch (Exception e) {
471 Log.e(LOGTAG, "LoginTask.run()... other error");
472 Log.e(LOGTAG, "Failed to login to xmpp server. Caused by: "
473 + e.getMessage());
474 xmppManager.startReconnectionThread();
475 }
476 //添加
477 xmppManager.runTask();
478 } else {
479 Log.i(LOGTAG, "Logged in already");
480 xmppManager.runTask();
481 }
482
483 }
484 }
485
486 }
新添加代码450-454行和477行
还有一个问题是:当客户端的用户有不在线的时候,消息应怎么进行推送,是直接忽略呢还是下次登录的时候在进行推送,想qq那样,很显然对已一个具体的实用项目来说是不能忽略的,那么怎么进行消息的离线推送呢,下次告诉大家,因为我现在还没解决,不过快了。和大家说下我的思路吧:在androidpn服务端有一个UserController这个类,源码如下:
1 /*
2 * Copyright (C) 2010 Moduad Co., Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License along
15 * with this program; if not, write to the Free Software Foundation, Inc.,
16 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17 */
18 package org.androidpn.server.console.controller;
19
20 import java.util.List;
21
22 import javax.servlet.http.HttpServletRequest;
23 import javax.servlet.http.HttpServletResponse;
24
25 import org.androidpn.server.model.User;
26 import org.androidpn.server.service.ServiceLocator;
27 import org.androidpn.server.service.UserService;
28 import org.androidpn.server.xmpp.presence.PresenceManager;
29 import org.springframework.web.servlet.ModelAndView;
30 import org.springframework.web.servlet.mvc.multiaction.MultiActionController;
31
32 /**
33 * A controller class to process the user related requests.
34 *
35 * @author Sehwan Noh ([email protected])
36 */
37 public class UserController extends MultiActionController {
38
39 private UserService userService;
40
41 public UserController() {
42 userService = ServiceLocator.getUserService();
43 }
44
45 //用户列表
46 public ModelAndView list(HttpServletRequest request,
47 HttpServletResponse response) throws Exception {
48 PresenceManager presenceManager = new PresenceManager();
49 List<User> userList = userService.getUsers();
50 for (User user : userList) {
51 if (presenceManager.isAvailable(user)) {
52 // Presence presence = presenceManager.getPresence(user);
53 user.setOnline(true);
54 } else {
55 user.setOnline(false);
56 }
57 // logger.debug("user.online=" + user.isOnline());
58 }
59 ModelAndView mav = new ModelAndView();
60 mav.addObject("userList", userList);
61 mav.setViewName("user/list");
62 return mav;
63 }
64
65 }
该源码里面有用户是否在线的判断,我们只要将用户的列表取出来,如果用户在线就将消息进行推送,如果有不在线的用户,我们就把该消息放到缓存中(也可以放到数据库中更加保险),当然为了防止用户过长没有登陆系统,导致下次登录时出现过多托送过来的消息,我们还可以在服务端进行设置时间,比如服务端只缓存近N天的消息,利用
session.getCreationDate();
session.getLastActiveDate();
这两句代码应该可以完成,本人没尝试过,还不知道。效果如如下:
本文链接