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项目,望各位不吝赐教。

相关文章
相关标签/搜索