class="FocusMe">今天我想分享我在Android上实现MVP(Model-View-Presenter)模式的方法。如果你对MVP模式还不熟悉,或者不了解为什么要在Android应用中使用MVP模式,推荐你先阅读这篇维基百科文章和这篇博客。
目前,在很多使用了MVP模式的Android项目中,主流做法是将Activity和Fragment作为视图层来进行处理。而Presenters通常是通过继承被视图层实例化或者注入的对象来得到的。我认可这种方式可以节省掉那些让人厌烦的”import android.*”语句,并且将Presenters从Activity的生命周期中分离出来, 这使项目后续的维护会变得简便很多。但另一方面, Activity有一个很复杂的生命周期(Fragment的生命周期可能会更复杂)。而这些生命周期很有可能对项目的业务逻辑有非常重要的影响。Activity可以获取Context和各种Android系统服务。Activity可以发送Intent,启动Service和执行FragmentTransisitons等等。在我看来,这些错综复杂的方面不应该是视图层涉及的领域(视图的功能只是显示数据,从用户那里获取输入数据。在理想情况下,视图应该避免业务逻辑,无需单元测试)。基于上述原因,我对目前的主流做法并不赞同,所以我尝试使用Activity和Fragment作为Presenters。
将Activity和Fragment作为Presenter最大的困难就是如何将关于UI的逻辑分离出来。我的解决方案是:让需要作为Presenter的Activity或者Fragment来继承一个抽象的类。这样关于View各种组件的初始化以及逻辑,都可以在继承了抽象类的方法中进行操作。而当继承了该抽象类的class需要对某些组件进行操作的时候,只需要调用继承自抽象类的方法而不必考虑Presenter类型。在抽象类里面会有一个实例化的接口,这个接口里面的初始化方法就会对view进行实例化,这个接口我称为Vu,如下所示:
1 2 3 4public
interface
Vu {
void
init(LayoutInflater inflater, ViewGroup container);
View getView();
}
如你所见,Vu定义了一个通用的初始化例程,我可以通过它来传递一个填充器和一个容器视图。它也有一个方法可以获得一个View的实例,每一个presenter将会和它自己的Vu关联,这个presenter将会实现这个接口(直接或间接地去实现一个继承自Vu的接口)。
现在我有了抽象的View的基础,我可以着手定义一个Activity或者Fragment基类来充分利用Vu从而实现View的实例化。我是通过利用普通类型和抽象方法来实现的,它定义了一个特殊的表示Presenter的Vu类。这是实现中最单调乏味的部分,因为我需要重新实现想要的相似逻辑或者每一个Presente基类。
下面是我实现的Activity例子:
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 33public
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 39public
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接口的类开始写:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21public
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 13public
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 13public
class
HelloActivity
extends
BasePresenterActivity<IHelloVu> {
@Override
protected
void
onBindVu() {
vu.setHelloMessage(
"Hello World!"
);
}
@Override
protected
Class<MainVu> getVuClass() {
return
HelloVuImpl.
class
;
}
}
在我使用强大的模拟工具过程中,并没有看到一个接口下面实现Vu所带来的好处。但是对于我来说一个好的方面是,即使没有定义Vu接口它也能够工作,唯一的需求就是你最终还要实现Vu。
通过以上几步我们可以发现,在去除了UI逻辑之后Activity变得非常简洁。同时,相关的测试也变的异常简单。请看如下单元测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19public
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设备的情况下运行测试了。
将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 32public
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模式。
这篇文章介绍了一种实现MVP模式的方法。从中我发现唯一的途径就是网上寻找答案。我非常期待其他Android开发者的反馈,是否有人在用这个方法?你发现它有用么?我是否过于大胆(疯狂)?如果是的话,这是一个好办法吗?
我已经把这套方法(和一些其他的比如Dagger开源库)集成在一个开源框架上,并且即将公布。与此同时,我在Github上面有一个demo项目,望各位不吝赐教。