Android推送進階課程學習筆記
今天在慕課網學習了Android進階課程推送的伺服器端處理回執的訊息 。這集課程主要介紹了,當伺服器往用戶端推送訊息的時候,用戶端需要發送一個回執回來確認收到了推送訊息才算一次完整的推送過程。
具體的實現方法為伺服器推送一個訊息到用戶端的時候,會產生一個相應的uuid標識這個訊息,並把這個訊息以及uuid儲存到資料庫中,用戶端收到訊息後,取出其中的uuid並將這個uuid發給伺服器端,服務端收到這個uuid,根據uuid到資料庫裡刪除了對應的訊息記錄,整個推送算完成。這裡先貼出比較核心的發送代碼
public void sendNotifcationToUser(String apiKey, String username, String title, String message, String uri) { log.debug("sendNotifcationToUser()..."); Random random = new Random(); //這個id就是用戶端發送回執對應的uuid String id = Integer.toHexString(random.nextInt()); IQ notificationIQ = createNotificationIQ(id, apiKey, title, message, uri); ClientSession session = sessionManager.getSession(username); if (session != null) { if (session.getPresence().isAvailable()) { notificationIQ.setTo(session.getAddress()); session.deliver(notificationIQ); } else{ saveNotification(apiKey, username, title, message, uri, id); } } //不管使用者存在不存在都需要將訊息存入資料庫,直到使用者收到訊息發送回饋之後再刪除 try { User user = mUserService.getUserByUsername(username); if(null != user){ saveNotification(apiKey, username, title, message, uri, id); } } catch (UserNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } }
可以看到,每次推送訊息給用戶端的時候都會做入庫操作。
同時,原始碼裡還有個商務邏輯,當伺服器端檢測到用戶端從離線到上線狀態的時候,會去資料庫尋找是否有該客戶的的訊息,有的話就會取出來發送,代碼如下
List list = mNotificationSevice.findNotificationByUsername(session.getUsername()); if(null != list && list.size() > 0){ for(Notification notification: list){ String apiKey = notification.getApiKey(); String title = notification.getTitle(); String message = notification.getMessage(); String uri = notification.getUri(); mNotificationManager.sendNotifcationToUser(apiKey, session.getUsername(), title, message, uri); mNotificationSevice.deleteNotification(notification); } }
這個代碼存在的一個bug是,當檢測到有訊息要給剛上線的用戶端發送的時候,調用發送方法sendNotifcationToUser,並從資料庫刪除掉了原來的訊息,這樣操作後,會發現在sendNotifcationToUser裡入庫的訊息被
mNotificationSevice.deleteNotification(notification);也一起刪除了(當然原來的入庫的訊息也一起刪除,但這個刪除是正確的),而剛剛入庫的那條訊息是不應該刪除的,必須等用戶端發送回執回來後再刪除。
視頻作者郭神對這個bug的解決方案如下,先直接貼出代碼
public void sendNotifcationToUser(String apiKey, String username, String title, String message, String uri, boolean shouldSave) { log.debug("sendNotifcationToUser()..."); Random random = new Random(); //這個id就是用戶端發送回執對應的uuid String id = Integer.toHexString(random.nextInt()); IQ notificationIQ = createNotificationIQ(id, apiKey, title, message, uri); ClientSession session = sessionManager.getSession(username); if (session != null) { if (session.getPresence().isAvailable()) { notificationIQ.setTo(session.getAddress()); session.deliver(notificationIQ); } else{ saveNotification(apiKey, username, title, message, uri, id); } } //不管使用者存在不存在都需要將訊息存入資料庫,直到使用者收到訊息發送回饋之後再刪除 try { User user = mUserService.getUserByUsername(username); if(null != user && shouldSave){ saveNotification(apiKey, username, title, message, uri, id); } } catch (UserNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } }
以上代碼增加了一個欄位shouldSave來判斷是否入庫,同時在檢測到用戶端上線並且資料庫有之前發送失敗的訊息得推送的時候,傳入false
if(null != list && list.size() > 0){ for(Notification notification: list){ String apiKey = notification.getApiKey(); String title = notification.getTitle(); String message = notification.getMessage(); String uri = notification.getUri(); mNotificationManager.sendNotifcationToUser(apiKey, session.getUsername(), title, message, uri, false); mNotificationSevice.deleteNotification(notification); } }
這樣改完測了之後,發現沒有任何問題,用戶端從離線到上線後,原本存在資料庫的訊息都沒有了,滿足了需求。
但是,其實是有問題的,當用戶端從離線到上線並且伺服器端從資料庫檢測到有訊息得推送的時候,因為傳入sendNotifcationToUser的最後一個參數是false,根本沒有做入庫操作,所以資料庫根本沒有這條發送訊息的資料,用戶端收到訊息發送回執後,伺服器沒有對應的資料可以刪除,導致看起來似乎達到了預期的效果。
針對這個問題,我做的修改如下,針對用戶端從離線到線上的狀態並需要推送之前為推送成功的訊息,從資料庫取出資料,直接推送該訊息,不刪除該訊息,也不再插入新訊息,等收到用戶端回執後再刪除。
public void sendNotifcationToUser(String id, String apiKey, String username, String title, String message, String uri, boolean shouldSave) { log.debug("sendNotifcationToUser()..."); IQ notificationIQ = createNotificationIQ(id, apiKey, title, message, uri); ClientSession session = sessionManager.getSession(username); if (session != null) { if (session.getPresence().isAvailable()) { notificationIQ.setTo(session.getAddress()); session.deliver(notificationIQ); } else if(shouldSave){ saveNotification(apiKey, username, title, message, uri, id); } } //不管使用者存在不存在都需要將訊息存入資料庫,直到使用者收到訊息發送回饋之後再刪除 try { User user = mUserService.getUserByUsername(username); if(null != user && shouldSave){ saveNotification(apiKey, username, title, message, uri, id); } } catch (UserNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } }
這裡還多了id欄位,每次發送訊息,id訊息都是產生一個新的,對於發送之前的訊息,完全沒必要產生新的id(即uuid),取出原來訊息的id就行了,尋找訊息的地方改為如下
List list = mNotificationSevice.findNotificationByUsername(session.getUsername()); if(null != list && list.size() > 0){ for(Notification notification: list){ String apiKey = notification.getApiKey(); String title = notification.getTitle(); String message = notification.getMessage(); String uri = notification.getUri(); String id = notification.getUuid(); mNotificationManager.sendNotifcationToUser(id, apiKey, session.getUsername(), title, message, uri, false); } }這樣就可以避免作者郭神的bug,其實思路很簡單,就是重新發送訊息的時候不再入庫訊息,而是取出之前的訊息來發送,等收到用戶端回執後再刪除。