Android MVP—An Alternate Approach

Architecture

    今天我想分享一下我自己的方式来实现在Android上的MVP(Model-View-Presenter)模式。如果你不熟悉的MVP模式或者你为什么会想在你的Android应用程序运用它,我建议先读 this Wikipedia article, this blog pos

Activities and Fragments应当被当做视图层?

    在Android MVP实现中的共识(如上链接的博客文章中描述)是Activities 和Fragments 应被视为视图层。在这些实现中,Presenters 是通用的对象,这些对象被注入到他们各自的Activity/Fragment-Views中。我赞成使你的Presenters相对于任何完全自由,imports使得编写测试用例很容易(通过普通的JUnit,不需要其他工具)。我也赞成通过使你的Presenters 与Activity生命周期分离,整个配置更改保留状态更容易。然而另一方面,Activities有着很复杂的生命周期,Fragments甚至更复杂。而且各个activity/fragment 生命周期事件有可能对你的业务逻辑非常重要。另外,Activities有进入你的Context以及各个安卓系统服务的权限。Activities发送Intents,开始Services,创建以及执行FragmentTransisitons,等等。在我看来,所有这些复杂性就是跳出一个“View”应该被认为的思维。 一个视图的工作是呈现数据以及取得用户的输入。理想的情况下,一个视图应该与业务逻辑分离,使得视图的单元测试是不必要的。鉴于此,我并不满足于这个consensus approach,并着手去实现 Activities 、Fragments、Presenters.

Activities and Fragments as Presenters

第一步:从所有的view-yy中摆脱 把Activities and Fragments转换成 Presenters 的最大挑战是分出所有的UI逻辑。我把一个UI元素宽广的定义为 在android.view 或 android.widget 包中的任何事物。然后出发试着去从我的Presenters 中分离,以这样一种一贯会忽略presenter的类型的方式。我想出的这个方式就是在视图周围建立一个抽象层。结果如下图展示的,由于缺乏一个词汇我暂且把它叫做Vu:

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

正如你所看到的,Vu定义了一个初始化的路径,通过这个路径我可以跨过一个inflator 以及一个视图容器。它也包含了一个得到真正的视图实例的方法。每一个presenter 将与它自己的可以实现这个接口的Vu相联系(或者直接或者间接的实现一个继承自Vu的接口)。

第二步:创建一个基础的 Presenters 既然我拥有一个抽象视图的基础,我可以着手定义一系列利用视图instatiation的Activity/Fragment类。我通过利用普通的类型以及一个确定特殊Presenter’s Vu 类的抽象方法来实现这件事。因为我需要去重新实现相似的逻辑或者基础Presenter 类的每一个我想要使用的类型。

这里我是如何用android.app.Activity:实现它的示例:

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() {};
 }

这是一个用android.app.Fragment的相同的实现:

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 and Frament类型重复运用,例如那些来自支持库的等等。

正如你看到的,我可以重写以及完成那个instatiate 视图的方法(onCreate and onCreateView) ,以及那些被粘下来的方法((onDestroy and onDestroyView).)我选择去完成这些方法去强制抽象Vu实例的运用。一旦它们被完成了,我创建了新的生命周期的方法,每一个方法的实现都可以被初始化或者粘贴下来:onBindVu and onDestroyVu. 这个方法的一个好处就是两种presenter 类型的实现利用了这些生命周期事件的同一个签名。这使得Activity 与 Fragment生命周期的差异更加平顺,并且使从一种Presenter 类型向另一种转换更加容易。(你可能注意到我没用对InstantiationException或者 IllegalAccessException做任何处理,这只是因为我比较懒,因为如果我正确地使用这些类,这些示例将永远不会被扔掉)

第四步:把它应用到工作中 既然我们已经将我们的框架准备就绪,我们就可以开始使用它了。为了让文章简短一点,我只展示一个“Hello world”的示例。我将通过创建一个实现Vu接口的新类而开始。

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 

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’s getVuClass()与实现直接关联。 在MVP的传统模式中,Presenters 通过一个接口与它的视图进行了分离。当然,你也可以这么做。与直接实现Vu相反,我们可以构建一个直接继承自Vu 的IHelloView ,并且用这个接口作为类型定义的工具。接下来Presenter 会变成这样:

public class HelloActivity extends BasePresenterActivity<IHelloVu> {

   @Override
   protected void onBindVu() {
       vu.setHelloMessage("Hello World!");
   }

   @Override
   protected Class<MainVu> getVuClass() {
       return HelloVuImpl.class;
   }

}

我个人并没有发现在一个接口后面抽象我的实现的任何好处。Esp.有对于我的处理的强大工具。但是我的approach 的一个好处就是它与特殊的Vu接口一起工作。唯一的要求就是你最终要实现Vu

第五步:测试 Ok,如果我真的跟随了TDD 这只能算是第一步。但是撇开那不说,你可以看到Activity 变的非常简洁,而且把所有的UI逻辑移除了,这变得很容易去测试。这里有一个单元测试:

public class HelloActivityTest {

     HelloActivity activity;
     HelloVu vu;

     @Before
     public void setup() throws Exception {
        activity = new HelloActivity();
        vu = Mockito.mock(HelloVu.class);
        activity.vu = vu;
     }

     @Test
     public void testOnBindVu(){
        activity.onBindVu();
        verify(vu).setHelloMessage("Hello World!");
     }

}

这只是一个JUnit 测试的标准,因此它可以不需要任何安卓设备就运行。当然我们正在测试的Activity 是我能做得尽可能简单的了,在练习中,你可能测试一些至少需要最小支持设备数量的东西。例如,你可能需要测试在onResume() 方法上运行的代码。没有任何设备支持的情况下测试它的话,只要你击中了super.onResume().方法你将会捕获一个异常。幸运的是这里有类似Robolectric的工具,而且在built-in Unit Testing support in the Android Gradle 1.1 plugin and Android Studio.上的testOptions { unitTests.returnDefaultValues = true } 选项。然而除了它,你也可以扩大你的Base Presenter类去抽象这些生命周期事件,就像这样:

...

@Override
protected final void onResume() {
    super.onResume();
    afterResume();
}

protected void afterResume(){}

...

现在你可以移动你的应用特殊逻辑到你的新生命周期事件中,并且在不需要任何设备的情况下自由的运行的你测试。

Bonus: 适配器作为 Presenters 正如从Presenter分离视图一样狡猾,适配器甚至更加扑朔迷离。它们是视图还是Presenter呢?我的观点从现在起变得很明显。因此少说点,看代码:

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 and Fragment Presenters几乎是一样的。然而,我用一个用position 作为输入的onBindListItemVu 方法代替了空的onBindVu 方法。同样,我继续并拷贝了 View Holder pattern.   结论和演示项目: 我在这篇文章里说的都是我自己实现MVP的方式。从我的网络上研究的东西来看这是一种独特的方法。我期待从其他安卓编程者那里得到反馈。

原文 :http://blog.cainwong.com/android-mvp-an-alternate-approach/

发表评论

电子邮件地址不会被公开。 必填项已用*标注