当前位置: 代码迷 >> Android >> AndroidPn服务端一部分bug解决方案
  详细解决方案

AndroidPn服务端一部分bug解决方案

热度:50   发布时间:2016-04-28 02:46:09.0
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

?

?

  相关解决方案