一种在 Android 中实现 MVP 模式的新思路
今天我想分享我自己在 Android 上实现 MVP 模式的方法。如果你对 MVP 模式还不熟悉或者你不清楚为什么要在 Android 应用中使用 MVP 模式,我建议你先阅读以下 这篇维基百科的文章 和 这篇博客 。
使用 Activity 和 Fragment 作为视图层(View) 真的合适么?
目前很多使用了 MVP 模式的 android 项目,基本上都是将 activity 和 fragment 作为视图层来进行处理的。而 presenters 通常是通过继承自被视图层实例化或者注入的对象来得到的。诚然,我同意说,这种方式可以节省掉很多让人厌烦的 import android..语句,并且将 presenters 从 activity 的生命周期中分割出来以后,项目后续的维护会变得简便很多。
这种思路是正确的, 但是,从另一个角度来说,activity 有一个很复杂的生命周期(fragment 的生命周期可能会更复杂),而这些生命周期很有可能对你项目的业务逻辑有非常重大的影响. Activity 可以获取上下文环境和多种 android 系统服务,Activity 中发送 Intent,启动 Service 和执行 FragmentTransisitons 等。而这些特性在我看来绝不应该是视图层应该涉及的领域(视图的功能就是现实数据和从用户那里获取输入数据,在理想的情况下,视图应该避免业务逻辑).
基于上述的原因,我对目前的主流做法并不赞同,所以我在尝试使用 Activity 和 Fragment 作为 Presenters。
使用 Activity 和 Fragment 作为 presenters 的步骤
1. 去除所有的 view
将 Activity 和 Fragment 作为 presenter 最大的困难就是如何将关于 UI 的逻辑抽取出来.我的解决方案是: 让需要作为 presenter 的 activity 或者 fragment 来继承一个抽象的类(或者叫"基类"), 这样关于 View 各种组件的初始化以及逻辑,都可以在继承了抽象类的方法中进行操作,而当继承了该抽象类的 class 需要对某些组件进行操作的时候,只需要调用继承自抽象类的方法,就可以了。
那么抽象类怎么获取到的 view 组件呢?在抽象类里面会有一个实例化的接口,这个接口里面的 init() 方法就会对 view 进行实例化,这个接口如下:
public interface Vu {
void init(LayoutInflater inflater, ViewGroup container);
View getView();
}
正如你所见,Vu 定义了一个通用的初始化例程,我可以通过它来实现一个容器视图,它也有一个方法来获得一个 View 的实例,每一个 presenter 将会和它自己的 Vu 关联,这个 presenter 将会继承这个接口(直接或者间接的去继承一个来自 Vu 的接口)
2. 创建一个 presenter 基类 (Activity)
有了 Vu 接口,我们可以通过构建一系列的 class 来操纵很多不同的 view 组件,这些 class 使用 Vu 接口来初始化 View 组件,并通过继承的方式给子类以操纵 view 组件的方法,以此来达到将 ui 逻辑剥离出 activity 的目的。在下面的代码中,你可以看到,我覆写了 activity 的 onCreate 、 onCreateView、onDestroy 、 onDestroyView,通过对这些方法的覆写,就可以对 Vu 的实例化和销毁进行精确的控制(vu.init() 就是实例化一个 view 组件)。onBindVu() 和 onDestoryVu() 是控制 view 生命周期的两个方法。通过对 actiivty 中相关方法的覆写达到控制组件的生命周期的目的(具体看下面的代码,你就明白了), 这样做的好处就是无论是 activity 还是 fragment, 其用与控制 view 组件创建和销毁的语句是一样的(尽量避免定义多余的函数)。这样的话,二者之间的切换也会减少一定的阻力(也许你今天的需求是用 fragment 实现的,但是第二天发现使用 fragment 会有一个惊天 bug,译者本人就遇到过)。
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() {};
}
3. 创建一个基本的 presenter(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();
}
4. 一个简单的例子
如前文所述,我们已经确定了一个框架,现在就来写一个简单的例子来进一步的说明. 为了避免篇幅过长,我就写一个“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 来操作这个 TextView
public class HelloActivity extends BasePresenterActivity {
@Override
protected void onBindVu() {
vu.setHelloMessage("Hello World!");
}
@Override
protected Class<MainVu> getVuClass() {
return HelloVu.class;
}
}
OK,这样就大功告成了!!是不是很简便!
等等...耦合警告!
你可能注意到我的 HelloVu 类直接实现了 Vu 接口,我的 Presenter 的 getVuClass 方法直接引用了实现类。传统的 MVP 模式中,Presenter 是要通过接口与他们的 View 解耦合的。因此,你也可以这么做。避免直接实现 Vu 接口,我们可以创建一个扩展了 Vu 的 IHelloView 接口,然后使用这个接口作为 Presenter 的泛型类型。这样 Presenter 看起来应该是如下这样的 :
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。
5. 如何进行测试
通过以上几步,我们可以发现,去除了 UI 逻辑之后,Activity 变得非常简洁。并且,相关的测试
也变的非常异常的简单。请看如下的代码:
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!");
}
}
以上代码是一段标准的 jnuit 单元测试的代码,不需要在 android 设备中部署运行,只需要在编译环境中即可测试。大幅度的提高了测试效率。但是,在测试某些方法的时候,你必须要使用 android 设备,例如当你想测试 activity 生命周期中的 resume() 方法。在缺乏设备环境的时候,super.resume() 会报错。为了解决这个问题,可以借鉴一些工具,例如 Robolectric、还有 android gradle 1.1 插件中内置的 testOptions { unitTests.returnDefaultValues = true }。此外,你仍然可以将这些生命周期也抽离出来。例如如下:
@Override
protected final void onResume() {
super.onResume();
afterResume();
}
protected void afterResume(){}
现在,你可以在没有 android 设备的情况下,快速的测试了!
意外收获:使用 adapter 作为 presenter
将 Activity 作为 presente 已经足够狡猾了吧?使用 adapter 作为 presenter,你想过没有?
好吧,请看如下的代码:
public abstract class BasePresenterAdapter 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();
}
如上代码,使用 adapter 作为 presenter 其实和 activity 或者 fragement 几乎是一样的,只有一点明显的区别就是,我把 onBingVu 替换成了 onBindListItemVu(接受 int 参数),其实我是借鉴了 ViewHolder 模式 。
总结和一个 demo
我在这篇文章里介绍了我自己的一个实现 MVP 的方法。我非常期待其他 android 开发者对我的这套方法的反馈。我切实的想知道我的方法是否可行?我已经把这套思路用在一个框架的开发上,并且即将公布,与此同时,我在 github 上面有一个 demo 项目,感兴趣的人可以去看一下 https://github.com/wongcain/MVP-Simple-Demo
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
下一篇: 一种更清晰的 Android 架构
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论