最近公司需要做推送訊息,網上找了一段時間,除非用第三方,要不然感覺還是用androidpn比較方便,但是實際開發運用時碰到不到問題和BUG,在這裡總結記錄下,以免日後忘記。
官方的資料庫用的是jetty,我在網上下了tomcat版的,我弄到百度網盤了,有需要的可以下載,點擊開啟連結
首先為了能運行起來,先修改用戶端的res/raw/androidpn.properties這個檔案把xmppHost改成你伺服器的IP就可以了,要是用模擬器的就改成10.0.2.2
接著改服務端的jdbc.properties,這個主要就是帳號密碼修改下基本就可以了。這時候你的用戶端和服務端就可以運行起來了。
接下來就說說開發時遇到的問題吧。
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"); }
final String newUsername = newRandomUUID();final String newPassword = newRandomUUID();
修改為自己應用的使用者名稱密碼
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);
搞了半天經別別人提示才探索服務端的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
個包用來儲存離線資料,配置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); }} }
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); } }
org.androidpn.client.Notifier類的notify(String notificationId, String apiKey,String title,String message, String uri)方法,將 該方法中的Intent修改為自己需要的Intent即可。
的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也歡迎大家指正。