天天看點

App設計:消息推送和界面路由跳轉

app消息推送、顯示通知欄,點選跳轉頁面是很一般的功能了,下面以個推為例示範push內建,消息處理子產品及app内部路由子產品的簡單設計。

內建sdk步驟根據文檔一步步做就行了,一般包括lib引入,<code>AndroidManifest.xml</code>配置,gradle配置,拷貝資源和java檔案等。

需要注意的,自己應該做一層封裝,因為像圖檔,統計,推送等第三方api,如果有替換更新等需求,那麼封裝一層來確定自己代碼的更少變動也是蠻必要的。

服務端推送消息的操作是非UI操作,個推接入後在一個IntentService中收到透傳消息(透明傳輸消息):

payload就是收到的push消息,一般是約定好的json文本。

下面是一個基本示例:

一般的推送都需要立即顯示通知的,是以會有通知的資訊。當然也可以是不帶通知的推送。

這裡payload裡面攜帶了點選推送後的操作資料,type="page"表示此推送需要執行一個跳轉。

path是跳轉到的(以下路由表示相同含義)頁面的路徑——類似url那樣的格式,抽象了具體界面。params包括了跳轉相關參數,比如這裡需要打開文章詳情頁,那麼傳遞了文章id。

web中的url跳轉機制非常值得借鑒。

程式設計中,有一種模式:指令模式,将操作和具體的執行分開。安卓系統中的輸入事件的處理,Handler+Message機制等,都是類似的。

Msg用來抽象一個消息,而對應的有Handler來處理它。

這樣的好處是指令被對象化,之後對它的處理可以利用多态等特性,指令的處理或許要經曆多個階段(Stage),這樣可以動态組合不同的Handler完成對同一個Msg的處理。

如果僅僅是簡單的switch+static method去實作的話,随着業務增加,是無法勝任變化的。如果有實作涉及到“消息處理”類似功能的話,不同消息不同處理的大前提,多重處理的需要,會讓switch泛濫成災的,而msg+handler僅需要一次switch選擇合适的Handler,之後的處理是鍊式的,不會有再次switch的需要的。

可以思考下“消息+處理”這類功能的設計方案。

下面分PushMessage和PushHandler兩個抽象,分别是推送消息和對應處理。

這裡的思路借鑒了安卓中Handler的機制——Handler+Message這樣的設計。

此外,源碼ViewRootImpl、InputStage對輸入事件的處理也可以借鑒。

類PushMessage其實就是個bean,它對背景推送的消息進行表示。

每一個PushHandler處理一個PushMessage。這裡是一個基類:

handlePushMsg()用來供子類完成具體的消息處理。

這裡假設業務功能上,需要一類推送是彈通知,并處理通知點選後的路由操作——界面跳轉。

這裡引入另一個子產品——路由子產品,路由子產品完成界面跳轉相關操作。

像Arouter這樣的開源庫就是做這類事情的——不論web還是移動app,都會碰到接收并響應界面跳轉指令的功能。

接下來繼續自己嘗試實作路由功能。

因為路由子產品和推送不是相關的——路由指令(或者稱為消息)的發出不一定是推送,也可以是其它界面中的按鈕等,知道路由子產品和推送子產品需要分别設計很重要。

有一部分推送是需要執行路由的,對這類推送的處理就是得到其對應的路由指令,之後交給路由子產品去處理。

BaseRoutePushHandler重寫handlePushMsg()完成routeMsg——路由指令的push消息的處理。getRouteMsg()供子類擷取到路由指令的消息對象,之後交給RouterManager去處理。

路由子產品實作app内不同界面之間的跳轉導航。設計上,RouteMsg表示一個具體的路由指令,之後會有一個(或多個——如果對指令的處理是鍊式的話?)RouteHandler來處理此路由消息。

鑒于URL對不同web界面的定位導航優勢,為系統中不同的跳轉定義路由path是很不錯的想法。

甚至可以定位到界面中的tab子界面,如果直接去關聯Activity等,那麼耦合非常嚴重。

RouteMsg設計上隻用來表達路由指令,它包含路由path和額外參數。為了面向對象化,參數是有含義的強類型,而不是queryParams那樣的基本類型key-value集合,要知道key的命名本身就是一種依賴,那麼還不如定義key對應的java屬性更直接些。

RouteMsg也是一個bean,當然可以跨程序,這裡實作Parcelable當然更好,簡單點就實作Serializable标記接口即可。

基類BaseRouteMsg定義如下:

其中getPath()方法要求每個具體的路由消息聲明其對應的跳轉路徑。子類可以定義其它任意屬性——可以被序列化即可。

作為示例,下面是文章詳情界面的跳轉路由消息:

對應每個RouteMsg對象需要有RouteHandler來處理它,這裡引入路由表的概念——RouteMap,它定義了所有的path常量以及擷取不同path對應RouteHandler的方法(工廠方法)。

getRouter(path)根據path傳回處理它的RouteHandler,并且RouteMap定義了所有可能的路由path。BaseRouter就是處理某個path對應路由消息的Handler。

基類BaseRouter是抽象的路由消息處理器。将路由子產品作為架構設計,需要盡可能使用抽象的東西,允許變更及擴充。

對于mRouteMsg可能更應該是構造函數參數,而且藐似不應該被setter篡改。這裡為了可能的友善性(目前不知道是什麼),決定還是作為普通的屬性對待。

注意Context是android中的上帝對象,可以肯定導航操作需要它,但為了弱化它和RouteHandler的依賴關系(或許是生命周期)僅作為參數提供,而非字段。

方法canRoute(context)用來做導航操作的前置判斷,因為路由可能涉及登入判斷等環境問題,這個邏輯需要子類去重寫,如果沒特殊要求,這裡預設傳回true。

方法navigate(context)是具體的導航操作,如打開某個Activity。

上面分别介紹了推送和路由子產品的大體設計,那麼收到一個推送消息,彈出通知,使用者點選通知後的跳轉,這一系列操作是如何貫徹的呢?接下來就看看。

在sdk提供的IntentService.onReceiveMessageData()中收到透傳消息,這裡的代碼是依賴伺服器傳回的資料格式的,即json和PushMessage對應,第一步将push消息轉為java對象,接着交給PushManager去處理:

這裡使用一個Manaher類來完成對PushMessage的一般處理邏輯。因為需求假定push都需要談通知,并且通知點選後執行路由,那麼先得到一個routeMsg,之後調用NotifiyManager.notifyRouteMsg()來發送通知。

通知以類似Intent的方式攜帶了之後的路由消息資料。

安卓中發送通知到通知欄是很簡單的操作,需要注意的是:

使用NotificationCompat.Builder 來避免相容問題。

建議使用<code>String tag</code>來區分不同的通知。

使用tag來發送通知的notify()方法如下:

因為id是一個int整數,很難做到對不同業務通知進行唯一區分。

使用tag,因為是一個可以組合的字元串,那麼格式就比較靈活了,例如可以使用uri這種格式,或者其它任意你能夠輕松用來區分不同業務子產品不同通知的格式來産生tag作為通知的辨別。

有關Notification的完整用法這裡不去展開,為了能在點選通知之後做一些控制——比如判斷使用者是否登入等,可以讓通知的點選行為是打開一個Service,而不是跳轉到某個Activity。

這樣的好處是不至于去修改Activity的代碼來插入通知跳轉的各種邏輯,當然必要的處理有時是必須的——比如Activity打開後清除對應通知。但這類工作可以做的更一般化,讓Activity提供最少的邏輯,比如提供管理的跳轉path,這樣清除通知(或需要撤銷的其它路由指令)的動作就可以架構去做了。這部分的功能目前不打算提供,但的确是一個需要考慮的必要功能。

下面的代碼展示了點選通知啟動Service的操作:

類RouteIntentService是繼承IntentService的業務類,它響應所有來源(包括此處的通知)的路由指令。下面看它是如何工作的。

在RouteIntentService.java中:

從intent中擷取到發送通知時設定的routeMsg,交給RouterManager去處理。

調用RouteMap.getRouter()擷取到對應routeMsg的處理器——router。

router.canJump()用來對目前導航做前置判斷,預設傳回true。

router.navigate(context)執行具體的跳轉邏輯。

作為示例,文章詳情界面的路由器如下:

本文整理了實作“推送、通知、頁面跳轉”功能的一個簡單設計。

Message+Handler模式是一個典型的程式設計模型。類似Task+Schedulers(異步任務+線程池)那樣,展現一種資料和處理的分離思想。

如果後續有更多的關于推送、路由的要求,優先選擇改進架構去滿足一般需求。

面向抽象程式設計,不要直接對具體業務程式設計。

TODO:demo代碼後續補上。

(本文使用Atom編寫)