我们有可能使用过MVC(Model—View—Controller)模式,但当我们用更优的方法测试Android代码时,使用MVP(模型—视图—主导器:Model—View—Presenter)模式可能更合适。MVP模式与MVC模式的根本区别是:在MVP模式中,视图中的业务逻辑被放入主导器中,主导器通过接口与视图交互。在MVC模式中,视图可以包含访问模型的逻辑。在MVP模式中,视图与模型是隔离的,所有与视图和模型的交互操作都是在主导器中完成,因此主导器在整个MVP模式中处于“主导”地位。
在接下来的demo里,我会展示如何在Android中使用MVP模式,以及如何利用该模式提高代码的易测性。为了演示其运行机制,我们创建一个启动画面。所谓启动画面,就是一个普通的界面,在应用程序开始运行前做一些初始化和验证工作。在本例中,我们会在启动画面中检查网络连接是否正常,并显示一个进度条。如果网络连接正常,就切换到另一个Activity中;否则便不会切换到其他Activity,而是向用户显示一条错误信息以阻止程序继续运行。
要创建启动画面,需要一个负责在模型和视图间交互的主导器。在本例中,主导器有两个功能:一个功能用于判断网络是否连接,另一个功能用于控制视图。
主导器中会用到一个模型类ConnectionStatus,该类实现了IConnectionStatus接口,该接口中只定义了一个判断网络是否在线的方法。源码如下所示:
public interface IConnectionStatus {
boolean isOnline();
}
负责控制视图的代码位于Activity中,并且这个Activity实现了ISplashView接口。主导器会通过该接口控制应用程序的执行过程。ISplashView接口的源码如下所示:
public interface ISplashView {
void showProgress();
void hideProgress();
void showNoInetErrorMsg();
void moveToMainView();
}
因为我们是在Android平台上开发应用程序,因此首先需要创建视图,然后我们会把视图的控制权交给主导器。代码如下所示:
public class SplashActivity extends Activity implements ISplashView {
private TextView mTextView;
private ProgressBar mProgressBar;
private SplashPresenter mPresenter;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.splash);
//为当前Activity初始化主导器,并将当前Activity设置给主导器
mPresenter = new SplashPresenter();
mPresenter.setView(this);
//Activity初始化代码
mTextView = (TextView) findViewById(R.id.splash_text);
mProgressBar = (ProgressBar) findViewById(R.id.splash_progress_bar);
}
@Override
protected void onResume() {
super.onResume();
//运行onResume()方法时,通知主导器当前视图已经准备完毕,可以把控制权交给主导器了
mPresenter.didFinishLoading();
}
@Override
public void showProgress() {
mProgressBar.setVisibility(View.VISIBLE);
}
@Override
public void hideProgress() {
mProgressBar.setVisibility(View.INVISIBLE);
}
@Override
public void showNoInetErrorMsg() {
mTextView.setText("No internet");
}
@Override
public void moveToMainView() {
startActivity(new Intent(this, MainActivity.class));
}
}
主导器的代码比较简单,其源码如下所示:
public class SplashPresenter {
private IConnectionStatus mConnectionStatus;
private ISplashView mView;
public SplashPresenter() {
this(new ConnectionStatus());
}
public SplashPresenter(IConnectionStatus connectionStatus) {
mConnectionStatus = connectionStatus;
}
public void setView(ISplashView view) {
this.mView = view;
}
protected ISplashView getView() {
return mView;
}
public void didFinishLoading() {
//获取视图,即设置给主导器的ISplashView接口的实现类的引用
ISplashView view = getView();
//判断程序是否继续执行的逻辑
if (mConnectionStatus.isOnline()) {
view.moveToMainView();
} else {
view.hideProgress();
view.showNoInetErrorMsg();
}
}
}
从上述代码可以看出,主导器通过接口访问视图,主导器并不知道该接口是由Activity实现的。这样,在单元测试中就更容易模拟(mock)视图。
MVP模式可以使代码更易组织且更易测试。在上述demo中,有一个测试文件夹,在测试代码中需要初始化主导器并模拟(mock)接口。在主导器中并未使用任何Android平台相关的代码,因此不需要在Android设备生运行测试用例,只需要在JVM上运行即可。此外,在本例中我们使用Mockito模拟接口。
在Android平台上开发应用程序,我们会发现Activity中会存在大量代码。遗憾的是,测试Activity是很痛苦的。使用MVP模式不仅可以简化创建测试用例的过程,还可以更容易地实施TDD(test-driven development,测试驱动开发)。