Android上實現MVP模式的途徑

treesouth發表於2015-04-29

今天我想分享我在Android上實現MVPModel-View-Presenter模式的方法。如果你對MVP模式還不熟悉或者不瞭解為什麼要在Android應用中使用MVP模式,推薦你先閱讀這篇維基百科文章這篇部落格

使用Activity和Fragment作為View合適麼?

目前在很多使用了MVP模式的Android專案中,主流做法是將Activity和Fragment作為檢視層來進行處理。而Presenters通常是通過繼承被檢視層例項化或者注入的物件來得到的。我認可這種方式可以節省掉那些讓人厭煩的”import android.*”語句,並且將Presenters從Activity的生命週期中分離出來 這使專案後續的維護會變得簡便很多。但另一方面 Activity有一個很複雜的生命週期(Fragment的生命週期可能會更復雜)。而這些生命週期很有可能對專案的業務邏輯有非常重要的影響。Activity可以獲取Context和各種Android系統服務。Activity可以傳送Intent,啟動Service和執行FragmentTransisitons等等。在我看來,這些錯綜複雜的方面不應該是檢視層涉及的領域(檢視的功能只是顯示資料,從使用者那裡獲取輸入資料。在理想情況下,檢視應該避免業務邏輯,無需單元測試)。基於上述原因,我對目前的主流做法並不贊同,所以我嘗試使用Activity和Fragment作為Presenters。

使用Activity和Fragment作為Presenters

1、去除所有的view

將Activity和Fragment作為Presenter最大的困難就是如何將關於UI的邏輯分離出來。我的解決方案是讓需要作為Presenter的Activity或者Fragment來繼承一個抽象的類這樣關於View各種元件的初始化以及邏輯,都可以在繼承了抽象類的方法中進行操作而當繼承了該抽象類的class需要對某些元件進行操作的時候,只需要呼叫繼承自抽象類的方法而不必考慮Presenter型別。在抽象類裡面會有一個例項化的介面,這個介面裡面的初始化方法就會對view進行例項化,這個介面我稱為Vu,如下所示:

如你所見,Vu定義了一個通用的初始化例程,我可以通過它來傳遞一個填充器和一個容器檢視。它也有一個方法可以獲得一個View的例項,每一個presenter將會和它自己的Vu關聯,這個presenter將會實現這個介面(直接或間接地去實現一個繼承自Vu的介面)。

2、建立Presenter基類

現在我有了抽象的View的基礎,我可以著手定義一個Activity或者Fragment基類來充分利用Vu從而實現View的例項化。我是通過利用普通型別和抽象方法來實現的,它定義了一個特殊的表示Presenter的Vu類。這是實現中最單調乏味的部分,因為我需要重新實現想要的相似邏輯或者每一個Presente基類。

下面是我實現的Activity例子:

下面是我實現的Fragment例子:

相同的邏輯可以用在Activity和Fragment型別上,比如支援庫中Activity和Fragment等等。

可以看到,我重寫了建立檢視view的方法(onCreate、onCreateView)和銷燬檢視view的方法(onDestroy、onDestroyView)。我選擇重寫這些方法目的是強制使用抽象例項Vu。一旦它們被重寫,我就可以建立新的生命週期方法,來精確控制對其初始化和銷燬,即onBindVu和onDestroyVu。這樣做的好處就是兩種型別的presenter都可以利用同樣的生命週期事件簽名來實現。這也消除了Activity和Fragemnt生命週期差異的影響,使得兩者之間的轉換更加容易。 (你也可能會注意到我並沒有真正的利用InstantiationException 或者IllegalAccessException做一些異常處理。這僅僅是我比較懶罷了,因為如果我正確地使用這些類就不會丟擲這些異常。)

3、寫一個可以工作的例子

現在我們可以使用剛才構建的框架。簡單起見,我寫一個“Hello World”的例子。我會從建立一個實現了Vu介面的類開始寫:

下一步,我會建立一個Presenter來操作這個view:

等等……有耦合警告!

你可能注意到了HelloVu類直接實現了Vu介面,Presenter的getVuClass()方法直接引用了實現類。常規的MVP模式中,Presenter要通過介面與他們的View解耦。當然,你也可以這麼做。為了避免直接實現Vu介面,我們可以建立一個擴充套件了Vu的IHelloView介面,然後使用這個介面作為Presenter的泛型型別。那麼Presenter看起來應該是這樣的:

在我使用強大的模擬工具過程中,並沒有看到一個介面下面實現Vu所帶來的好處。但是對於我來說一個好的方面是,即使沒有定義Vu介面它也能夠工作,唯一的需求就是你最終還要實現Vu。

4測試

通過以上幾步我們可以發現,在去除了UI邏輯之後Activity變得非常簡潔。同時,相關的測試也變的異常簡單。請看如下單元測試:

以上程式碼是一段標準的JUnit單元測試的程式碼,不需要在Android裝置中部署執行。當然我們測試的Activity要足夠簡單。特殊情況下,在測試需要某些硬體支援的方法的時候,你可能需要使用Android裝置。例如當你想測試Activity生命週期中的onResume()方法。在缺乏硬體裝置支援環境的時候,super.onResume()會報錯。還好我們可以使用一些工具,例如Robolectric、還有Android Studio 中的Gradle 1.1 外掛中內建的testOptions { unitTests.returnDefaultValues = true }選項。此外,你仍然可以將這些生命週期按照下面的方式抽離出來:

現在,你可以把應用程式中特定的邏輯程式碼轉移到生命週期事件中,並且在沒有Android裝置的情況下執行測試了。

意外收穫:使用Adapter作為Presenter

將Activity作為Presenter已經足夠巧妙了吧,如果是adapter,情況會更復雜。它們可以是View或者Presenter麼?廢話不多說,請看如下的程式碼:

正如你看到的,實現方式和Activity和Fragment的Presenter是一樣的。然而,我不是用空的onBindVu方法,而是用引數為整型的position的onBindListItemVu方法。同時,我仍然沿用了View Holder模式。

總結和Demo專案

這篇文章介紹了一種實現MVP模式的方法。從中我發現唯一的途徑就是網上尋找答案。我非常期待其他Android開發者的反饋,是否有人在用這個方法?你發現它有用麼?我是否過於大膽(瘋狂)?如果是的話,這是一個好辦法嗎?

我已經把這套方法(和一些其他的比如Dagger開源庫)整合在一個開源框架上,並且即將公佈。與此同時,我在Github上面有一個demo專案,望各位不吝賜教。

相關文章