Android MVP模式简单使用和封装使用

MVP+Retrofit2+RxJava2

本Demo使用MVP+Retrofit2+RxJava2来写的,如果大家对Retrofit2+RxJava都不会使用进行网络请求的话,请先查看我之前写的文章Retrofit2.0+RxJava2.0封装使用,因为我这里面网络请求是直接使用之前封装好的,这里就不再介绍了。这里只介绍Android MVP的简单使用和封装使用。

Demo地址:https://github.com/pengjunshan/MVPRetrofitRxJava

其它文章

Retrofit2+RxJava2 封装使用
OkHttp3简单使用和封装使用
Android开发 多语言、指纹登录、手势登录
Android使用IconFont阿里矢量图标
Android Studio 使用SVN 主干和分支合并代码

主要讲解内容
  • MVP封装前简单使用
  • MVP对Activity封装使用
  • MVP对Frgment封装使用
效果图
MVP介绍

MVP全名Mode View Presenter,Presenter处理逻辑业务,Model提供数据,View更新展示界面。完全隔离界面显示与业务逻辑。

流程图

优点:

  • 分离了视图逻辑和业务逻辑,降低了耦合。
  • 单一职责, Model, View, Presenter只处理单一逻辑。
  • Model层的修改和View层的修改互不影响。
  • 视图逻辑和业务逻辑分别抽象到了View和Presenter的接口中去,提高代码的可阅读性

缺点:

  • 接口类过多,代码量增加。
  • Presenter和View相互持有引用,解除不及时的话容易出现内存泄漏。

说明:

  • 这里对MVP不做过多的解释,网上对MVP的解释有一大堆基本说的都差不多。
  • 下面我会讲解怎么尽可能的减少代码量,和避免内存泄漏的方法。
  • MVP并没有一个统一标准模式,只要遵循View和Model相互分离就行。(按照自己喜欢的模式写就行)

MVP简单使用方法

用到的类

Concacts:契约类 将Model、View、Presenter 进行约束管理,方便后期类的查找、维护。
View:Activity 和Fragment 视为View层,负责处理 UI。
Model:包含着具体的数据请求,数据源、本地存储等等。
Presenter :为业务处理层,既能调用View逻辑,又能调用Model请求数据。

需要的类

EaseConcacts 类

契约类 将Model、View、Presenter 进行约束管理。在各自的接口类中定义不同需求的方法。IView接口类中创建一个接收Data数据的回调方法;IPresenter接口类中定义一个IView触发网络请求的方法;IModel接口类中定义一个真正网络请求的方法和一个回调给IPresenter类的回调方法;

public class EaseConcacts {

  interface IView {

    /**
     * View层获取数据回调方法
     */
    void onResultData(String data);
  }

  interface IPresenter {

    /**
     * View层向Presenter发送请求方法
     */
    void requestData(Context context);
  }

  interface IModel {

    /**
     * Persenter层向Model发送请求方法
     */
    void getData(Context context, ModelListener modelListener);

    interface ModelListener {

      /**
       * Model层请求接口完成后回调Persenter层方法
       */
      void onReslutJson(String test);
    }
  }
}

EaseActivity类

IView类中实现Concacts中的IView接口,并且初始化Presenter,然后点击按钮触发Presenter类中的requestData方法;requestData方法中会触发IModel中真正网络请求方法;然后IView类通过onResultData回调方法接收数据;

public class EaseActivity extends AppCompatActivity implements IView {

  private EasePresenter presenter;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_ease);
    presenter = new EasePresenter(this);
    findViewById(R.id.getJSON).setOnClickListener((view) -> presenter.requestData(this));
  }

  @Override
  public void onResultData(String data) {
    Toast.makeText(this, data, Toast.LENGTH_SHORT).show();
  }

}

EasePresenter类

首先要实现EaseConcacts契约类中的IPresenter接口,并实现requestData方法,当View调用requestData方法后Presenter会通过Mode进行网络请求,然后把结果回调过来。

public class EasePresenter implements EaseConcacts.IPresenter {

  private EaseConcacts.IView iView;
  private EaseModel iModle;

  public EasePresenter(EaseConcacts.IView view) {
    this.iView = view;
    this.iModle = new EaseModel();
  }

  @Override
  public void requestData(Context context) {
    iModle.getData(context, test -> iView.onResultData(test));
}

EaseModel类

首先要实现EaseConcacts契约类中IModel接口,这里写网络业务代码,网络请求完成后可以在此可以处理数据 增删改查、本地存储等等;然后把数据通过接口返回给EasePresenter类;

public class EaseModel implements EaseConcacts.IModel {

  @Override
  public void getData(Context context, ModelListener modelListener) {
    RetrofitClient.request(context, RetrofitClient.createApi().getJson(),
        new IResponseListener<BannerBean>() {
          @Override
          public void onSuccess(BannerBean data) {
            /**
             * 在此可以处理数据 增删改查、本地存储等等
             */
            Logout.e("data= "+data.toString());
            modelListener.onReslutJson(data.toString());
          }

          @Override
          public void onFail(OkHttpException failuer) {
            modelListener.onReslutJson("失败= "+failuer.getEmsg());
          }
        });
  }

}

Model弱化

可以弱化Model的作用,把RetrofitClient网络请求当做Modle,可以省略Modle接口和类文件

public class EasePresenter implements EaseConcacts.IPresenter {

  private EaseConcacts.IView iView;
  private EaseModel iModle;

  public EasePresenter(EaseConcacts.IView view) {
    this.iView = view;
    this.iModle = new EaseModel();
  }

  @Override
  public void requestData(Context context) {

    /**
     * 方式一:调用Modle进行调用接口
     */
    //iModle.getData(context, test -> iView.onResultData(test));

    /**
     * 方式二:弱化了Model的作用,这里RetrofitClient网络请求就是Modle,可以省略一个Modle文件
     */
    RetrofitClient.request(context, RetrofitClient.createApi().getJson(),
        new IResponseListener<BannerBean>() {
          @Override
          public void onSuccess(BannerBean data) {
            Logout.e("data= " + data.toString());
            iView.onResultData(data.toString());
          }

          @Override
          public void onFail(OkHttpException failuer) {
            iView.onResultData("失败= " + failuer.getEmsg());
          }
        });
  }

}

封装MVP使用

上面说过MVP缺点,Presenter和View相互持有引用,解除不及时的话容易出现内存泄漏。下面我们要创建一些Base类来处理一些公共的代码逻辑和防止内存泄漏。

创建Base类

BaseActivity:通过泛型接收View和Presenter,进行V和P的绑定和解绑触发。
BaseFragment:通过泛型接收View和Presenter,进行V和P的绑定和解绑触发。
BasePresenter:动态判断View是否已销毁,防止内存泄漏。
BaseView:为了处理公共View逻辑。比如在BaseView中写公共处理UI方法、通过泛型对View进行绑定和解绑。


BaseView类

在BaseActivity、BaseFragment、BasePresenter中都要处理View的逻辑,都需要用泛型来接收View,所以所有的IView都要继承BaseView。

public interface BaseView {
  //添加公共处理UI方法 比如处理接口失败的UI

}

BasePresenter类

所有的Presenter类都要继承BasePresenter,在Base里对View做了统一的绑定和解绑的代码处理。还统一对View做了是否已解绑的判断,防止调用View中的回调方法(防止内存泄漏)。

public abstract class BasePresenter<V extends BaseView> {

  private V view;
  private V proxyView;

  public V getView() {
    return proxyView;
  }

  /**
   * 绑定view
   */
  public void attachView(V view) {
    this.view = view;
    //参数一:类加载器
    ClassLoader classLoader = view.getClass().getClassLoader();
    //参数二:代理接口
    Class<?>[] interfaces = view.getClass().getInterfaces();
    //参数三:方法回调
    BaseViewInvocationHandler handler = new BaseViewInvocationHandler(view);
    proxyView = (V) Proxy.newProxyInstance(classLoader, interfaces, handler);
  }

  /**
   * 解绑view
   */
  public void detachView() {
    this.view = null;
  }

  private class BaseViewInvocationHandler implements InvocationHandler {

    private BaseView view;

    BaseViewInvocationHandler(BaseView view) {
      this.view = view;
    }

    //统一判断->控制对象访问权限
    @Override
    public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
      //检查是不是为null
      if (isViewNull()) {
        //不用回调
        return null;
      }
      //执行回调
      return method.invoke(view, objects);
    }
  }

  private boolean isViewNull() {
    if (view == null) {
      return true;
    }
    return false;
  }

}

BaseActivity类

通过泛型接收View和Presenter,进行V和P的绑定和解绑触发。还有提供创建Presenter类的抽象方法,和获取Presenter类的构造方法。

public abstract class BaseActivity<V extends BaseView, P extends BasePresenter<V>> extends
    Activity implements BaseView {

  private P presenter;

  @Override
  protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    if (presenter == null) {
      presenter = createPresenter();
    }
    /**
     * 绑定view
     */
    if (presenter != null) {
      presenter.attachView((V) this);
    }

  }

  /**
   * 得到当前的Presenter类
   */
  public P getPresenter() {
    return presenter;
  }

  /**
   * 创建Presenter类
   */
  public abstract P createPresenter();

  @Override
  protected void onDestroy() {
    super.onDestroy();
    /**
     * 解绑view
     */
    if (presenter != null) {
      presenter.detachView();
    }
  }
}

BaseFragment类

通过泛型接收View和Presenter,进行V和P的绑定和解绑触发。还有提供创建Presenter类的抽象方法,和获取Presenter类的构造方法。和BaseActivity处理方式是一样的。

public abstract class BaseFragment<V extends BaseView, P extends BasePresenter<V>> extends
    Fragment implements BaseView {

  private P presenter;

  @Override
  public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    if (presenter == null) {
      presenter = createPresenter();
    }
    if (presenter != null) {
      presenter.attachView((V) this);
    }
  }

  public P getPresenter() {
    return presenter;
  }

  public abstract P createPresenter();

  @Override
  public void onDestroyView() {
    super.onDestroyView();
    if (presenter != null) {
      presenter.detachView();
    }
  }

}
Activity中使用
Activity使用

LoginConcacts契约类

来统一管理IView、IPresenter、IModel接口。可以对Model进行弱化,不仅少定义了IModel接口还可以少写一个实现IModel接口类,从而减少了代码量。

public class LoginConcacts {

  interface IView extends BaseView {

    /**
     * 校验账号密码错误的提示信息
     */
    void showToast(String msg);

    /**
     * View中获取接口信息的回调
     */
    void onResultData(String data);
  }

  interface IPresenter {

    /**
     * 校验账号密码
     */
    void checkData(String userName, String userPwd);

    /**
     * 请求数据
     */
    void requestData(String userName, String userPwd);
  }

  /**
   * Model可以弱化掉,节省代码量
   */
  interface IModel {

    void getData(Context context, String userName, String userPwd, ModelListener modelListener);

    interface ModelListener {

      void onReslutJson(String test);
    }
  }

}

LoginPresenter类

首先要继承BasePresenter,通过泛型传入LoginConcacts.IView,再实现LoginConcacts.IPresenter接口类并实现里面的方法。在LoginPresenter里可以直接进行网络请求,把Retrofit网络请求当做Model(把Model弱化),也可以调用LoginModel进行网络请求。

public class LoginPresenter extends BasePresenter<LoginConcacts.IView> implements
    LoginConcacts.IPresenter {

  private Context context;
  private LoginModel modle;

  public LoginPresenter(Context context) {
    this.context = context;
    this.modle = new LoginModel();
  }

  @Override
  public void checkData(String userName, String userPwd) {
    if (TextUtils.isEmpty(userName)) {
      getView().showToast("请输入账号!");
    } else if (TextUtils.isEmpty(userPwd)) {
      getView().showToast("请输入密码!");
    } else {
      requestData(userName, userPwd);
    }
  }

  @Override
  public void requestData(String userName, String userPwd) {

    /**
     * 方式一:调用Modle进行调用接口
     */
//    modle.getData(context,userName,userPwd, test -> getView().onResultData(test));

    /**
     * 方式二:把Modle弱引用掉,这里RetrofitClient网络请求就是Modle,可以省略一个Modle文件
     */
    Map<String, String> map = new HashMap<>();
    map.put("username", userName);
    map.put("password", userPwd);

    RetrofitClient.request(context, RetrofitClient.createApi().postLogin(map),
        new IResponseListener<LoginBean>() {
          @Override
          public void onSuccess(LoginBean data) {
            Logout.e("data= " + data.toString());
            getView().onResultData(data.toString());
          }

          @Override
          public void onFail(OkHttpException failuer) {
            getView().onResultData("失败= " + failuer.getEmsg());
          }
        });
  }
}

LoginModel类

首先要实现IModel接口类并实现方法,Model中不光是进行网络请求的,其它业务逻辑都可以处理。这里是进行网络请求,然后通过接口返回给Presenter。

public class LoginModel implements IModel {

  @Override
  public void getData(Context context, String userName, String userPwd,
      ModelListener modelListener) {
    Map<String, String> map = new HashMap<>();
    map.put("username", userName);
    map.put("password", userPwd);

    RetrofitClient.request(context, RetrofitClient.createApi().postLogin(map),
        new IResponseListener<LoginBean>() {
          @Override
          public void onSuccess(LoginBean data) {
            Logout.e("data= " + data.toString());
            modelListener.onReslutJson(data.toString());
          }

          @Override
          public void onFail(OkHttpException failuer) {
            modelListener.onReslutJson("失败= " + failuer.getEmsg());
          }
        });
  }

}

LoginActivity类

LoginActivity就是View的实现类,首先要继承BaseActivity类并通过泛型传入View和Presenter。然后实现LoginConcacts.IView接口并实现其中的方法。

public class LoginActivity extends
    BaseActivity<LoginConcacts.IView, LoginPresenter> implements LoginConcacts.IView {

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_login);
    findViewById(R.id.getJSON)
        .setOnClickListener((view) -> getPresenter().checkData("账号", "密码"));
  }

  @Override
  public LoginPresenter createPresenter() {
    return new LoginPresenter(this);
  }

  @Override
  public void showToast(String msg) {
    Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
  }

  @Override
  public void onResultData(String data) {
    Toast.makeText(this, data, Toast.LENGTH_SHORT).show();
  }
}
Fragment中使用
Fragment使用

BannerConcacts类

来统一管理IView、IPresenter的接口,这里我对Model弱化掉了不再提供Model的实现类了。

public class BannerConcacts {

  interface IView extends BaseView {

    /**
     * 获取接口数据回调方法
     */
    void onReslutData(String data);
  }

  interface IPresenter {

    /**
     * 进行网络业务处理
     */
    void requestData(Context context);
  }

}

BannerPresenter类

首先继承BasePresenter类通过泛型传入相应的IView,然后再实现BannerConcacts.IPresenter接口并实现其中的方法。这里没有调用Model来进行业务处理,对Model弱化了

public class BannerPresenter extends BasePresenter<BannerConcacts.IView> implements
    BannerConcacts.IPresenter {

  @Override
  public void requestData(Context context) {

    RetrofitClient.request(context, RetrofitClient.createApi().getJson(),
        new IResponseListener<BannerBean>() {
          @Override
          public void onSuccess(BannerBean data) {
            Logout.e("Tag", "data= " + data.toString());
            getView().onReslutData(data.toString());
          }

          @Override
          public void onFail(OkHttpException failuer) {
            getView().onReslutData("失败= " + failuer.getEmsg());
          }
        });
  }
}

BannerFragment类

BannerFragment就是View的实现类,首先要继承BaseFragment类并通过泛型传入View和Presenter。然后实现BannerConcacts.IView接口并实现其中的方法。使用方式和Activity一样。

public class BannerFragment extends BaseFragment<BannerConcacts.IView, BannerPresenter> implements
    BannerConcacts.IView {

  @Nullable
  @Override
  public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
      @Nullable Bundle savedInstanceState) {
    return inflater.inflate(R.layout.banner, null);
  }

  @Override
  public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    view.findViewById(R.id.getJSON)
        .setOnClickListener((v) -> getPresenter().requestData(getContext()));
  }

  @Override
  public BannerPresenter createPresenter() {
    return new BannerPresenter();
  }

  @Override
  public void onReslutData(String data) {
    Toast.makeText(getContext(), data, Toast.LENGTH_SHORT).show();
  }

}
我对MVP的理解

有些人说MVP适合大型项目,我不这么认为。之前我做过某网的项目使用MVC模式,有些Activity代码行能达到两千多行代码,查找问题很麻烦。我说下我的看法,我认为View层业务逻辑比较多的话就适合MVP模式,业务逻辑比较少的话看个人你可以用MVP也可以用MVC。还有就是封装一个适合自己项目的框架。

Demo地址:https://github.com/pengjunshan/MVPRetrofitRxJava

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

推荐阅读更多精彩内容