- 在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这个类中做简要的处理即可修改。源码如下:
/* * Copyright (C) 2010 Moduad Co., Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.androidpn.client; import java.util.ArrayList; import java.util.List; import java.util.UUID; import java.util.concurrent.Future; import org.jivesoftware.smack.ConnectionConfiguration; import org.jivesoftware.smack.ConnectionListener; import org.jivesoftware.smack.PacketListener; import org.jivesoftware.smack.XMPPConnection; import org.jivesoftware.smack.XMPPException; import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode; import org.jivesoftware.smack.filter.AndFilter; import org.jivesoftware.smack.filter.PacketFilter; import org.jivesoftware.smack.filter.PacketIDFilter; import org.jivesoftware.smack.filter.PacketTypeFilter; import org.jivesoftware.smack.packet.IQ; import org.jivesoftware.smack.packet.Packet; import org.jivesoftware.smack.packet.Registration; import org.jivesoftware.smack.provider.ProviderManager; import android.content.Context; import android.content.SharedPreferences; import android.content.SharedPreferences.Editor; import android.os.Handler; import android.util.Log; /** * This class is to manage the XMPP connection between client and server. * * @author Sehwan Noh ([email protected]) */ public class XmppManager { private static final String LOGTAG = LogUtil.makeLogTag(XmppManager.class); private static final String XMPP_RESOURCE_NAME = "AndroidpnClient"; private Context context; private NotificationService.TaskSubmitter taskSubmitter; private NotificationService.TaskTracker taskTracker; private SharedPreferences sharedPrefs; private String xmppHost; private int xmppPort; private XMPPConnection connection; private String username; private String password; private ConnectionListener connectionListener; private PacketListener notificationPacketListener; private Handler handler; private List<Runnable> taskList; private boolean running = false; private Future<?> futureTask; private Thread reconnection; public XmppManager(NotificationService notificationService) { context = notificationService; taskSubmitter = notificationService.getTaskSubmitter(); taskTracker = notificationService.getTaskTracker(); sharedPrefs = notificationService.getSharedPreferences(); xmppHost = sharedPrefs.getString(Constants.XMPP_HOST, "localhost"); xmppPort = sharedPrefs.getInt(Constants.XMPP_PORT, 5222); username = sharedPrefs.getString(Constants.XMPP_USERNAME, ""); password = sharedPrefs.getString(Constants.XMPP_PASSWORD, ""); connectionListener = new PersistentConnectionListener(this); notificationPacketListener = new NotificationPacketListener(this); handler = new Handler(); taskList = new ArrayList<Runnable>(); reconnection = new ReconnectionThread(this); } public Context getContext() { return context; } public void connect() { Log.d(LOGTAG, "connect()..."); submitLoginTask(); } public void disconnect() { Log.d(LOGTAG, "disconnect()..."); terminatePersistentConnection(); } public void terminatePersistentConnection() { Log.d(LOGTAG, "terminatePersistentConnection()..."); Runnable runnable = new Runnable() { final XmppManager xmppManager = XmppManager.this; public void run() { if (xmppManager.isConnected()) { Log.d(LOGTAG, "terminatePersistentConnection()... run()"); xmppManager.getConnection().removePacketListener( xmppManager.getNotificationPacketListener()); xmppManager.getConnection().disconnect(); } xmppManager.runTask(); } }; addTask(runnable); } public XMPPConnection getConnection() { return connection; } public void setConnection(XMPPConnection connection) { this.connection = connection; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public ConnectionListener getConnectionListener() { return connectionListener; } public PacketListener getNotificationPacketListener() { return notificationPacketListener; } public void startReconnectionThread() { synchronized (reconnection) { if (!reconnection.isAlive()) { reconnection.setName("Xmpp Reconnection Thread"); reconnection.start(); } } } public Handler getHandler() { return handler; } public void reregisterAccount() { removeAccount(); submitLoginTask(); runTask(); } public List<Runnable> getTaskList() { return taskList; } public Future<?> getFutureTask() { return futureTask; } public void runTask() { Log.d(LOGTAG, "runTask()..."); synchronized (taskList) { running = false; futureTask = null; if (!taskList.isEmpty()) { Runnable runnable = (Runnable) taskList.get(0); taskList.remove(0); running = true; futureTask = taskSubmitter.submit(runnable); if (futureTask == null) { taskTracker.decrease(); } } } taskTracker.decrease(); Log.d(LOGTAG, "runTask()...done"); } private String newRandomUUID() { String uuidRaw = UUID.randomUUID().toString(); return uuidRaw.replaceAll("-", ""); } private boolean isConnected() { return connection != null && connection.isConnected(); } private boolean isAuthenticated() { return connection != null && connection.isConnected() && connection.isAuthenticated(); } private boolean isRegistered() { return sharedPrefs.contains(Constants.XMPP_USERNAME) && sharedPrefs.contains(Constants.XMPP_PASSWORD); } private void submitConnectTask() { Log.d(LOGTAG, "submitConnectTask()..."); addTask(new ConnectTask()); } private void submitRegisterTask() { Log.d(LOGTAG, "submitRegisterTask()..."); submitConnectTask(); addTask(new RegisterTask()); } private void submitLoginTask() { Log.d(LOGTAG, "submitLoginTask()..."); submitRegisterTask(); addTask(new LoginTask()); } private void addTask(Runnable runnable) { Log.d(LOGTAG, "addTask(runnable)..."); taskTracker.increase(); synchronized (taskList) { if (taskList.isEmpty() && !running) { running = true; futureTask = taskSubmitter.submit(runnable); if (futureTask == null) { taskTracker.decrease(); } } else { taskList.add(runnable); } } Log.d(LOGTAG, "addTask(runnable)... done"); } private void removeAccount() { Editor editor = sharedPrefs.edit(); editor.remove(Constants.XMPP_USERNAME); editor.remove(Constants.XMPP_PASSWORD); editor.commit(); } /** * A runnable task to connect the server. */ private class ConnectTask implements Runnable { final XmppManager xmppManager; private ConnectTask() { this.xmppManager = XmppManager.this; } public void run() { Log.i(LOGTAG, "ConnectTask.run()..."); if (!xmppManager.isConnected()) { // Create the configuration for this new connection ConnectionConfiguration connConfig = new ConnectionConfiguration( xmppHost, xmppPort); // connConfig.setSecurityMode(SecurityMode.disabled); connConfig.setSecurityMode(SecurityMode.required); connConfig.setSASLAuthenticationEnabled(false); connConfig.setCompressionEnabled(false); XMPPConnection connection = new XMPPConnection(connConfig); xmppManager.setConnection(connection); try { // Connect to the server connection.connect(); Log.i(LOGTAG, "XMPP connected successfully"); // packet provider ProviderManager.getInstance().addIQProvider("notification", "androidpn:iq:notification", new NotificationIQProvider()); } catch (XMPPException e) { Log.e(LOGTAG, "XMPP connection failed", e); } xmppManager.runTask(); } else { Log.i(LOGTAG, "XMPP connected already"); xmppManager.runTask(); } } } /** * A runnable task to register a new user onto the server. */ private class RegisterTask implements Runnable { final XmppManager xmppManager; private RegisterTask() { xmppManager = XmppManager.this; } public void run() { Log.i(LOGTAG, "RegisterTask.run()..."); //如果账号不存在的话,随机生成一个uuid的用户名和mima if (!xmppManager.isRegistered()) { final String newUsername = newRandomUUID(); final String newPassword = newRandomUUID(); // final String newUsername = "af100042487d4b06a49adda8c3a82d41"; // final String newPassword = "af100042487d4b06a49adda8c3a82d41"; Registration registration = new Registration(); PacketFilter packetFilter = new AndFilter(new PacketIDFilter( registration.getPacketID()), new PacketTypeFilter( IQ.class)); PacketListener packetListener = new PacketListener() { public void processPacket(Packet packet) { Log.d("RegisterTask.PacketListener", "processPacket()....."); Log.d("RegisterTask.PacketListener", "packet=" + packet.toXML()); if (packet instanceof IQ) { IQ response = (IQ) packet; if (response.getType() == IQ.Type.ERROR) { if (!response.getError().toString().contains( "409")) { Log.e(LOGTAG, "Unknown error while registering XMPP account! " + response.getError() .getCondition()); } } else if (response.getType() == IQ.Type.RESULT) { xmppManager.setUsername(newUsername); xmppManager.setPassword(newPassword); Log.d(LOGTAG, "username=" + newUsername); Log.d(LOGTAG, "password=" + newPassword); Editor editor = sharedPrefs.edit(); editor.putString(Constants.XMPP_USERNAME, newUsername); editor.putString(Constants.XMPP_PASSWORD, newPassword); editor.commit(); Log .i(LOGTAG, "Account registered successfully"); xmppManager.runTask(); } } } }; connection.addPacketListener(packetListener, packetFilter); registration.setType(IQ.Type.SET); // registration.setTo(xmppHost); // Map<String, String> attributes = new HashMap<String, String>(); // attributes.put("username", rUsername); // attributes.put("password", rPassword); // registration.setAttributes(attributes); registration.addAttribute("username", newUsername); registration.addAttribute("password", newPassword); connection.sendPacket(registration); } else { Log.i(LOGTAG, "Account registered already"); xmppManager.runTask(); } } } /** * A runnable task to log into the server. */ private class LoginTask implements Runnable { final XmppManager xmppManager; private LoginTask() { this.xmppManager = XmppManager.this; } public void run() { Log.i(LOGTAG, "LoginTask.run()..."); if (!xmppManager.isAuthenticated()) { Log.d(LOGTAG, "username=" + username); Log.d(LOGTAG, "password=" + password); try { xmppManager.getConnection().login( xmppManager.getUsername(), xmppManager.getPassword(), XMPP_RESOURCE_NAME); Log.d(LOGTAG, "Loggedn in successfully"); // connection listener if (xmppManager.getConnectionListener() != null) { xmppManager.getConnection().addConnectionListener( xmppManager.getConnectionListener()); } // packet filter PacketFilter packetFilter = new PacketTypeFilter( NotificationIQ.class); // packet listener PacketListener packetListener = xmppManager .getNotificationPacketListener(); connection.addPacketListener(packetListener, packetFilter); //判断是否处于连接状态(添加) if(!getConnection().isConnected()) { xmppManager.runTask(); } xmppManager.runTask(); } catch (XMPPException e) { Log.e(LOGTAG, "LoginTask.run()... xmpp error"); Log.e(LOGTAG, "Failed to login to xmpp server. Caused by: " + e.getMessage()); String INVALID_CREDENTIALS_ERROR_CODE = "401"; String errorMessage = e.getMessage(); if (errorMessage != null && errorMessage .contains(INVALID_CREDENTIALS_ERROR_CODE)) { xmppManager.reregisterAccount(); return; } xmppManager.startReconnectionThread(); } catch (Exception e) { Log.e(LOGTAG, "LoginTask.run()... other error"); Log.e(LOGTAG, "Failed to login to xmpp server. Caused by: " + e.getMessage()); xmppManager.startReconnectionThread(); } //添加 xmppManager.runTask(); } else { Log.i(LOGTAG, "Logged in already"); xmppManager.runTask(); } } } }
新添加代码450-454行和477行
还有一个问题是:当客户端的用户有不在线的时候,消息应怎么进行推送,是直接忽略呢还是下次登录的时候在进行推送,想qq那样,很显然对已一个具体的实用项目来说是不能忽略的,那么怎么进行消息的离线推送呢,下次告诉大家,因为我现在还没解决,不过快了。和大家说下我的思路吧:在androidpn服务端有一个UserController这个类,源码如下:
View Code /* * Copyright (C) 2010 Moduad Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ package org.androidpn.server.console.controller; import java.util.List; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.androidpn.server.model.User; import org.androidpn.server.service.ServiceLocator; import org.androidpn.server.service.UserService; import org.androidpn.server.xmpp.presence.PresenceManager; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.mvc.multiaction.MultiActionController; /** * A controller class to process the user related requests. * * @author Sehwan Noh ([email protected]) */ public class UserController extends MultiActionController { private UserService userService; public UserController() { userService = ServiceLocator.getUserService(); } //用户列表 public ModelAndView list(HttpServletRequest request, HttpServletResponse response) throws Exception { PresenceManager presenceManager = new PresenceManager(); List<User> userList = userService.getUsers(); for (User user : userList) { if (presenceManager.isAvailable(user)) { // Presence presence = presenceManager.getPresence(user); user.setOnline(true); } else { user.setOnline(false); } // logger.debug("user.online=" + user.isOnline()); } ModelAndView mav = new ModelAndView(); mav.addObject("userList", userList); mav.setViewName("user/list"); return mav; } }
该源码里面有用户是否在线的判断,我们只要将用户的列表取出来,如果用户在线就将消息进行推送,如果有不在线的用户,我们就把该消息放到缓存中(也可以放到数据库中更加保险),当然为了防止用户过长没有登陆系统,导致下次登录时出现过多托送过来的消息,我们还可以在服务端进行设置时间,比如服务端只缓存近N天的消息,利用
session.getCreationDate(); session.getLastActiveDate();
这两句代码应该可以完成,本人没尝试过,还不知道。效果如如下: