Android上實現MVP模式的途徑

今天我想分享我在Android上實現MVP(Model-View-Presenter)模式的方法。若是你對MVP模式還不熟悉,或者不瞭解爲何要在Android應用中使用MVP模式,推薦你先閱讀這篇維基百科文章這篇博客java

使用Activity和Fragment做爲View合適麼?

目前,在不少使用了MVP模式的Android項目中,主流作法是將Activity和Fragment做爲視圖層來進行處理。而Presenters一般是經過繼承被視圖層實例化或者注入的對象來獲得的。我承認這種方式能夠節省掉那些讓人厭煩的」import android.*」語句,而且將Presenters從Activity的生命週期中分離出來, 這使項目後續的維護會變得簡便不少。但另外一方面, Activity有一個很複雜的生命週期(Fragment的生命週期可能會更復雜)。而這些生命週期頗有可能對項目的業務邏輯有很是重要的影響。Activity能夠獲取Context和各類Android系統服務。Activity能夠發送Intent,啓動Service和執行FragmentTransisitons等等。在我看來,這些錯綜複雜的方面不該該是視圖層涉及的領域(視圖的功能只是顯示數據,從用戶那裏獲取輸入數據。在理想狀況下,視圖應該避免業務邏輯,無需單元測試)。基於上述緣由,我對目前的主流作法並不贊同,因此我嘗試使用Activity和Fragment做爲Presenters。android

使用Activity和Fragment做爲Presenters

一、去除全部的view

將Activity和Fragment做爲Presenter最大的困難就是如何將關於UI的邏輯分離出來。個人解決方案是:讓須要做爲Presenter的Activity或者Fragment來繼承一個抽象的類。這樣關於View各類組件的初始化以及邏輯,均可以在繼承了抽象類的方法中進行操做。而當繼承了該抽象類的class須要對某些組件進行操做的時候,只須要調用繼承自抽象類的方法而沒必要考慮Presenter類型。在抽象類裏面會有一個實例化的接口,這個接口裏面的初始化方法就會對view進行實例化,這個接口我稱爲Vu,以下所示:git

1
2
3
4
public interface Vu { 
     void init(LayoutInflater inflater, ViewGroup container);
     View getView();
}

如你所見,Vu定義了一個通用的初始化例程,我能夠經過它來傳遞一個填充器和一個容器視圖。它也有一個方法能夠得到一個View的實例,每個presenter將會和它本身的Vu關聯,這個presenter將會實現這個接口(直接或間接地去實現一個繼承自Vu的接口)。github

二、建立Presenter基類

如今我有了抽象的View的基礎,我能夠着手定義一個Activity或者Fragment基類來充分利用Vu從而實現View的實例化。我是經過利用普通類型和抽象方法來實現的,它定義了一個特殊的表示Presenter的Vu類。這是實現中最單調乏味的部分,由於我須要從新實現想要的類似邏輯或者每個Presente基類。框架

下面是我實現的Activity例子:ide

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public abstract class BasePresenterActivity<V extends Vu> extends Activity {
 
     protected V vu;
 
     @Override
     protected final void onCreate(Bundle savedInstanceState) {
         super .onCreate(savedInstanceState);
         try {
             vu = getVuClass().newInstance();
             vu.init(getLayoutInflater(), null );
             setContentView(vu.getView());
             onBindVu();
         } catch (InstantiationException e) {
             e.printStackTrace();
         } catch (IllegalAccessException e) {
             e.printStackTrace();
         }
     }
 
     @Override
     protected final void onDestroy() {
         onDestroyVu();
         vu = null ;
         super .onDestroy();
     }
 
     protected abstract Class<V> getVuClass();
 
     protected void onBindVu(){};
 
     protected void onDestroyVu() {};
 
}

下面是我實現的Fragment例子:工具

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public abstract class BasePresenterFragment<V extends Vu> extends Fragment {
 
     protected V vu;
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super .onCreate(savedInstanceState);
     }
 
     @Override
     public final View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
         View view = null ;
         try {
             vu = getVuClass().newInstance();
             vu.init(inflater, container);
             onBindVu();
             view = vu.getView();
         } catch (java.lang.InstantiationException e) {
             e.printStackTrace();
         } catch (IllegalAccessException e) {
             e.printStackTrace();
         }
         return view;
     }
 
     @Override
     public final void onDestroyView() {
         onDestroyVu();
         vu = null ;
         super .onDestroyView();
     }
 
     protected void onDestroyVu() {};
 
     protected void onBindVu(){};
 
     protected abstract Class<V> getVuClass();
 
}

相同的邏輯能夠用在Activity和Fragment類型上,好比支持庫中Activity和Fragment等等。單元測試

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

三、寫一個能夠工做的例子

如今,咱們可使用剛纔構建的框架。簡單起見,我寫一個「Hello World」的例子。我會從建立一個實現了Vu接口的類開始寫:spa

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class HelloVu implements Vu {
 
     View view;
     TextView helloView;
 
     @Override
     public void init(LayoutInflater inflater, ViewGroup container) {
         view = inflater.inflate(R.layout.hello, container, false );
         helloView = (TextView) view.findViewById(R.id.hello);
     }
 
     @Override
     public View getView() {
         return view;
     }
 
     public void setHelloMessage(String msg){
         helloView.setText(msg);
     }
 
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
public class HelloActivity extends BasePresenterActivity<HelloVu> {
 
     @Override
     protected void onBindVu() {
         vu.setHelloMessage( "Hello World!" );
     }
 
     @Override
     protected Class<MainVu> getVuClass() {
         return HelloVu. class ;
     }
 
}

等等……有耦合警告!

你可能注意到了,HelloVu類直接實現了Vu接口,Presenter的getVuClass()方法直接引用了實現類。常規的MVP模式中,Presenter要經過接口與他們的View解耦。固然,你也能夠這麼作。爲了不直接實現Vu接口,咱們能夠建立一個擴展了Vu的IHelloView接口,而後使用這個接口做爲Presenter的泛型類型。那麼Presenter看起來應該是這樣的:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class HelloActivity extends BasePresenterActivity<IHelloVu> {
 
     @Override
     protected void onBindVu() {
         vu.setHelloMessage( "Hello World!" );
     }
 
     @Override
     protected Class<MainVu> getVuClass() {
         return HelloVuImpl. class ;
     }
 
}

在我使用強大的模擬工具過程當中,並無看到一個接口下面實現Vu所帶來的好處。可是對於我來講一個好的方面是,即便沒有定義Vu接口它也可以工做,惟一的需求就是你最終還要實現Vu。

4、測試

經過以上幾步咱們能夠發現,在去除了UI邏輯以後Activity變得很是簡潔。同時,相關的測試也變的異常簡單。請看以下單元測試:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class HelloActivityTest {
 
     HelloActivity activity;
     HelloVu vu;
 
     @Before
     public void setup() throws Exception {
         activity = new HelloActivity();
         vu = Mockito.mock(HelloVu. class );
         activity.vu = vu;
     }
 
     <a href= "http://www.jobbole.com/members/test/" rel= "nofollow" > @Test </a>
     public void testOnBindVu(){
         activity.onBindVu();
         verify(vu).setHelloMessage( "Hello World!" );
     }
 
}

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

1
2
3
4
5
6
7
8
9
10
11
...
 
     @Override
     protected final void onResume() {
         super .onResume();
         afterResume();
     }
 
     protected void afterResume(){}
 
...

如今,你能夠把應用程序中特定的邏輯代碼轉移到生命週期事件中,而且在沒有Android設備的狀況下運行測試了。

意外收穫:使用Adapter做爲Presenter

將Activity做爲Presenter已經足夠巧妙了吧,若是是adapter,狀況會更復雜。它們能夠是View或者Presenter麼?廢話很少說,請看以下的代碼:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public abstract class BasePresenterAdapter<V extends Vu> extends BaseAdapter {
 
     protected V vu;
 
     @Override
     public final View getView( int position, View convertView, ViewGroup parent) {
         if (convertView == null ) {
             LayoutInflater inflater = (LayoutInflater) parent.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
             try {
                 vu = (V) getVuClass().newInstance();
                 vu.init(inflater, parent);
                 convertView = vu.getView();
                 convertView.setTag(vu);
             } catch (InstantiationException e) {
                 e.printStackTrace();
             } catch (IllegalAccessException e) {
                 e.printStackTrace();
             }
         } else {
             vu = (V) convertView.getTag();
         }
         if (convertView!= null ) {
             onBindListItemVu(position);
         }
         return convertView;
     }
 
     protected abstract void onBindListItemVu( int position);
 
     protected abstract Class<V> getVuClass();
 
}

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

總結和Demo項目

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

我已經把這套方法(和一些其餘的好比Dagger開源庫)集成在一個開源框架上,而且即將公佈。與此同時,我在Github上面有一個demo項目,望各位不吝賜教。

相關文章
相關標籤/搜索