1.为什么需要一个清晰的软件架构
避免创建神类。避免创建无所不知、无所不能的上帝类。如果一个类需要花费时间从其他类中通过Get()和Set()检索数据(也就是说,需要深入业务并且告诉它们如何去做),那么是否应该把这些功能函数更好的组织到其它类而不是上帝类中。
上帝类的维护成本很高,你很难理解正在进行的操作,并且难以测试和扩展,这就是为什么要避免创建上帝类的黄金法则。
然而,在Android开发中,如果不考虑架构的话,Activity类往往会越来越大。这是因为,在Android中允许View和其它线程共存于Activity内。其实最大的问题莫过于在Activity中同时存在业务逻辑和UI逻辑,这会增加测试和维护的成本。
这是为什么需要清晰架构的原因之一。不仅会造成Activity的臃肿,还会引起其他问题,如使Activity和Fragment的生命周期变复杂以及数据绑定等。
2.MVP
MVP代表Model、View和Presenter。
View层负责处理用户事件和视图部分的展示。在Android中,它可能是Activity或者Fragment类。
Model层负责访问数据。数据可以是远端的Server API、本地数据库或者SharedPreferences等。
Presenter层是连接(或适配)View和Model的桥梁。
View从不直接与Model通信。
MVP模式可以分离显示层和逻辑层,所以功能接口如何工作与功能的展示可以实现分离,MVP模式理想化地可以实现同一份逻辑代码搭配不同的显示界面。
下图是基于MVP架构的模式之一。
View是UI线程。Presenter是View与Model之间的适配器。UseCase或者Domain在Model层中,负责从实体获取或载入数据。
依赖规则如下:
关键是,高层接口不知道底层接口的细节,或者更准确地说,高层接口不能,不应该,并且必须不了解底层接口的细节,是(面向)抽象的,并且是细节隐藏的。
同心圆将软件划分为不同的区域,一般的,随着层级的深入,软件的等级也就越高。外圆是实现机制,内圆是核心策略。
①Entities:
可以是一个持有方法函数的对象
可以是一组数据结构或方法函数
它并不重要,能在项目中被不同应用程序使用即可
②Use Cases
包含特定于应用程序的业务规则
精心编排流入Entity或从Entity流出的数据
指挥Entity直接使用项目范围内的业务规则,从而实现Use Case的目标
③Presenters
将Use Case和Entity中的数据转换成格式最方便的数据,使外部系统,如数据库或网页能够方便地使用这些数据
④External Interfaces, UI, DB
所有的细节所在,如数据库细节,Web框架细节等等。
view只是负责处理事件监听或者展示每个视图组件,所有的业务逻辑必须委托给Presenter类。在MVP中,View和Presenter是一 一对应的(在MVVM中是一对多的)
MVP的优缺点:
①优点
耦合降低,Presenter变为纯Java的代码逻辑, 不再与Android Framework中的类如Activity、Fragment等关联, 便于写单元测试。
MVP很好的体现了单一职责的原则,并且严格分为三层,即使后期业务变多,结构仍然清晰,非常利于项目后期的维护。当有新的需求时,只需将相关的需求写在接口,然后实现这个接口,无需顾及之前的结构,免去几方面去调整的头痛。另外,还能更好的对接口代码进行单元测试。
②缺点
使用MVP模式构建项目,会造成类文件和接口文件过多,进而增大包的体积。解决办法的话,可以采用官方的做法,写一个Contract接口,然后把与MVP三层的相关接口全部列入到里面去,类似下面这种:
public interface Contract {
public interface IModel {
xxx;
xxx;
}
public interface IPresenter {
xxx;
xxx;
}
public interface IView {
xxx;
xxx;
}
}
除此之外,还可能内存泄漏的问题。用户关闭了View层,但这时Model层如果仍然在进行耗时操作,因为Presenter层也持有View层的引用,所以造成垃圾回收器无法对View层进行回收,这样一来,就造成了内存泄漏。这里可以重写onDestroy()方法,在View销毁时强制回收掉Presenter。还有一个解决办法就是采用弱引用的方式,如下:
WeakReference< xxx> refrence = new WeakRefrence<>(this);
// 使用时直接就能获得对象的引用
reference.get();
然后在引用进行引用之前,都需要判断引用不为空,以防止空指针异常。
3.MVP与MVC比较
MVP其实就是从MVC模式演化产生的,先看一下著名的MVC模式:
View:对应于布局文件
Model:业务逻辑和实体模型
Controller:控制器,Android中对应于Activity
对应的MVC交互图如下:

虽然Android系统应用开发本身是遵循MVC开发模式的,但是我们仔细看一下View层和Activity,具体view布局文件中的数据绑定和事件处理的方法代码都是冗余在Activity中的,所以我们经常可以看到Activity类动不动就是少则九百行,多则上千甚至几千行。
现在来看一下MVP模式,MVP模式会引入 Presenter层,完成View层和Model层的交互,那么具体MVP对应如下:
View:View通常来说是由Activity实现的,它会包含一个Presenter的引用,View要做的就只是在每次有接口调用的时候(比如按钮点击后)调用Presenter的方法。
Model:业务逻辑和实体模型
Presenter:主要作为沟通View和Model的桥梁,它从Model层检索数据后,返回给View层,但是不像MVC结构,因为它也可以决定与View层的交互操作。
MVP的数据交互图如下:
我们来具体看一下下面两张对比,就可以看来具体区别了:
MVP与MVC唯一的差别是Model和View之间不进行通讯,都是通过Presenter完成, Presenter主要作为View和model交互的一个纽带,扮演 “主持交互”的角色,处理交互逻辑。比如model只负责网络的请求、pesenter负责处理请求网络后的数据处理:加载中 成功 or 失败 取消加载;最后View进行界面的展示。还有一点就是Presenter与View之间的交互是通过接口的。
MVC一个致命缺点,就是在android中由于activity(god object)的存在,Controller和View很难做到完全解耦。但在MVP中就可以很好的解决这个问题
4.MVP举例
以登录为例使用MVP。
使用MVP的好处之一就是模块职责划分明显,层次清晰。 该例的结构图即可展现此优点:
(1)Model层
Model层负责对从登录页面获取帐号、密码进行验证(一般需要请求服务器进行验证,本例直接模拟这一过程)。 从上图的包结构图中可以看出,Model层包含内容:
①实体类bean。
②接口类,表示Model层所要执行的业务逻辑。
③接口实现类,具体实现业务逻辑,包含的一些主要方法。
下面以代码的形式一一展开。
①实体类bean
public class User {
private String password;
private String username;
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
}
封装了用户名、密码,方便数据传递。
②接口
public interface LoginModel {
void login(User user, OnLoginFinishedListener listener);
}
其中OnLoginFinishedListener是presenter层的接口,方便在Model中实现回调presenter,通知presenter业务逻辑的返回结果。
③接口实现类
public class LoginModelImpl implements LoginModel {
@Override
public void login(User user, final OnLoginFinishedListener listener) {
final String username = user.getUsername();
final String password = user.getPassword();
new Handler().postDelayed(new Runnable(){
@Override
public void run() {
boolean error = false;
if (TextUtils.isEmpty(username)){
listener.onUsernameError();//在M层里回调P层的接口
error = true;
}
if (TextUtils.isEmpty(password)){
listener.onPasswordError();//在M层里回调P层的接口
error = true;
}
if (!error){
listener.onSuccess();//在M层里回调P层的接口
}
}
}, 2000);
}
}
实现Model层逻辑:延时模拟登陆(2s),如果用户名或者密码为空则登陆失败,否则登陆成功。无论登录失败或成功,都会回调P层的接口,告诉P结果。
(2)View层
一般视图都只是包含用户界面(UI),而不包含界面逻辑,界面逻辑由Presenter来实现。
从上图的包结构图中可以看出,View包含内容:
①接口类,Presenter与View交互是通过接口。其中接口中方法的定义是根据Activity用户交互需要展示的控件确定的。
②接口实现类,将上述定义的接口中的方法在Activity中对应实现具体操作。
下面以代码的形式一一展开。
①接口
public interface LoginView {
//login是个耗时操作,需要给用户一个友好的提示,一般就是操作ProgressBar
void showProgress();
void hideProgress();
//login存在登录成功与失败的处理,失败给出提示
void setUsernameError();
void setPasswordError();
//login成功,也给个提示
void showSuccess();
}
这5个方法都是presenter根据model层返回结果需要view执行的对应的操作。
②接口实现类
即对应的登录Activity,需要实现LoginView接口。
public class LoginActivity extends AppCompatActivity implements LoginView, View.OnClickListener {
private ProgressBar progressBar;
private EditText username;
private EditText password;
private LoginPresenter presenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
progressBar = (ProgressBar) findViewById( R.id.progress);
username = (EditText) findViewById( R.id.username);
password = (EditText) findViewById( R.id.password);
findViewById(R.id.button).setOnClickListen er(this);
//创建一个presenter对象,当点击登录按钮时,让presenter去调用model层的login()方法,验证帐号密码
presenter = new LoginPresenterImpl(this);
}
@Override
protected void onDestroy() {
presenter.onDestroy();
super.onDestroy();
}
@Override
public void showProgress() {
progressBar.setVisibility(View.VISIBLE);
}
@Override
public void hideProgress() {
progressBar.setVisibility(View.GONE);
}
@Override
public void setUsernameError() {
username.setError("username_error");
}
@Override
public void setPasswordError() {
password.setError("password_error");
}
@Override
public void showSuccess() {
progressBar.setVisibility(View.GONE);
Toast.makeText(this,"login success",Toast.LENGTH_SHORT).show();
}
@Override
public void onClick(View v) {
User user = new User();
user.setPassword( password.getText().toString());
user.setUsername( username.getText().toString());
presenter.login(user);
}
}
View层实现Presenter层需要调用的控件操作,方便Presenter层根据Model层返回的结果进行操作View层进行对应的显示。
(3)Presenter层
Presenter是Model和View之间交互的桥梁。 从上图的包结构图中可以看出,Presenter包含内容:
①接口,包含Presenter需要进行Model和View之间交互逻辑的接口,以及上面提到的Model层数据请求完成后回调的接口。
②接口实现类,即实现具体的Presenter类逻辑。
下面以代码的形式一一展开。
①接口
public interface OnLoginFinishedListener {
void onUsernameError();
void onPasswordError();
void onSuccess();
}
当Model层得到请求的结果,需要回调Presenter层,让Presenter层调用View层的接口方法。
public interface LoginPresenter {
void login(User user);
void onDestroy();
}
登陆的Presenter 的接口,实现类为LoginPresenterImpl,完成登陆的验证,以及销毁当前view。
②接口实现类
public class LoginPresenterImpl implements LoginPresenter, OnLoginFinishedListener {
private LoginView loginView;
private LoginModel loginModel;
public LoginPresenterImpl(LoginView loginView) {
this.loginView = loginView;
this.loginModel = new LoginModelImpl();
}
@Override
public void login(User user) {
if (loginView != null) {
loginView.showProgress();
}
loginModel.login(user, this);//P中调用M的接口方法
}
@Override
public void onDestroy() {
loginView = null;
}
@Override
public void onUsernameError() {
if (loginView != null) {
loginView.setUsernameError();//P中调用V层的接口方法
loginView.hideProgress();
}
}
@Override
public void onPasswordError() {
if (loginView != null) {
loginView.setPasswordError();
loginView.hideProgress();
}
}
@Override
public void onSuccess() {
if (loginView != null) {
loginView.showSuccess();
}
}
}
由于presenter完成二者的交互,那么肯定需要二者的实现类(通过传入参数,或者new)。
presenter里面有个OnLoginFinishedListener, 其在Presenter层实现,给Model层回调,更改View层的状态, 确保 Model层不直接操作View层。
通过这个例子可以看出MVP模式的整个核心流程:
View与Model并不直接交互,而是使用Presenter作为View与Model之间的桥梁。其中Presenter中同时持有View层的Interface的引用以及Model层的引用,而View层持有Presenter层引用。当View层某个界面需要展示某些数据的时候,首先会调用Presenter层的引用,然后Presenter层会调用Model层请求数据,当Model层数据加载成功之后会调用Presenter层的回调方法通知Presenter层数据加载情况,最后Presenter层再调用View层的接口将加载后的数据展示给用户。
5.MVP中使用Contract
由于MVP在实现代码简洁的同时,额外增加了大量的接口、类,不方便进行管理,于是Contract就登场了。
Contract是一个契约,将Model、View和Presenter进行约束管理,方便后期类的查找、维护。
还是以上个例子为例:
①首先,创建一个登陆的Contract:
public interface LoginContract {
interface ILoginModel { }
interface ILoginView { }
interface ILoginPresenter { }
interface OnLoginFinishedListener { }
}
②其次创建Presenter、Model、View对应Contract中的接口;
public class LoginPresenterImpl implements LoginContract.ILoginPresenter, LoginContract.OnLoginFinishedListener{
}
public class MainModel implements MainContract.Model{
}
public class MainActivity implements MainContract.View {
}
架构搭起来了,然后写具体内容。完整的Contract:
public interface LoginContract {
interface ILoginModel {
void login(User user, OnLoginFinishedListener listener);
}
interface ILoginView {
void showProgress();
void hideProgress();
void setUsernameError();
void setPasswordError();
void showSuccess();
}
interface ILoginPresenter {
void login(User user);
void onDestroy();
}
interface OnLoginFinishedListener {
void onUsernameError();
void onPasswordError();
void onSuccess();
}
}
LoginContract就是将所有的接口都定义在这一个类里,从而减少了类的个数,而model,view和presenter对应的实现类不变。
在LoginContract中:
Model接口:创建登录请求的具体实现逻辑,将Presenter提交的字段放到联网请求中,发送给服务器;
View接口:创建在界面上显示加载中、取消加载以及登陆成功、失败的方法;
Presenter接口:创建登陆的方法,以及需要提交的字段 (username、password);
MainPresenter实现MainContract.Presenter 接口中的 login(String username, String password) 方法;实例化Model,在MainPresenter login(String username, String password)方法中,调用model的网络请求,将username、password放在model的login()方法中,进行请求服务器。请求服务器前 使用MainContract.View中的 mView.showLoading()方法,进行显示加载中;在成功失败的回调中,使用对应的方法,以及取消加载。
其中BasePresenter、BaseView 是对Presenter以及View进行的封装。



