1. 程式人生 > >淘寶京東APP節日更新佈局 實現方式

淘寶京東APP節日更新佈局 實現方式

做過Android開發的人都遇到過這樣的問題:隨著需求的變化,某些入口介面會出現UI的增減、內容變化和跳轉介面變化等問題。

問題描述

做過Android開發的人都遇到過這樣的問題:隨著需求的變化,某些入口介面通常會出現UI的增加、減少、內容變化以及跳轉介面變化等問題。每次發生變化都要手動修改程式碼,而入口介面通常具有未讀資訊提醒這樣的“小紅點”邏輯;一旦UI變化,“小紅點”邏輯也要重新計算。如果不同的RD來維護這些程式碼,耦合性非常高,出錯概率也很大。本文以自選股的個人頁卡為例(介面如下圖所示),給出了一套方案來解決動態更新UI的問題以及更好的解決未讀提醒的邏輯。


舊的方案(Phase out)

  • 對於UI動態變化的問題,通常結合遠端控制來解決。

以上圖的“資產管理”為例,舊的解決方案會在XML寫死全部的item,如:“港股交易”、“基金交易”和“精品理財”這三個item。然後根據後臺傳遞過來的JSON解析出需要隱藏哪些item。點選不同的item會跳轉到不同的activity(如下圖所示),這部分跳轉操作也是寫死在程式碼中的。


這解決了一部分問題,但是如果需求新增了item,比如新增了“滬深交易”、“美股交易”,那就需要改動現有程式碼了。

  • 對於未讀指示(小紅點)功能,它的作用是,有未讀資訊來了,需要在UI上面顯示一個小紅點提醒使用者。比如下圖的股友動態的頭像提醒,資產管理的“NEW”提醒,系統設定的新版本提醒等。


舊的方案是定義了一個int型(32位),用它的每一位代表一個UI上的item。比如好友動態是第1位,未讀提醒是第2位... “小紅點”思想是哪個item有未讀資訊,則該int型對應的那一位就置1,否則為0。一旦某個item有未讀提醒的改變,則將這個int型對應的位改變,非同步寫入SharedPreference中,同時利用觀察者模式通知UI做更新,如下圖所示:


上述做法總體來說最大的缺陷就是沒有做到“開放-封閉”原則。面對擴充套件的時候,即新增一個item則不得不修改現有程式碼,需要在該int型中新增一位標誌位,觀察者模式也要註冊新item。所以下面我會介紹另一種方案可以更好的解決該問題。

新的解決方法

  • 資料抽象

首先進行資料的抽象,並將UI進行分組,如下圖所示:


按照組合模式,將資料以樹形結構組織起來,表現“整體/部分”層次結構,如下圖所示。這樣做的好處是,可以以一致的方式來處理個別對象以及物件組合。藍色的表示節點,而綠色的表示葉節點。


組合模式的類圖,如下所示:


對UI進行的資料抽象。無論是ListItem列表項,還是GridView Item的項,都採用了PersonalItem物件來表示,如下所示:


對於PersonalItem來說,有些沒有意義的方法就不實現,如add()、remove()、getChild()等,呼叫它們會丟擲UnsupportedOperationException()異常。

  • 完美解決未讀提醒(小紅點)的問題

關於計算小紅點,PersonalGroup類利用組合+迭代器的模式,程式碼如下:


這裡使用了迭代器,用它遍歷所有PersonalComponent元件。遍歷過程中可能遇到PersonalItem也可能遇到PersonalGroup。由於它們都是PersonalComponent,且實現了getUnreadIndicator()方法,那我們只需呼叫getUnreadIndicator()即可。


如上圖所示,PersonalGroup中載入的是PersonalComponent,可能是PersonalItem也可能是PersonalGroup。組合模式的優點就是無視具體型別 -- 獲取出來的都是PersonalComponent,然後利用多型,呼叫具體類的getUnredIndicatorCount()方法。如果是PersonalGroup,則繼續呼叫它的這個方法(與此方法一樣,會開始另一個遍歷);如果為PersonalItem,則說明遍歷到了樹形結構的末端(即葉節點),則進行如下處理:


如果getUnreadIndicator為true,則表示該PersonalComponent需要顯示小紅點。因此,利用上述組合+迭代方式,運用遞迴在根節點處進行一次呼叫即可。如下圖所示,當計算出葉節點“A股大賽”有未讀提醒,則它上級的groups也有未讀提醒,一直統計到根節點。


getUnredIndicatorCount()是每一個item自己來決定自己是否需要展示小紅點的方法。這就是將區域性與整體解耦了。整體上面,需要計算小紅點,至於如何計算則委託給具體類來實現。即面向物件中的將 "做什麼" 與 "怎麼做"分開。RD可以從中解放出來,不必關注整體實現,只需關注自己的實現即可。比如,需要在“資產管理”中新增“美股交易”,RD只需新增“美股交易”的內容即可。下一節會說明,這部分內容也由遠端控制來代勞了,遠端控制傳遞過來的Date與本地儲存的Date比較,如果是新的Date值,則證明這個item為“NEW”,則對應的小紅點需要顯示。

  • 遠端控制動態更新UI

當遠端控制發生變化時(5分鐘主動發一次請求),通過解析遠端控制介面返回的JSON串,生成PersonalItem物件的列表。其中每一項對應UI上面的一個item。需要注意的是,這裡還包含了一個URL,它是點選UI控制元件跳轉的URL。以“資產管理”為例,它包含“滬深交易”、“基金交易”等子項。當點選任意一個子項的時候啟動的是同一個Activity - WebviewActivity,它包含一個WebView控制元件。因為每個子項的跳轉URL不一樣,所以這個WebView load了不同的URL,即完成了跳轉不同介面的問題。 然後按照上面描述的樹形結構,把PersonalItem放到Groups中。如下圖所示:


Model儲存了待顯示的資料結構。這份資料通過Parser的解析生成UI的內容。過程如下圖所示:


Parser模組是一個遞迴函式,遞迴的對Model進行解析。並將解析出來的List Item、Grid Group、GridView Item載入各自的XML檔案,在程式中動態的新增UI元件。其中onClick事件是在定義PersonalItem的時候已經寫好了回撥。例如,“資產管理”屬於Grid Group,其子項“滬深交易”、“基金交易”等屬於GridView Item。在上述“Build PersonalItem Objects”步驟中,已經定義了onClick方法,呼叫onClick方法跳轉至WebViewActivity,這個Activity會載入不同GridView Item的URL,從而實現點選不同item跳轉不同頁面的目的。

Note:
對於ListItem元素,即上圖的列表項(不是GridView元素),並沒有實現遠端更新的策略。因為它們跳轉的邏輯是跳轉到各自的Activity,是固定不變的;並且它們的文字描述、圖示、是否隱藏均不需要後臺來控制更新。故實際專案中,只對GridView內容作了遠端控制動態更新UI機制的處理。
另外,在通過遠端控制動態更新UI的過程中也遇到了一些坑,比如遠端控制更新的時刻,恰好使用者退出App,此時系統剛好銷燬Activity。那麼在執行到上述Parser模組的inflateUI的時候就需要判斷當前上下文是否為空,如果為空則直接退出。

結論與資料

本文通過將UI資料進行抽象,利用組合模式進行資料的構建,利用遞迴的方式將資料對映為UI,同時處理了點選事件。資料來源則可以通過遠端控制動態的更新,RD從中解放。另外,組合+迭代器的方式完美的解決了小紅點的問題,遵循了“開放-封閉”原則,將“做什麼”與“怎麼做”分開。下圖從資料的角度描述了改版前後程式碼量、Bug量以及RD工作量的差異。