`

AndroidPn服务端部分bug解决方案

阅读更多

目前推送的情况已经大致可以了,可以正常推送。但是要在实际生产中使用,要改进很多地方。

原本的版本,是不会对消息重新发送的。消息如果丢失,或者用户没有在线,消息也不会重新的发送。所以,这些问题都是要解决的。

网上也有很多的讨论,是关于这几种情况的。CSDN有个名为“大饼馒头蘸大米”的程序员,对这些问题的思路也不错,是采取的对未发送信息进行存库,并且用state来标记信息是否发送,来进行处理的。

本人是采取的另外一种方式,这种方式,是某位网友最早提出来的。对离线消息,就是发送后存库,同时要记录用户的信息,已便于进行下次登录的发送。对于发送出去,某种原因丢失的情况,是采取发送三次的情况(这思路是老大提出的),我的设计是,发送后,开启重发的线程,等待几秒,如果客户端有回应的话,这条就不需要再发。没回应就存下来,等下次连接上再发。

from http://www.cnblogs.com/juepei/p/3921448.html

 

 

[pool-1-thread-1] ERROR: org.androidpn.server.xmpp.net.XmppIoHandlerexceptionCaught : java.lang.ExceptionInInitializerError

这个是第一个异常,这个异常发生之后,会接着很多个如下异常:

[pool-1-thread-10] ERROR: org.androidpn.server.xmpp.net.XmppIoHandlerexceptionCaught : java.lang.NoClassDefFoundError: Could not initialize class org.androidpn.server.xmpp.ssl.SSLConfig

 

[java] view plaincopy
 
  1. private static URL classPath ;  
  2.   
  3.  static {  
  4.           
  5.     storeType = Config.getString("xmpp.ssl.storeType""JKS");  
  6.         keyStoreLocation = Config.getString("xmpp.ssl.keystore""conf"  
  7.                 + File.separator + "security" + File.separator + "keystore");  
  8.         keyStoreLocation = <span style="color:#ff0000;">classPath</span>.getPath() + File.separator  
  9.                 + keyStoreLocation;  
  10.         keyPass = Config.getString("xmpp.ssl.keypass""changeit");  
  11.         trustStoreLocation = Config.getString("xmpp.ssl.truststore""conf"  
  12.                 + File.separator + "security" + File.separator + "truststore");  
  13.         trustStoreLocation = classPath.getPath()  
  14.                 + File.separator + trustStoreLocation;  
  15.         trustPass = Config.getString("xmpp.ssl.trustpass""changeit");  
  16.         <span style="color:#ff0000;">classPath</span> = SSLConfig.class.getResource("/");  


 

看到classPath,很明显,这个classPath会引起空指针异常,所以,只要把classPath = SSLConfig.class.getResource("/");往上提,提到static块里面的第一行,就ok了。

 

 

[pool-1-thread-1] ERROR: org.androidpn.server.xmpp.ssl.SSLConfig<clinit> : SSLConfig startup problem.

这一个错误由路径错误引起,因为编码格式的问题,如果路径中存在空格,会变成%20这样的字符,所以只要确保路径正确,就ok了。Bug出现还是在classPath的初始化上面:

 

 

[java] view plaincopy
 
  1. // Load keystore  
  2.         try {  
  3.             keyStore = KeyStore.getInstance(storeType);  
  4.             keyStore.load(new FileInputStream(URLDecoder.decode(keyStoreLocation)), keyPass  
  5.                     .toCharArray());  
  6.         }   



 

 

还是在静态初始化块这里,把keyStoreLocation换成这种初始方式就不会出现找不到路径这样的错误了。

 

 

 

服务端重启客户端不重启就无法连接上的BUG

 

 

 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 { 
             //解决服务器端重启后,客户端不能成功连接androidpn服务器 
             runTask(); 
                 taskList.add(runnable); 
             } 
         } 
         Log.d(LOGTAG, "addTask(runnable)... done"); 
    } 

 

把客户端的随机生成的UUID代码修改为自己应用的用户名密码

 

将org.androidpn.client.XmppManager中

 

final String newUsername = newRandomUUID();
final String newPassword = newRandomUUID();

 

 

修改为自己应用的用户名密码

把客户端的随机生成的UUID代码,改成把设备的id或者mac(device/mac)作为用户名,会出现重复插入的错误.

修改服务端org.androidpn.server.service.impl. UserServiceImpl这个类

public User saveUser(User user) throws UserExistsException {
        try {
        	//判断用户是否存在
        	user = getUserByUsername(user.getUsername());
        	
        } catch (DataIntegrityViolationException e) {
            e.printStackTrace();
            log.warn(e.getMessage());
            throw new UserExistsException("User '" + user.getUsername()
                    + "' already exists!");
        } catch (EntityExistsException e) { // needed for JPA
            e.printStackTrace();
            log.warn(e.getMessage());
            throw new UserExistsException("User '" + user.getUsername()
                    + "' already exists!");
        }catch (UserNotFoundException e) {
        		return userDao.saveUser(user);
		}
        return user;
    }



我这里采用捕获异常的方法

 

推送多次只显示最后一次的的问题:

 

修改客户端Notifier类中notify方法中的PendingIntent.getActivity方法的第二个参数每次不同就可以解决了,我是定义一个静态变量后每次+1解决。

 

PendingIntent contentIntent = PendingIntent.getActivity(context, notifyNum++,intent, PendingIntent.FLAG_UPDATE_CURRENT);

 

 

服务器连接不上出现xmpp connection failed xmpp502错误:


 

搞了半天经别别人提示才发现服务端的5222端口没开,根本ping不上

 

最后让服务端的人员把端口打开下解决了。

 

 

服务器中文乱码:

 

发送方先

 

String title = URLEncoder.encode("通知","UTF-8");
title = StringUtils.replace(title, "%", "@$");


接收方在org.androidpn.server.console.controller.NotificationController中

 

 

String title = ServletRequestUtils.getStringParameter(request, "title");
title = StringUtils.replace(title, "@$", "%");

 

 

已经登陆过一次,更换用户登陆但是显示登陆者还是第一次登陆的用户:

org.androidpn.client.XmppManager中

username = sharedPrefs.getString(Constants.XMPP_USERNAME, "");
password = sharedPrefs.getString(Constants.XMPP_PASSWORD, "");

 

换成自己的用户名和密码,然后在程序启动service的地方判断下,因为我的应用是登陆后启动的service,所以登录需要判断下原来的service是否启动,如果已经启动了就要关掉下再开,否则无法更新用户

/**
	 * 启动推送相关服务
	 * @Description:     
	 * @return void
	 */
	private void startAndroidpnServer(){
		serviceManager = new ServiceManager(this);
        serviceManager.setNotificationIcon(R.drawable.ic_launcher);
        RunningServiceInfo runningServiceInfo = ServiceUtil.isServiceRunning(MainActivity.this, "org.androidpn.client.NotificationService");
        if(runningServiceInfo == null){
        	serviceManager.startService();
        }else{
        	// 获得该Service的组件信息 可能是pkgname/servicename
        	ComponentName serviceCMP = runningServiceInfo.service;
        	// 设置该service的组件信息
			Intent intent = new Intent();
			intent.setComponent(serviceCMP);
			stopService(intent);
			serviceManager.startService();
        }
	}
	/**
	 * 判断某个service是否启动
	 * @Description: 
	 * @param mContext
	 * @param className
	 * @return    
	 * @return 如果存在返回service否则返回null
	 */
	public static ActivityManager.RunningServiceInfo isServiceRunning(Context mContext,String className) {

        ActivityManager activityManager = (ActivityManager)
        mContext.getSystemService(Context.ACTIVITY_SERVICE); 
        List<ActivityManager.RunningServiceInfo> serviceList 
                   = activityManager.getRunningServices(30);

        if (!(serviceList.size()>0)) {
            return null;
        }
        ActivityManager.RunningServiceInfo runningService = null;
        for (int i=0; i<serviceList.size(); i++) {
        	if (serviceList.get(i).service.getClassName()
.equals(className) == true) {
            	runningService = serviceList.get(i);
                break;
            }
        }
        return runningService;
    }

 

离线推送功能:

 

主要用到org.androidpn.server.dao.hibernate,

org.androidpn.server.dao

org.androidpn.server.model

org.androidpn.server.service.impl

org.androidpn.server.service

这5个包用来存储离线数据,配置spring-config.xml,hibernate.cfg.xml,这里代码太多又很简单,照着它原来的例子写就好了,这里就不贴了。

修改org.androidpn.server.xmpp.push.NotificationManager,在session没连上时候将数据存入数据库

 

public void sendBroadcast(String apiKey, String title, String message,
            String uri) {
        log.debug("sendBroadcast()...");
        
        //给所有注册过的用户发送消息
        IQ notificationIQ = createNotificationIQ(apiKey, title, message, uri);
        UserService userService = ServiceLocator.getUserService();
        for (User user : userService.getUsers()) {
        	
        	String username = user.getUsername();
        	
        	ClientSession session = sessionManager.getSession(username);
            if (session != null && session.getPresence().isAvailable()) {
                    notificationIQ.setTo(session.getAddress());
                    session.deliver(notificationIQ);
            }else{
            	UserPushMessageService msgService = (UserPushMessageService) ServiceLocator.getService("userPushMessageService");
            	JSONObject msg = new JSONObject();
            	msg.put("apiKey", apiKey);
            	msg.put("username", username);
            	msg.put("title", title);
            	msg.put("message", message);
            	msg.put("uri", uri);
            	
            	msgService.addMsg(username, msg);
            }
		}
        
        
    }

 

 

最后在org.androidpn.server.xmpp.handler. PresenceUpdateHandler类的

public void process(Packet packet) {
        ClientSession session = sessionManager.getSession(packet.getFrom());

        try {
            Presence presence = (Presence) packet;
            Presence.Type type = presence.getType();

            if (type == null) { // null == available
                if (session != null
                        && session.getStatus() == Session.STATUS_CLOSED) {
                    log.warn("Rejected available presence: " + presence + " - "
                            + session);
                    return;
                }

                if (session != null) {
                    session.setPresence(presence);
                    
                    //登录成功后发送离线消息
                    NotificationManager notificationManager = new NotificationManager();
                    String username = session.getUsername();
                    UserPushMessageService msgService = (UserPushMessageService) ServiceLocator.getService("userPushMessageService");
                    String msgsStr = msgService.getMessages(username);
                    if( msgsStr != null ){
                    	JSONArray msgs = JSONArray.fromObject(msgsStr);
                    	for (int i = 0; i < msgs.size(); i++) {
    						JSONObject msg = msgs.optJSONObject(i);
    						notificationManager.sendNotifcationToUser(msg.optString("apiKey"),username, msg.optString("title"), msg.optString("message"), msg.optString("uri"));
    					}
                    	msgService.removeMessages(username);
                    }
                    
                    if (!session.isInitialized()) {
                        // initSession(session);
                        session.setInitialized(true);
                    }
                }

            } else if (Presence.Type.unavailable == type) {

                if (session != null) {
                    session.setPresence(presence);
                }

            } else {
                presence = presence.createCopy();
                if (session != null) {
                    presence.setFrom(new JID(null, session.getServerName(),
                            null, true));
                    presence.setTo(session.getAddress());
                } else {
                    JID sender = presence.getFrom();
                    presence.setFrom(presence.getTo());
                    presence.setTo(sender);
                }
                presence.setError(PacketError.Condition.bad_request);
                PacketDeliverer.deliver(presence);
            }

        } catch (Exception e) {
            log.error("Internal server error. Triggered by packet: " + packet,
                    e);
        }
    }

 

客户端接收消息跳转到指定的activity:

修改org.androidpn.client.Notifier类的notify(String notificationId, String apiKey,String title,String message, String uri)方法,将 该方法中的Intent修改为自己需要的Intent即可。

客户端重复登陆的问题:

这个问题困扰我挺长时间,一开始是想在XmppManager的login前发送个消息结果有时可以有时失败很不稳定,最后决定在客户端service启动前判断该用户是否登陆,如果登陆则发送消息给最先登陆的用户告诉他他的账号被登陆了。

服务端的代码,我在org.androidpn.server.console.controller.UserController里加了个方法

 

/**
     * 判断重复登陆
     * @param request
     * @param response
     * @return
     * @throws Exception
     */
    public ModelAndView checkRepetition(HttpServletRequest request,
            HttpServletResponse response) throws Exception {
    	
    	String userCode = request.getParameter("userCode");
    	if(userCode!=null){
	    	SessionManager sessmg = SessionManager.getInstance();
	    	for (Iterator iterator = sessmg.getSessions().iterator(); iterator.hasNext();) {
	    		ClientSession sess = (ClientSession) iterator.next();
				String name = sess.getUsername();
				if(userCode.equals(name)){
					
					String apiKey = Config.getString("apiKey", "");
					String title = "通知";
					String message = "您的账号在其他设备登录,如非本人操作,请注意账号安全,及时修改密码。";
					String uri = "LoginActivity";
					NotificationManager notificationManager = new NotificationManager();
					notificationManager.sendNotifcationToUser(apiKey, name, title, message, uri);

					response.getOutputStream().println("true");
					
					return null;
				}
			}
    	}
    	response.getOutputStream().println("false");
        return null;
}

 

客户端首先在启动之前先异步访问上面的接口,如果重复登录则会发送消息给客户端,代码我这里不贴了,我用的是自己封装的AsyncTask类,贴出来你们也不能用,你们自己开个线程执行下就好了。

然后是在org.androidpn.client. Notifier的方法中加了个判断用来判断是重复登陆的消息提示,然后跳转到登陆界面,这里我延迟了30秒才提示,要是马上提示怕两个人同时登录一个账号,一个用户发现另一个用户登陆后自己又登录,这是可能第二个用户还没建立好session连接,这样就无法发送给第二个用户,讲的有点乱,就是两个用户登录一个账号的问题,用用就会发现了。

if (uri.equals("LoginActivity")) {
				
				new NetWork<String, Integer, String>().setNetWorkListen(
						new NetWorkListen<String, Integer, String>() {
					
					Intent intent;
					String notificationId;
					String apiKey;
					String title;
					String message;
					String uri;
					Notification notification;
					
					@Override
					public void onPreExecute() {
						// TODO Auto-generated method stub
						
					}

					@Override
					public String doInBackground(String... params) {
						
						//初始化数据
						notificationId = params[0];
						apiKey= params[1];
						title= params[2];
						message= params[3];
						uri= params[4];
						
						//重复定义notification
						notification = new Notification();
						notification.icon = getNotificationIcon();
						notification.defaults = Notification.DEFAULT_LIGHTS;
						if (isNotificationSoundEnabled()) {
							notification.defaults |= Notification.DEFAULT_SOUND;
						}
						if (isNotificationVibrateEnabled()) {
							notification.defaults |= Notification.DEFAULT_VIBRATE;
						}
						notification.flags |= Notification.FLAG_AUTO_CANCEL;
						notification.when = System.currentTimeMillis();
						notification.tickerText = message;
						
						try {
							//因为推送需要时间,所以延迟1分钟推送,保证后登陆的用户连接上
							Thread.sleep(30000);
						} catch (InterruptedException e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						}
						return null;
					}

					@Override
					public void onPostExecute(String result) {

						intent = new Intent(context,
			                    NotificationDetailsActivity.class);
			            intent.putExtra(Constants.NOTIFICATION_ID, notificationId);
			            intent.putExtra(Constants.NOTIFICATION_API_KEY, apiKey);
			            intent.putExtra(Constants.NOTIFICATION_TITLE, title);
			            intent.putExtra(Constants.NOTIFICATION_MESSAGE, message);
			            intent.putExtra(Constants.NOTIFICATION_URI, uri);
			            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
			            intent.setFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
			            intent.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
			            intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
			            intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
			            
			            PendingIntent contentIntent = PendingIntent.getActivity(context,
								notifyNum++, intent, PendingIntent.FLAG_UPDATE_CURRENT);

						notification.setLatestEventInfo(context, title, message,
								contentIntent);
						notificationManager.notify(random.nextInt(), notification);
						
					}

					@Override
					public void onProgressUpdate(Integer... values) {
						// TODO Auto-generated method stub
						
					}
				}).execute( notificationId,  apiKey,  title, message,  uri);
				return;
				
			} 

from http://blog.csdn.net/yhjsspz/article/details/11980635

 

 

分享到:
评论

相关推荐

    androidpn服务端项目源码已修改

    "AndroidPN服务端项目源码已修改"这个标题暗示了一个关于Android Push Notification (AndroidPN)的更新或优化,这是在Android应用开发中用于实现实时推送通知的技术。AndroidPN是基于XMPP(Extensible Messaging and...

    androidpn服务端,客户端.rar

    总之,这个资源包为开发者提供了一个完整的Android推送通知解决方案,包括服务端和客户端的源码,以及部署说明。开发者可以通过深入学习和实践,掌握AndroidPN的工作原理和实现方式,进一步提升自己的移动应用开发...

    androidpn 服务端和android端源代码

    "AndroidPN 服务端和Android端源代码"是关于Android Push Notification(AndroidPN)系统的一套开源实现,它包括服务端和客户端两部分。这个项目主要用于帮助开发者实现在Android设备上进行远程消息推送,这对于实时...

    androidpn客户端与服务端

    服务端通常由以下几部分组成: 1. **注册接口**:当一个新的Android设备需要接收推送通知时,它会通过API向服务端注册,提供一个唯一标识(如IMEI)和接收通知的通道(通常是Google Cloud Messaging,GCM,或者现在...

    androidpn服务端

    androidpn服务端源代码,布署在tomcat/webapp下即可,注意修改android_xmpp_server/resources/下的jdbc.properties数据库链接为你自己环境的数据库,第一次启动时,会自动建表的,不用关心。 客户端修正了androidpn...

    AndroidPn(客户端和服务端)

    AndroidPn是一个专门为Android平台设计的点对点(P2P)通信框架,它涵盖了服务端和客户端的实现,使得Android设备之间可以高效、安全地进行数据交换和通信。在这个框架中,服务端通常负责处理客户端的连接请求,管理...

    androidpn 客户端和服务端

    "AndroidPN 客户端和服务端"是一个完整的推送通知服务解决方案,主要针对Android平台。这个项目包括了客户端应用程序和服务器端的实现,旨在提供实时、可靠的信息推送功能,使得应用程序能够在用户不主动打开应用的...

    AndroidPn客户端和服务端

    Androidpn客户端 和服务端源码下载,实现了消息推送。具体使用方法可百度。

    androidpn客户端和服务端

    总的来说,AndroidPN提供了一套完整的解决方案,使得开发者可以轻松地在Android应用中实现推送通知功能。通过理解并掌握其服务端和客户端的工作原理,以及如何配置和使用MySQL数据库,可以有效地将推送通知集成到你...

    androidpn 客户端模拟代码

    本项目主要关注的是客户端的部分,特别是针对AndroidPN服务端的测试,通过模拟客户端代码来验证服务端功能的正确性。 首先,我们需要了解推送通知的基本概念。推送通知是移动应用开发中的一个重要特性,它允许...

    Androidpn-tomcat

    总结,AndroidPN-tomcat项目为开发者提供了一套基于Tomcat的稳定、可扩展的Android消息推送解决方案。通过合理的服务器配置和客户端集成,开发者可以轻松实现在Android应用中的远程通知功能,提升用户体验。同时,...

    Androidpn推送增强版

    总结,AndroidPN是一个强大的推送解决方案,尤其对于那些需要实时更新信息的应用程序来说,它可以极大地提高用户体验。通过理解服务端和客户端的工作原理,以及增强版的特性,开发者可以更有效地利用AndroidPN构建...

    androidpn-bin-server-0.5.0

    AndroidPN(Android Push ...总的来说,AndroidPN服务端是构建高效、可靠的Android推送通知解决方案的关键组成部分。通过对这个版本的深入理解和使用,开发者可以为他们的应用增添实时通知功能,提升用户体验。

    androidpn-tomcat-0.5.0

    总之,"androidpn-tomcat-0.5.0"项目是实现Android设备推送通知服务的一种解决方案,它结合了Tomcat服务器的稳定性和XMPP协议的实时性,为开发者提供了构建高效实时通信系统的基础。开发者可以通过深入理解和定制这...

    Android平台校园消息推送服务的设计与实现,基于AndroidPN实现的即时通信系统,Android服务端部分。.zip

    基于AndroidPN(Android Push Notification)的即时通信系统是解决这一问题的一种常见方案。AndroidPN是专门为Android设备设计的一个开源推送通知服务,它允许服务器向Android客户端发送消息,即使应用在后台或关闭...

    Android push notification 服务端源代码

    AndroidPN(Android Push Notification)是一个开源项目,专注于提供服务端的解决方案,帮助开发者实现推送通知功能。这个压缩包包含的是AndroidPN服务端的源代码,而非编译后的二进制文件,因此可以让你深入理解其...

    androidpn自动重连

    在本文中,我们将深入探讨如何解决AndroidPN服务端重启后客户端无法自动重连的问题。 首先,我们需要理解AndroidPN的工作原理。AndroidPN基于XMPP(Extensible Messaging and Presence Protocol)协议,这是一种...

    androidpn 消息推送客户端+服务器端

    总之,AndroidPN是一个强大的开源解决方案,它利用XMPP协议实现了Android设备的消息推送功能。理解其客户端和服务器端的运作机制,可以帮助开发者更有效地集成推送通知到自己的应用程序中。同时,熟悉XMPP协议和...

    androidpn-client 0.5 推送

    1. **服务端**:AndroidPN服务端主要负责接收应用服务器发送的推送消息,并将这些消息转发到注册的Android设备。服务端通常采用Java或PHP等后端语言实现,负责处理注册、注销、消息分发等功能。 2. **客户端**:...

Global site tag (gtag.js) - Google Analytics