android推送之androidpn
来源:互联网 发布:淘宝店铺怎么申请花呗 编辑:程序博客网 时间:2024/05/17 01:02
最近公司需要做推送消息,网上找了一段时间,除非用第三方,要不然感觉还是用androidpn比较方便,但是实际开发运用时碰到不到问题和BUG,在这里总结记录下,以免日后忘记。
官方的数据库用的是jetty,我在网上下了tomcat版的,我弄到百度网盘了,有需要的可以下载,点击打开链接
首先为了能运行起来,先修改客户端的res/raw/androidpn.properties这个文件把xmppHost改成你服务器的IP就可以了,要是用模拟器的就改成10.0.2.2
接着改服务端的jdbc.properties,这个主要就是账号密码修改下基本就可以了。这时候你的客户端和服务端就可以运行起来了。
接下来就说说开发时遇到的问题吧。
服务端重启客户端不重启就无法连接上的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;@Overridepublic void onPreExecute() {// TODO Auto-generated method stub}@Overridepublic String doInBackground(String... params) {//初始化数据notificationId = params[0];apiKey= params[1];title= params[2];message= params[3];uri= params[4];//重复定义notificationnotification = 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 blocke.printStackTrace();}return null;}@Overridepublic 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);}@Overridepublic void onProgressUpdate(Integer... values) {// TODO Auto-generated method stub}}).execute( notificationId, apiKey, title, message, uri);return;}
暂时就这些了,因为公司的原因不能给大家修改后的源码,以上的代码都只给出了关键的部分,具体的还要大家自己动手去弄,有不明白的我们可以交流,我也是刚开始弄android,有不好的地方或者还有什么bug也欢迎大家指正。
- android推送之androidpn
- Android消息推送之AndroidPN
- android 之 Androidpn 消息推送总结
- Android推送框架 androidpn
- android消息推送(androidpn)
- androidpn搭建android推送
- Android 推送之Androidpn项目分析(一)
- 开源项目之Android 推送框架(androidpn)
- Android 推送之Androidpn项目分析(一)
- 开源项目之Android 推送框架(androidpn)
- 开源项目之Android 推送框架(androidpn)
- Android的推送方案AndroidPN
- Android消息推送的Androidpn实现方式:androidpn
- androidpn推送
- androidpn推送
- android下服务器推送实现 androidpn分析
- 使用Androidpn实现Android消息推送
- 使用Androidpn实现Android消息推送
- linux中的各种包的安装与卸载的方式
- Lua程序设计第二版(笔记) 第七章迭代器与泛型for
- OCP-1Z0-051-V9.02-119题
- C++ 的嵌套类模板的特化定义不允许写在类定义的范围内
- div显示滚动条
- android推送之androidpn
- 开通博客第一天
- trainging contest#1(2011大连现场赛)C BY bly
- iPhone&iPad DFU及恢复模式刷机、降级教程
- jquery-显示效果-淡入淡出(二)
- 2014趋势科技校招9月23日南京笔试题目答案
- Z-stack 射频数据发送最大个数是101字节
- mysql 的 插入数据乱码问题
- 【让我们一路修行,一路祈福】