abstract:还记得一年前,在上一家公司的时候,领导准备接一个案子,客户那边给了一份开发规范的文档,上面明确的写着要采用MVP模式进行开发。一开始看到这个模式时候,一脸懵逼,什么是MVP?不懂,问一下同事,也没有人能说清楚,无奈那就百度吧。好简单粗暴的说明啊,还是一脸懵逼。 后来,不知道为什么案子也没有接,就这样不了了之了。最近发生了很多事,从上一家公司离职,与朋友准备搞公司,搞了差不多2个月,到现在
还记得一年前,在上一家公司的时候,领导准备接一个案子,客户那边给了一份开发规范的文档,上面明确的写着要采用MVP模式进行开发。一开始看到这个模式时候,一脸懵逼,什么是MVP?不懂,问一下同事,也没有人能说清楚,无奈那就百度吧。
好简单粗暴的说明啊,还是一脸懵逼。 后来,不知道为什么案子也没有接,就这样不了了之了。
最近发生了很多事,从上一家公司离职,与朋友准备搞公司,搞了差不多2个月,到现在的从团队退出。然后准备找工作。。。。
在这期间搞项目的时候,就抽空研究了一下MVP模式,试着用它进行开发。因为只是一个项目,涉及的还不深,所以叫试水。记录一下。
网上关于MVP的介绍、讲解、示例以及开源的项目很多,我这里就不废话了,如果现在还有人不了解什么是MVP,那就百度去吧。我这里参考Google的源码todo-mvp来说。 先看一下目录结构:
不要问我为什么我截图的字体颜色是蓝色的,我不会告诉你我是用的octotree浏览器插件。 这里有两个Base文件:BaseView、BasePresenter,好像和VP有关,先看一下源码:
package com.example.android.architecture.blueprints.todoapp; public interface BasePresenter { void start(); } package com.example.android.architecture.blueprints.todoapp; public interface BaseView{ void setPresenter(T presenter); }
What ?这是什么鬼?两个接口?干吗用的?不知道,不明觉厉。不管了,反正一个对应的V,一个对应的是P就是了。好吧,你们估计再说我这不废话了。这里看不出什么东西,那就从程序的入口看吧,从AndroidManifest.xml中找到程序的入口是tasks/TasksActivity。
打开tasks包,看到如下几个文件
我的习惯是先不看里面的内容,先看文件名字,大致了解每个文件是干嘛用的,这样有助于对整体进行把控,所以这里就体现了命名规范的重要性,关于命名规范,可以百度,也可以参考我的另外一篇文章:Android 开发规范(个人版)。哎呀,又扯远了,继续回来看代码。
第一个文件,应该是个自定义的布局,好像没什么太大的关系。 第二个主程序的入口,没啥说的。 第三个Contract (契约),谁和谁的,不懂,先不管。 第四个Filter Type(过滤器类型),应该是一些类型的定义,好像关系也不大,先不管。 第五个Fragment,不说了 第六个Persenter,这个有关系,而且还很大,那就先看一下它吧。
public class TasksPresenter implements TasksContract.Presenter { .... private final TasksContract.View mTasksView; public TasksPresenter(@NonNull TasksRepository tasksRepository, @NonNull TasksContract.View tasksView) { ... mTasksView = checkNotNull(tasksView, "tasksView cannot be null!"); mTasksView.setPresenter(this); } @Override public void start() { ... } }
省略了一下不必要的代码,这里可以看到几个关键点, 1、TasksPresenter本身实现了TasksContract.Presenter; 2、构造函数里面需要传入一个TasksContract.View; 3、拿到这个tasksView后赋值给了mTasksView,并把自己通过mTasksView.setPresenter(this)方法传递出去。
到这里算是有点眉目了。知道了P和V是如何绑定在一起的了。 P绑定V:通过实例化是传入V; V绑定P: 通过v.setPresenter(P);
但如何使用V呢?继续往下,这里用到了TasksContract这个契约,跟踪一下代码看一下。
package com.example.android.architecture.blueprints.todoapp.tasks;
package com.example.android.architecture.blueprints.todoapp.tasks; import android.support.annotation.NonNull; import com.example.android.architecture.blueprints.todoapp.BaseView; import com.example.android.architecture.blueprints.todoapp.data.Task; import com.example.android.architecture.blueprints.todoapp.BasePresenter; import java.util.List; /** * 这指定 view 和 presenter 之间的 contract。 * This specifies the contract between the view and the presenter. */ public interface TasksContract { interface View extends BaseView{ void setLoadingIndicator(boolean active); void showTasks(List tasks); void showAddTask(); void showTaskDetailsUi(String taskId); void showTaskMarkedComplete(); void showTaskMarkedActive(); void showCompletedTasksCleared(); void showLoadingTasksError(); void showNoTasks(); void showActiveFilterLabel(); void showCompletedFilterLabel(); void showAllFilterLabel(); void showNoActiveTasks(); void showNoCompletedTasks(); void showSuccessfullySavedMessage(); boolean isActive(); void showFilteringPopUpMenu(); } interface Presenter extends BasePresenter { void result(int requestCode, int resultCode); void loadTasks(boolean forceUpdate); void addNewTask(); void openTaskDetails(@NonNull Task requestedTask); void completeTask(@NonNull Task completedTask); void activateTask(@NonNull Task activeTask); void clearCompletedTasks(); void setFiltering(TasksFilterType requestType); TasksFilterType getFiltering(); } }
看到这里就有点意思了,契约类里面主要做了两件事,
定义了一个继承自BaseView的接口(View), 并声明需要实现的方法。
定义一个继承自BasePresenter的接口并继承(Presenter),并声明需要实现的方法。
其实也可以说是一件事,就是声明一些接口。
哦,这下知道Contract是干吗用的了,就是把V、P的接口写到同一个文件里面啊,好像也并么有什么高大上的东西啊?那我把这个文件分成两个文件写,应该也可以吧?我认为是可以的。但是又基于Contract的含义即:契约,就是把View和Presenter绑定到一起:
interface Presenter extends BasePresenter {} interface View extends BaseView{}
这样还是按照官方的来,用一个文件来写好了。
Presenter找到了,Contract也知道是干吗用的了。那么View呢,从文件名已经找不到了,那就看继续看代码吧。从TasksActivity看起,首先我们知道TasksPresenter构造函数里面有一个TasksContract.View的参数,那么就找这个参数传的什么。
public class TasksActivity extends AppCompatActivity { .... private TasksPresenter mTasksPresenter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.tasks_act); .... TasksFragment tasksFragment = (TasksFragment) getSupportFragmentManager().findFragmentById(R.id.contentFrame); if (tasksFragment == null) { // Create the fragment tasksFragment = TasksFragment.newInstance(); } ..... // Create the presenter //这个传入的是 tasksFragment mTasksPresenter = new TasksPresenter( Injection.provideTasksRepository(getApplicationContext()), tasksFragment); } ....... }
由上面的代码可知,TasksPresenter 传入了一个TasksFragment的对象,那这样的TasksFragment就应该是所谓的View了,跟踪进入TasksFragment。
public class TasksFragment extends Fragment implements TasksContract.View {private TasksContract.Presenter mPresenter; ......public TasksFragment() {// Requires empty public constructor}public static TasksFragment newInstance() {return new TasksFragment(); } ......@Overridepublic void onResume() {super.onResume(); mPresenter.start(); }@Overridepublic void setPresenter(@NonNull TasksContract.Presenter presenter) { mPresenter = checkNotNull(presenter); } ....@Overridepublic View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { ..... } ....... }
果然,TasksFragment 实现了TasksContract.View,就是所谓的View。他的核心点在于: 1、实现了TasksContract.View; 2、重写setPresenter方法,接收传递过来的presenter。
这样之后,就可以通过presenter.xxxxx()的方式来调用presenter里面定义的一些方法,而presenter里面定义的方法主要执行耗时操作或者一些数据处理等等,等到presenter里面的函数执行完毕之后,在通过mTasksView.xxx()的方式回调给TasksFragment,TasksFragment再进行页面的改变。
官方给的Demo就看到这里吧,因为关于MVP核心的东西差不多就看完了,或许还有更多的东西我没有发掘。
根据官方Demo,我这里总结了一下实现MVP模式的步骤:
1、定义BaseView、BasePresenter。可以参考官方示例。 2、定义契约类,在里面定义两个接口,举个登录的例子:
public interface LoginContract { interface Presenter extends BasePresenter { /** * 登录 */ void login(); } interface View extends BaseView{ /** * 返回登录成功 */ void loginSuccess(); void loginFailed(String errorMessage); } }
3、定义一个实现契约类中Presenter接口的类,用于实现逻辑代码,并把处理结果返回。例如:
public class LoginPresenter implements LoginContract.Presenter { private LoginContract.View view; public LoginPresenter(LoginContract.View view) { this.view = view; view.setPresenter(this); } /** * 登录 */ @Override public void login() { String useName = view.getUserName(); String pwd = view.getPwd(); Mapparams = new HashMap (); params.put("phone", useName); params.put("password", pwd); AuthRequestUtil.doLogin(params, User.class, new ResponseCallBack () { @Override public void onSuccess(User data) { super.onSuccess(data); saveLoginInfo(data); //返回登录成功 view.loginSuccess(); } @Override public void onFailure(ServiceException e) { super.onFailure(e); //返回登录失败 view.loginFailed(e.getMessage()); } }); } }
4、在Activity 或者Fragment中实现契约类中的View接口。
要实现简单的MVP,差不多就这4步。接触的时间也不长,中间有可能会出现一些纰漏或者错误,如果有这方面的牛人在看到这篇文章的时候,希望能给出宝贵意见。这里先说声谢谢。
关于MVP,还有很多东西,我看到还有关Presenter生命周期的相关文章,还没有仔细研究。这里先记一下。等有时间在仔细研究一下。