Android MVP 试水

还记得一年前,在上一家公司的时候,领导准备接一个案子,客户那边给了一份开发规范的文档,上面明确的写着要采用MVP模式进行开发。一开始看到这个模式时候,一脸懵逼,什么是MVP?不懂,问一下同事,也没有人能说清楚,无奈那就百度吧。

MVP(Model-View-Presenter) 是从经典的模式MVC演变而来,它们的基本思想有相通的地方:Controller/Presenter负责逻辑的处理,Model提供数据,View负责显示

好简单粗暴的说明啊,还是一脸懵逼。


7B9465C2FD1827CD2DA2487906B5120E.gif

后来,不知道为什么案子也没有接,就这样不了了之了。

最近发生了很多事,从上一家公司离职,与朋友准备搞公司,搞了差不多2个月,到现在的从团队退出。然后准备找工作。。。。

在这期间搞项目的时候,就抽空研究了一下MVP模式,试着用它进行开发。因为只是一个项目,涉及的还不深,所以叫试水。记录一下。

网上关于MVP的介绍、讲解、示例以及开源的项目很多,我这里就不废话了,如果现在还有人不了解什么是MVP,那就百度去吧。我这里参考Google的源码todo-mvp来说。
先看一下目录结构:

屏幕快照 2016-10-26 12.32.32.png

不要问我为什么我截图的字体颜色是蓝色的,我不会告诉你我是用的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<T> {

void setPresenter(T presenter);

}

What ?这是什么鬼?两个接口?干吗用的?不知道,不明觉厉。不管了,反正一个对应的V,一个对应的是P就是了。好吧,你们估计再说我这不废话了。这里看不出什么东西,那就从程序的入口看吧,从AndroidManifest.xml中找到程序的入口是tasks/TasksActivity。

<activity
  android:name="com.example.android.architecture.blueprints.todoapp.tasks.TasksActivity"
  android:theme="@style/AppTheme.OverlapSystemBar">
  <intent-filter>
    <action android:name="android.intent.action.MAIN" />
    <category android:name="android.intent.category.LAUNCHER" />
   </intent-filter>
 </activity>

打开tasks包,看到如下几个文件。

屏幕快照 2016-10-26 12.51.02.png

我的习惯是先不看里面的内容,先看文件名字,大致了解每个文件是干嘛用的,这样有助于对整体进行把控,所以这里就体现了命名规范的重要性,关于命名规范,可以百度,也可以参考我的另外一篇文章: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;
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<Presenter> {
      void setLoadingIndicator(boolean active);
      void showTasks(List<Task> 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> {}

这样还是按照官方的来,用一个文件来写好了。

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();
}

......

@Override
public void onResume() {
super.onResume();
mPresenter.start();
}

@Override
public void setPresenter(@NonNull TasksContract.Presenter presenter) {
mPresenter = checkNotNull(presenter);
}
....

@Override
public 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<Presenter> {
      /** 
        * 返回登录成功
       */
      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();
      Map<String, String> params = new HashMap<String, String>();
      params.put("phone", useName);
      params.put("password", pwd);
      AuthRequestUtil.doLogin(params, User.class, new ResponseCallBack<User>() {
         @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生命周期的相关文章,还没有仔细研究。这里先记一下。等有时间在仔细研究一下。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,458评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,454评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,171评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,062评论 0 207
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,440评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,661评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,906评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,609评论 0 200
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,379评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,600评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,085评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,409评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,072评论 3 237
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,088评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,860评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,704评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,608评论 2 270

推荐阅读更多精彩内容